mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-08 00:10:21 +08:00
* fix(llm): filter unexpected config keys to prevent LangChain warnings (#411) Add allowlist validation for LLM configuration keys to prevent unexpected parameters like SEARCH_ENGINE from being passed to LLM constructors. Changes: - Add ALLOWED_LLM_CONFIG_KEYS set with valid LLM configuration parameters - Filter out unexpected keys before creating LLM instances - Log clear warning messages when unexpected keys are removed - Add unit test for configuration key filtering This fixes the confusing LangChain warning "WARNING! SEARCH_ENGINE is not default parameter. SEARCH_ENGINE was transferred to model_kwargs" that occurred when users accidentally placed configuration keys in wrong sections of conf.yaml. * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
128 lines
4.4 KiB
Python
128 lines
4.4 KiB
Python
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
import pytest
|
|
|
|
from src.llms import llm
|
|
|
|
|
|
class DummyChatOpenAI:
|
|
def __init__(self, **kwargs):
|
|
self.kwargs = kwargs
|
|
|
|
def invoke(self, msg):
|
|
return f"Echo: {msg}"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def patch_chat_openai(monkeypatch):
|
|
monkeypatch.setattr(llm, "ChatOpenAI", DummyChatOpenAI)
|
|
|
|
|
|
@pytest.fixture
|
|
def dummy_conf():
|
|
return {
|
|
"BASIC_MODEL": {"api_key": "test_key", "base_url": "http://test"},
|
|
"REASONING_MODEL": {"api_key": "reason_key"},
|
|
"VISION_MODEL": {"api_key": "vision_key"},
|
|
}
|
|
|
|
|
|
def test_get_env_llm_conf(monkeypatch):
|
|
# Clear any existing environment variables that might interfere
|
|
monkeypatch.delenv("BASIC_MODEL__API_KEY", raising=False)
|
|
monkeypatch.delenv("BASIC_MODEL__BASE_URL", raising=False)
|
|
monkeypatch.delenv("BASIC_MODEL__MODEL", raising=False)
|
|
|
|
monkeypatch.setenv("BASIC_MODEL__API_KEY", "env_key")
|
|
monkeypatch.setenv("BASIC_MODEL__BASE_URL", "http://env")
|
|
conf = llm._get_env_llm_conf("basic")
|
|
assert conf["api_key"] == "env_key"
|
|
assert conf["base_url"] == "http://env"
|
|
|
|
|
|
def test_create_llm_use_conf_merges_env(monkeypatch, dummy_conf):
|
|
# Clear any existing environment variables that might interfere
|
|
monkeypatch.delenv("BASIC_MODEL__BASE_URL", raising=False)
|
|
monkeypatch.delenv("BASIC_MODEL__MODEL", raising=False)
|
|
monkeypatch.setenv("BASIC_MODEL__API_KEY", "env_key")
|
|
result = llm._create_llm_use_conf("basic", dummy_conf)
|
|
assert isinstance(result, DummyChatOpenAI)
|
|
assert result.kwargs["api_key"] == "env_key"
|
|
assert result.kwargs["base_url"] == "http://test"
|
|
|
|
|
|
def test_create_llm_use_conf_invalid_type(monkeypatch, dummy_conf):
|
|
# Clear any existing environment variables that might interfere
|
|
monkeypatch.delenv("BASIC_MODEL__API_KEY", raising=False)
|
|
monkeypatch.delenv("BASIC_MODEL__BASE_URL", raising=False)
|
|
monkeypatch.delenv("BASIC_MODEL__MODEL", raising=False)
|
|
|
|
with pytest.raises(ValueError):
|
|
llm._create_llm_use_conf("unknown", dummy_conf)
|
|
|
|
|
|
def test_create_llm_use_conf_empty_conf(monkeypatch):
|
|
# Clear any existing environment variables that might interfere
|
|
monkeypatch.delenv("BASIC_MODEL__API_KEY", raising=False)
|
|
monkeypatch.delenv("BASIC_MODEL__BASE_URL", raising=False)
|
|
monkeypatch.delenv("BASIC_MODEL__MODEL", raising=False)
|
|
|
|
with pytest.raises(ValueError):
|
|
llm._create_llm_use_conf("basic", {})
|
|
|
|
|
|
def test_get_llm_by_type_caches(monkeypatch, dummy_conf):
|
|
called = {}
|
|
|
|
def fake_load_yaml_config(path):
|
|
called["called"] = True
|
|
return dummy_conf
|
|
|
|
monkeypatch.setattr(llm, "load_yaml_config", fake_load_yaml_config)
|
|
llm._llm_cache.clear()
|
|
inst1 = llm.get_llm_by_type("basic")
|
|
inst2 = llm.get_llm_by_type("basic")
|
|
assert inst1 is inst2
|
|
assert called["called"]
|
|
|
|
|
|
def test_create_llm_filters_unexpected_keys(monkeypatch, caplog):
|
|
"""Test that unexpected configuration keys like SEARCH_ENGINE are filtered out (Issue #411)."""
|
|
import logging
|
|
|
|
# Clear any existing environment variables that might interfere
|
|
monkeypatch.delenv("BASIC_MODEL__API_KEY", raising=False)
|
|
monkeypatch.delenv("BASIC_MODEL__BASE_URL", raising=False)
|
|
monkeypatch.delenv("BASIC_MODEL__MODEL", raising=False)
|
|
|
|
# Config with unexpected keys that should be filtered
|
|
conf_with_unexpected_keys = {
|
|
"BASIC_MODEL": {
|
|
"api_key": "test_key",
|
|
"base_url": "http://test",
|
|
"model": "gpt-4",
|
|
"SEARCH_ENGINE": {"include_domains": ["example.com"]}, # Should be filtered
|
|
"engine": "tavily", # Should be filtered
|
|
}
|
|
}
|
|
|
|
with caplog.at_level(logging.WARNING):
|
|
result = llm._create_llm_use_conf("basic", conf_with_unexpected_keys)
|
|
|
|
# Verify the LLM was created
|
|
assert isinstance(result, DummyChatOpenAI)
|
|
|
|
# Verify unexpected keys were not passed to the LLM
|
|
assert "SEARCH_ENGINE" not in result.kwargs
|
|
assert "engine" not in result.kwargs
|
|
|
|
# Verify valid keys were passed
|
|
assert result.kwargs["api_key"] == "test_key"
|
|
assert result.kwargs["base_url"] == "http://test"
|
|
assert result.kwargs["model"] == "gpt-4"
|
|
|
|
# Verify warnings were logged
|
|
assert any("SEARCH_ENGINE" in record.message for record in caplog.records)
|
|
assert any("engine" in record.message for record in caplog.records)
|