From 616caa92b126c36eb7ad55b86f51578cbeb97d89 Mon Sep 17 00:00:00 2001 From: Octopus Date: Thu, 9 Apr 2026 15:09:39 +0800 Subject: [PATCH] fix(models): resolve duplicate keyword argument error when reasoning_effort appears in both config and kwargs (#2017) When a model config includes `reasoning_effort` as an extra YAML field (ModelConfig uses `extra="allow"`), and the thinking-disabled code path also injects `reasoning_effort="minimal"` into kwargs, the previous `model_class(**kwargs, **model_settings_from_config)` call raises: TypeError: got multiple values for keyword argument 'reasoning_effort' Fix by merging the two dicts before instantiation, giving runtime kwargs precedence over config values: `{**model_settings_from_config, **kwargs}`. Fixes #1977 Co-authored-by: octo-patch --- .../harness/deerflow/models/factory.py | 2 +- backend/tests/test_model_factory.py | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/backend/packages/harness/deerflow/models/factory.py b/backend/packages/harness/deerflow/models/factory.py index b05b8625f..900bb71cc 100644 --- a/backend/packages/harness/deerflow/models/factory.py +++ b/backend/packages/harness/deerflow/models/factory.py @@ -109,7 +109,7 @@ def create_chat_model(name: str | None = None, thinking_enabled: bool = False, * elif "reasoning_effort" not in model_settings_from_config: model_settings_from_config["reasoning_effort"] = "medium" - model_instance = model_class(**kwargs, **model_settings_from_config) + model_instance = model_class(**{**model_settings_from_config, **kwargs}) callbacks = build_tracing_callbacks() if callbacks: diff --git a/backend/tests/test_model_factory.py b/backend/tests/test_model_factory.py index 5e980bd1b..573b2fc58 100644 --- a/backend/tests/test_model_factory.py +++ b/backend/tests/test_model_factory.py @@ -690,3 +690,44 @@ def test_openai_responses_api_settings_are_passed_to_chatopenai(monkeypatch): assert captured.get("use_responses_api") is True assert captured.get("output_version") == "responses/v1" + + +# --------------------------------------------------------------------------- +# Duplicate keyword argument collision (issue #1977) +# --------------------------------------------------------------------------- + + +def test_no_duplicate_kwarg_when_reasoning_effort_in_config_and_thinking_disabled(monkeypatch): + """When reasoning_effort is set in config.yaml (extra field) AND the thinking-disabled + path also injects reasoning_effort=minimal into kwargs, the factory must not raise + TypeError: got multiple values for keyword argument 'reasoning_effort'.""" + wte = {"extra_body": {"thinking": {"type": "enabled", "budget_tokens": 5000}}} + # ModelConfig.extra="allow" means extra fields from config.yaml land in model_dump() + model = ModelConfig( + name="doubao-model", + display_name="Doubao 1.8", + description=None, + use="deerflow.models.patched_deepseek:PatchedChatDeepSeek", + model="doubao-seed-1-8-250315", + reasoning_effort="high", # user-set extra field in config.yaml + supports_thinking=True, + supports_reasoning_effort=True, + when_thinking_enabled=wte, + supports_vision=False, + ) + cfg = _make_app_config([model]) + + captured: dict = {} + + class CapturingModel(FakeChatModel): + def __init__(self, **kwargs): + captured.update(kwargs) + BaseChatModel.__init__(self, **kwargs) + + _patch_factory(monkeypatch, cfg, model_class=CapturingModel) + + # Must not raise TypeError + factory_module.create_chat_model(name="doubao-model", thinking_enabled=False) + + # kwargs (runtime) takes precedence: thinking-disabled path sets reasoning_effort=minimal + assert captured.get("reasoning_effort") == "minimal"