From 4d47339ce6b3ba556efa0b89080788a58d5c5e64 Mon Sep 17 00:00:00 2001 From: GareArc Date: Thu, 29 Jan 2026 17:06:29 -0800 Subject: [PATCH] feat: Add parent trace context propagation for workflow-as-tool hierarchy Enables distributed tracing for nested workflows across all trace providers (Langfuse, LangSmith, community providers). When a workflow invokes another workflow via workflow-as-tool, the child workflow now includes parent context attributes that allow trace systems to reconstruct the full execution tree. Changes: - Add parent_trace_context field to WorkflowTool - Set parent context in tool node when invoking workflow-as-tool - Extract and pass parent context through app generator This is a community enhancement (ungated) that improves distributed tracing for all users. Parent context includes: trace_id, node_execution_id, workflow_run_id, and app_id. --- api/core/app/apps/workflow/app_generator.py | 5 ++++- api/core/tools/workflow_as_tool/tool.py | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/api/core/app/apps/workflow/app_generator.py b/api/core/app/apps/workflow/app_generator.py index ee205ed153..5d04ae56e0 100644 --- a/api/core/app/apps/workflow/app_generator.py +++ b/api/core/app/apps/workflow/app_generator.py @@ -147,9 +147,12 @@ class WorkflowAppGenerator(BaseAppGenerator): inputs: Mapping[str, Any] = args["inputs"] - extras = { + extras: dict[str, Any] = { **extract_external_trace_id_from_args(args), } + parent_trace_context = args.get("_parent_trace_context") + if parent_trace_context: + extras["parent_trace_context"] = parent_trace_context workflow_run_id = str(uuid.uuid4()) # FIXME (Yeuoly): we need to remove the SKIP_PREPARE_USER_INPUTS_KEY from the args # trigger shouldn't prepare user inputs diff --git a/api/core/tools/workflow_as_tool/tool.py b/api/core/tools/workflow_as_tool/tool.py index 9c1ceff145..0106f60c0d 100644 --- a/api/core/tools/workflow_as_tool/tool.py +++ b/api/core/tools/workflow_as_tool/tool.py @@ -50,6 +50,7 @@ class WorkflowTool(Tool): self.workflow_call_depth = workflow_call_depth self.label = label self._latest_usage = LLMUsage.empty_usage() + self.parent_trace_context: dict[str, str] | None = None super().__init__(entity=entity, runtime=runtime) @@ -90,11 +91,15 @@ class WorkflowTool(Tool): self._latest_usage = LLMUsage.empty_usage() + args: dict[str, Any] = {"inputs": tool_parameters, "files": files} + if self.parent_trace_context: + args["_parent_trace_context"] = self.parent_trace_context + result = generator.generate( app_model=app, workflow=workflow, user=user, - args={"inputs": tool_parameters, "files": files}, + args=args, invoke_from=self.runtime.invoke_from, streaming=False, call_depth=self.workflow_call_depth + 1,