fix(trace): LangSmith trace_id mismatch in chatflow workflow traces (#35979)

This commit is contained in:
kien duong 2026-05-11 09:52:29 +07:00 committed by GitHub
parent 1e6dc62470
commit bf117dd0c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 89 additions and 1 deletions

View File

@ -64,7 +64,9 @@ class LangSmithDataTrace(BaseTraceInstance):
self.generate_name_trace(trace_info)
def workflow_trace(self, trace_info: WorkflowTraceInfo):
trace_id = trace_info.trace_id or trace_info.message_id or trace_info.workflow_run_id
# trace_id must equal the root run's run_id (LangSmith protocol); external trace_id
# cannot be used here as it would cause HTTP 400.
trace_id = trace_info.message_id or trace_info.workflow_run_id
if trace_info.start_time is None:
trace_info.start_time = datetime.now()
message_dotted_order = (
@ -77,6 +79,8 @@ class LangSmithDataTrace(BaseTraceInstance):
)
metadata = trace_info.metadata
metadata["workflow_app_log_id"] = trace_info.workflow_app_log_id
if trace_info.trace_id:
metadata["external_trace_id"] = trace_info.trace_id
if trace_info.message_id:
message_run = LangSmithRunModel(

View File

@ -208,13 +208,17 @@ def test_workflow_trace(trace_instance, monkeypatch: pytest.MonkeyPatch):
assert call_args[0].id == "msg-1"
assert call_args[0].name == TraceTaskName.MESSAGE_TRACE
# trace_id must equal root run's id (message_id), not the external trace_id "trace-1"
assert call_args[0].trace_id == "msg-1"
assert call_args[1].id == "run-1"
assert call_args[1].name == TraceTaskName.WORKFLOW_TRACE
assert call_args[1].parent_run_id == "msg-1"
assert call_args[1].trace_id == "msg-1"
assert call_args[2].id == "node-llm"
assert call_args[2].run_type == LangSmithRunType.llm
assert call_args[2].trace_id == "msg-1"
assert call_args[3].id == "node-other"
assert call_args[3].run_type == LangSmithRunType.tool
@ -604,3 +608,83 @@ def test_get_project_url_error(trace_instance):
trace_instance.langsmith_client.get_run_url.side_effect = Exception("error")
with pytest.raises(ValueError, match="LangSmith get run url failed: error"):
trace_instance.get_project_url()
def _make_workflow_trace_info(
*, message_id: str | None, workflow_run_id: str, trace_id: str | None
) -> WorkflowTraceInfo:
workflow_data = MagicMock()
workflow_data.created_at = _dt()
workflow_data.finished_at = _dt() + timedelta(seconds=1)
return WorkflowTraceInfo(
tenant_id="tenant-1",
workflow_id="wf-1",
workflow_run_id=workflow_run_id,
workflow_run_inputs={},
workflow_run_outputs={},
workflow_run_status="succeeded",
workflow_run_version="1.0",
workflow_run_elapsed_time=1.0,
total_tokens=0,
file_list=[],
query="q",
message_id=message_id,
conversation_id="conv-1" if message_id else None,
start_time=_dt(),
end_time=_dt() + timedelta(seconds=1),
trace_id=trace_id,
metadata={"app_id": "app-1"},
workflow_app_log_id=None,
error=None,
workflow_data=workflow_data,
)
def _patch_workflow_trace_deps(monkeypatch, trace_instance):
monkeypatch.setattr("dify_trace_langsmith.langsmith_trace.sessionmaker", lambda bind: lambda: MagicMock())
monkeypatch.setattr("dify_trace_langsmith.langsmith_trace.db", MagicMock(engine="engine"))
repo = MagicMock()
repo.get_by_workflow_execution.return_value = []
factory = MagicMock()
factory.create_workflow_node_execution_repository.return_value = repo
monkeypatch.setattr("dify_trace_langsmith.langsmith_trace.DifyCoreRepositoryFactory", factory)
monkeypatch.setattr(trace_instance, "get_service_account_with_tenant", lambda app_id: MagicMock())
trace_instance.add_run = MagicMock()
def test_workflow_trace_id_uses_message_id_not_external(trace_instance, monkeypatch):
"""Chatflow with external trace_id: LangSmith trace_id must be message_id, not external."""
trace_info = _make_workflow_trace_info(
message_id="msg-abc",
workflow_run_id="run-xyz",
trace_id="external-999",
)
_patch_workflow_trace_deps(monkeypatch, trace_instance)
trace_instance.workflow_trace(trace_info)
calls = [c[0][0] for c in trace_instance.add_run.call_args_list]
# message run (root) and workflow run (child) must both use message_id as trace_id
assert calls[0].id == "msg-abc"
assert calls[0].trace_id == "msg-abc"
assert calls[1].id == "run-xyz"
assert calls[1].trace_id == "msg-abc"
# external_trace_id preserved in metadata
assert trace_info.metadata.get("external_trace_id") == "external-999"
def test_workflow_trace_id_pure_workflow_uses_run_id(trace_instance, monkeypatch):
"""Pure workflow (no message_id) with external trace_id: trace_id must be workflow_run_id."""
trace_info = _make_workflow_trace_info(
message_id=None,
workflow_run_id="run-xyz",
trace_id="external-999",
)
_patch_workflow_trace_deps(monkeypatch, trace_instance)
trace_instance.workflow_trace(trace_info)
calls = [c[0][0] for c in trace_instance.add_run.call_args_list]
# workflow run is the root; trace_id must equal its run_id
assert calls[0].id == "run-xyz"
assert calls[0].trace_id == "run-xyz"