Add dynamic node/task summaries to LangGraph plugin#1612
Conversation
Adds a per-node/per-task summary_fn(args, kwargs) -> str | None (and a plugin-wide default_summary_fn) that computes a Temporal summary at runtime from the node's input. - execute_in="activity" nodes: the result sets the activity summary (user_metadata, shown on each scheduled-activity event). - execute_in="workflow" nodes: the result updates the workflow's current details via workflow.set_current_details (last-writer-wins). A static summary activity option already flowed through to execute_activity; this is now documented. Setting both a static summary and summary_fn on the same node raises ValueError. summary_fn runs in workflow context (must be deterministic and must not raise) and is replay-safe, since summaries ride in user_metadata.
| """Prepare a node or task to execute as an activity or inline in the workflow.""" | ||
| opts = kwargs or {} | ||
| execute_in = opts.pop("execute_in") | ||
| node_summary_fn = opts.pop("summary_fn", None) |
There was a problem hiding this comment.
Why pop from the opts if we're already passing the opts? Should this be a get()?
There was a problem hiding this comment.
So I believe it has to be pop, not get: a few lines down the leftover opts is splatted into wrap_execute_activity(..., **opts), which forwards them to workflow.execute_activity(...). summary_fn isn't a valid execute_activity kwarg, and we also pass it explicitly as summary_fn=summary_fn, so leaving it in opts would give some error about an unexpected/duplicate keyword. It's the same reason execute_in is popped on the line right above.
Pushed a comment to make that explicit in case that's helpful!
There was a problem hiding this comment.
Hmm, is summary a valid kwarg? I wonder if we could normalize summary and summary_fn into the relevant kwarg
There was a problem hiding this comment.
Pull request overview
Adds runtime-computed node/task summaries to the temporalio.contrib.langgraph plugin, aligning it with existing dynamic-summary capabilities in other contrib integrations and improving observability in Temporal UI/CLI.
Changes:
- Introduces per-node/per-task
summary_fn(args, kwargs) -> str | Noneplus plugin-widedefault_summary_fn, with validation against using staticsummaryandsummary_fntogether. - Plumbs computed summaries into activity scheduling (
execute_in="activity") and into workflow current details (execute_in="workflow"). - Adds documentation and a dedicated test suite covering activity-history summaries, defaults/overrides, metadata stripping, workflow current details, and replay behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/contrib/langgraph/test_summary_fn.py | New tests validating summary_fn behavior for activity- and workflow-executed nodes, plus replay safety. |
| temporalio/contrib/langgraph/README.md | Documents static summaries and new dynamic summary_fn/default_summary_fn behavior. |
| temporalio/contrib/langgraph/_workflow.py | Adds workflow-side summary_fn support via workflow.set_current_details. |
| temporalio/contrib/langgraph/_plugin.py | Adds default_summary_fn, plumbs per-node/task summary_fn, and strips it from node metadata. |
| temporalio/contrib/langgraph/_activity.py | Computes dynamic activity summary on schedule path and passes it to workflow.execute_activity. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Workflow-side nodes have no activity to carry a summary, so a | ||
| truthy ``summary_fn`` result updates the workflow's current details | ||
| via :func:`temporalio.workflow.set_current_details` (last-writer-wins). | ||
| """ | ||
|
|
||
| async def wrapper(*args: Any, **kwargs: Any) -> Any: | ||
| if summary_fn is not None: | ||
| summary = summary_fn(args, kwargs) | ||
| if summary: | ||
| workflow.set_current_details(summary) |
What
Adds dynamic, runtime-computed summaries to the LangGraph plugin, matching the
summary_fncapability the Google ADK integration already exposes.summary_fn(args, kwargs) -> str | None, settable via Graph API nodemetadataor Functionalactivity_options[name], plus a plugin-widedefault_summary_fn(overridable per node).execute_in="activity"nodes: the result is set as the activitysummary(carried inuser_metadataon eachActivityTaskScheduledEvent, visible in UI/CLI/history).execute_in="workflow"nodes: there is no activity, so the result updates the workflow's current details viaworkflow.set_current_details(last-writer-wins).summaryactivity option already flowed through toexecute_activity; this is now documented. Setting both a staticsummaryand asummary_fnon the same node raisesValueError.summary_fnruns in workflow context, so it must be deterministic and must not raise. It is replay-safe: summaries ride inuser_metadata, which is not part of command/history matching.Test plan
tests/contrib/langgraph/test_summary_fn.py(11 tests): activity summary in history; dynamic/None/empty variants;default_summary_fn+ per-node override; static-suppresses-default; static+summary_fnraises;summary_fnnot leaked into nodeconfig["metadata"]; workflow-nodecurrent_detailsvia__temporal_workflow_metadata; replay safety.tests/contrib/langgraphsuite: 44 passed. The lone failure,test_continue_as_new, is a pre-existing flake (an asyncio "in select" teardown timing issue that reproduces onmainindependently of this change).ruff,pyright,basedpyright,mypy,pydocstyleclean on the touched files.temporalio/samples-python(tests/langgraph_plugin/) against this branch vs. the released SDK — per-test outcomes identical (9 passed; the 2human_in_the_loopfailures occur in both runs solely from a missingANTHROPIC_API_KEY).