fix(agent): preserve plugin-declared model parameters through Agent Soul (#40163)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
chelsealong
2026-08-09 16:48:01 +08:00
committed by GitHub
parent e09eee8fd1
commit 7522ae14b2
11 changed files with 95 additions and 4 deletions
+16 -2
View File
@@ -159,8 +159,12 @@ class AgentBackendModelConfig(BaseModel):
# ``DifyPluginLLMLayerConfig.model_settings`` is pydantic_ai's ``ModelSettings``
# TypedDict (closed: unknown keys are rejected, explicit ``None`` values fail the
# per-field type checks). Agent Soul model settings carry a wider, nullable shape
# (``stop`` / ``response_format`` plus null-padded fields), so the layer config
# only receives the keys the runtime contract accepts.
# (``stop`` / ``response_format`` plus null-padded fields, plus arbitrary
# plugin-declared parameters such as Qwen's ``enable_thinking``), so the layer
# config only receives the keys the runtime contract accepts directly; anything
# else is forwarded through ``extra_body``, the TypedDict's own escape hatch for
# provider-specific parameters (see
# ``dify_agent.adapters.llm.model._map_model_settings_to_parameters``).
_AGENT_MODEL_SETTINGS_PASSTHROUGH_KEYS = (
"temperature",
"top_p",
@@ -168,6 +172,7 @@ _AGENT_MODEL_SETTINGS_PASSTHROUGH_KEYS = (
"frequency_penalty",
"max_tokens",
)
_AGENT_MODEL_SETTINGS_KNOWN_KEYS = frozenset({*_AGENT_MODEL_SETTINGS_PASSTHROUGH_KEYS, "stop", "response_format"})
def _agent_model_settings(settings: Mapping[str, JsonValue]) -> dict[str, JsonValue] | None:
@@ -177,6 +182,15 @@ def _agent_model_settings(settings: Mapping[str, JsonValue]) -> dict[str, JsonVa
stop = settings.get("stop")
if isinstance(stop, list) and stop:
sanitized["stop_sequences"] = stop
extra_body: dict[str, JsonValue] = {
key: value
for key, value in settings.items()
if key not in _AGENT_MODEL_SETTINGS_KNOWN_KEYS and value is not None
}
if extra_body:
sanitized["extra_body"] = extra_body
return sanitized or None
+8 -2
View File
@@ -538,8 +538,14 @@ class AgentModelResponseFormatConfig(AgentFlexibleConfig):
type: str | None = Field(default=None, max_length=64)
class AgentSoulModelSettings(BaseModel):
model_config = ConfigDict(extra="ignore")
class AgentSoulModelSettings(AgentFlexibleConfig):
"""Model parameters for the Agent Soul model.
Model plugins can declare arbitrary parameters via ``parameter_rules``
(e.g. Qwen/Tongyi's ``enable_thinking``) beyond the common OpenAI-style
fields typed below, so extra keys must round-trip through persistence
rather than being dropped.
"""
temperature: float | None = None
top_p: float | None = None
+7
View File
@@ -14933,6 +14933,13 @@ Reference to model credentials resolved only at runtime.
#### AgentSoulModelSettings
Model parameters for the Agent Soul model.
Model plugins can declare arbitrary parameters via ``parameter_rules``
(e.g. Qwen/Tongyi's ``enable_thinking``) beyond the common OpenAI-style
fields typed below, so extra keys must round-trip through persistence
rather than being dropped.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| frequency_penalty | number | | No |
@@ -119,6 +119,29 @@ def test_request_builder_separates_agent_soul_and_workflow_job_prompt():
assert dumped["composition"]["layers"][2]["config"]["user"] == "Summarize the report."
def test_request_builder_forwards_plugin_specific_model_settings_via_extra_body():
run_input = _run_input().model_copy(
update={
"model": AgentBackendModelConfig(
plugin_id="langgenius/tongyi",
model_provider="tongyi",
model="qwen-plus-latest",
credentials={"api_key": "secret-key"},
model_settings={"temperature": 0.7, "enable_thinking": True, "thinking_budget": 4096},
)
}
)
request = AgentBackendRunRequestBuilder().build_for_workflow_node(run_input)
layers = {layer.name: layer for layer in request.composition.layers}
model_config = cast(DifyPluginLLMLayerConfig, layers[DIFY_AGENT_MODEL_LAYER_ID].config)
assert model_config.model_settings == {
"temperature": 0.7,
"extra_body": {"enable_thinking": True, "thinking_budget": 4096},
}
@pytest.mark.parametrize("agent_config_version_kind", ["snapshot", "draft"])
def test_agent_app_request_builder_keeps_agent_soul_prompt_for_snapshot_and_draft(
agent_config_version_kind: str,
@@ -2,6 +2,7 @@ import pytest
from core.workflow.file_reference import build_file_reference
from models.agent_config_entities import (
AgentSoulModelSettings,
DeclaredArrayItem,
DeclaredOutputChildConfig,
DeclaredOutputConfig,
@@ -9,6 +10,22 @@ from models.agent_config_entities import (
)
def test_agent_soul_model_settings_preserves_plugin_declared_parameters() -> None:
settings = AgentSoulModelSettings.model_validate(
{
"temperature": 0.7,
"enable_thinking": True,
"thinking_budget": 4096,
}
)
dumped = settings.model_dump(mode="json", exclude_none=True)
assert dumped["temperature"] == 0.7
assert dumped["enable_thinking"] is True
assert dumped["thinking_budget"] == 4096
def test_file_default_value_accepts_canonical_reference_mapping() -> None:
reference = build_file_reference(record_id="tool-file-1")
@@ -1586,6 +1586,7 @@ export type AgentSoulModelSettings = {
stop?: Array<string> | null
temperature?: number | null
top_p?: number | null
[key: string]: unknown
}
export type AgentSandboxProviderConfig = {
@@ -2037,6 +2037,13 @@ export const zAgentModelResponseFormatConfig = z.object({
/**
* AgentSoulModelSettings
*
* Model parameters for the Agent Soul model.
*
* Model plugins can declare arbitrary parameters via ``parameter_rules``
* (e.g. Qwen/Tongyi's ``enable_thinking``) beyond the common OpenAI-style
* fields typed below, so extra keys must round-trip through persistence
* rather than being dropped.
*/
export const zAgentSoulModelSettings = z.object({
frequency_penalty: z.number().nullish(),
@@ -2846,6 +2846,7 @@ export type AgentSoulModelSettings = {
stop?: Array<string> | null
temperature?: number | null
top_p?: number | null
[key: string]: unknown
}
export type AgentSandboxProviderConfig = {
@@ -3643,6 +3643,13 @@ export const zAgentModelResponseFormatConfig = z.object({
/**
* AgentSoulModelSettings
*
* Model parameters for the Agent Soul model.
*
* Model plugins can declare arbitrary parameters via ``parameter_rules``
* (e.g. Qwen/Tongyi's ``enable_thinking``) beyond the common OpenAI-style
* fields typed below, so extra keys must round-trip through persistence
* rather than being dropped.
*/
export const zAgentSoulModelSettings = z.object({
frequency_penalty: z.number().nullish(),
@@ -924,6 +924,7 @@ export type AgentSoulModelSettings = {
stop?: Array<string> | null
temperature?: number | null
top_p?: number | null
[key: string]: unknown
}
export type AgentSandboxProviderConfig = {
@@ -1155,6 +1155,13 @@ export const zAgentModelResponseFormatConfig = z.object({
/**
* AgentSoulModelSettings
*
* Model parameters for the Agent Soul model.
*
* Model plugins can declare arbitrary parameters via ``parameter_rules``
* (e.g. Qwen/Tongyi's ``enable_thinking``) beyond the common OpenAI-style
* fields typed below, so extra keys must round-trip through persistence
* rather than being dropped.
*/
export const zAgentSoulModelSettings = z.object({
frequency_penalty: z.number().nullish(),