mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(runtime): persist the effective model name for every run (#6980)
run_agent() only refreshed runs.model_name when the request named a model, and it read the effective model from agent.metadata, which compiled LangGraph graphs do not have. The default model (no model_name requested) and fallback models were therefore never persisted. Use the effective model that lead-agent assembly already resolves, and write only when it differs from the stored value. Supersedes #3110. Fixes #3109. Co-authored-by: Yi Tang <6054101+yitang@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8d5f5fd0a8
commit
a1349a1fe1
@ -1395,16 +1395,11 @@ async def run_agent(
|
||||
graph_input["goal"] = Overwrite(copy.deepcopy(current_values.get("goal")))
|
||||
graph_input["goal_outcome"] = None
|
||||
|
||||
# Capture the effective (resolved) model name from the agent's metadata.
|
||||
# _resolve_model_name in agent.py may return the default model if the
|
||||
# requested name is not in the allowlist — this update ensures the
|
||||
# persisted model_name reflects the actual model used.
|
||||
if record.model_name is not None:
|
||||
resolved = getattr(agent, "metadata", {}) or {}
|
||||
if isinstance(resolved, dict):
|
||||
effective = resolved.get("model_name")
|
||||
if effective and effective != record.model_name:
|
||||
await run_manager.update_model_name(record.run_id, effective)
|
||||
# Persist the model assembly resolved — the default when the request
|
||||
# named none, or a fallback when the requested model is unavailable —
|
||||
# so the stored model_name reflects the actual model used (#3109).
|
||||
if effective_model and effective_model != record.model_name:
|
||||
await run_manager.update_model_name(record.run_id, effective_model)
|
||||
|
||||
# 4. Attach checkpointer and store
|
||||
if checkpointer is not None:
|
||||
|
||||
@ -210,6 +210,66 @@ async def test_run_agent_tags_the_effective_model(monkeypatch, requested_model):
|
||||
assert "model:missing-model" not in tags
|
||||
|
||||
|
||||
class _ModelNameRecordingRunManager(_FakeRunManager):
|
||||
def __init__(self) -> None:
|
||||
self.model_name_updates: list[tuple[str, str | None]] = []
|
||||
|
||||
async def update_model_name(self, run_id: str, model_name: str | None) -> None:
|
||||
self.model_name_updates.append((run_id, model_name))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("requested_model", "expected_updates"),
|
||||
[
|
||||
(None, [("run-persist-model", "actual-model")]),
|
||||
("missing-model", [("run-persist-model", "actual-model")]),
|
||||
("actual-model", []),
|
||||
],
|
||||
)
|
||||
async def test_run_agent_persists_the_effective_model(monkeypatch, requested_model, expected_updates):
|
||||
"""The run record stores the model that ran, including when none was requested (#3109)."""
|
||||
from deerflow.agents.lead_agent import agent as lead_agent_module
|
||||
from deerflow.config.app_config import AppConfig
|
||||
from deerflow.config.model_config import ModelConfig
|
||||
from deerflow.config.sandbox_config import SandboxConfig
|
||||
|
||||
fake_agent = _FakeAgent()
|
||||
app_config = AppConfig(
|
||||
models=[ModelConfig(name="actual-model", model="actual-model", use="langchain_openai:ChatOpenAI")],
|
||||
sandbox=SandboxConfig(use="deerflow.sandbox.local:LocalSandboxProvider"),
|
||||
)
|
||||
monkeypatch.setattr(lead_agent_module, "create_chat_model", lambda **kwargs: object())
|
||||
monkeypatch.setattr(lead_agent_module, "create_agent", lambda **kwargs: fake_agent)
|
||||
|
||||
def agent_factory(config):
|
||||
return lead_agent_module.assemble_lead_agent(config, app_config=app_config)
|
||||
|
||||
record = RunRecord(
|
||||
run_id="run-persist-model",
|
||||
thread_id="thread-persist-model",
|
||||
assistant_id="lead-agent",
|
||||
status=RunStatus.pending,
|
||||
on_disconnect=DisconnectMode.cancel,
|
||||
model_name=requested_model,
|
||||
)
|
||||
record.abort_event = asyncio.Event()
|
||||
run_manager = _ModelNameRecordingRunManager()
|
||||
|
||||
await run_agent(
|
||||
_FakeBridge(),
|
||||
run_manager,
|
||||
record,
|
||||
ctx=RunContext(checkpointer=None, app_config=app_config),
|
||||
agent_factory=agent_factory,
|
||||
graph_input={"messages": []},
|
||||
config={"configurable": {"thread_id": record.thread_id, "model_name": requested_model}},
|
||||
)
|
||||
|
||||
assert fake_agent.captured_config is not None, "astream was not invoked"
|
||||
assert run_manager.model_name_updates == expected_updates
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_agent_uses_context_user_id_over_contextvar(monkeypatch):
|
||||
"""A run carrying ``context.user_id`` traces to that user, not the contextvar.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user