mirror of
https://github.com/langgenius/dify.git
synced 2026-08-31 01:36:38 +08:00
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:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user