Skip to content

Add dynamic node/task summaries to LangGraph plugin#1612

Open
DABH wants to merge 6 commits into
mainfrom
langgraph-dynamic-summaries
Open

Add dynamic node/task summaries to LangGraph plugin#1612
DABH wants to merge 6 commits into
mainfrom
langgraph-dynamic-summaries

Conversation

@DABH

@DABH DABH commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What

Adds dynamic, runtime-computed summaries to the LangGraph plugin, matching the summary_fn capability the Google ADK integration already exposes.

  • New per-node / per-task summary_fn(args, kwargs) -> str | None, settable via Graph API node metadata or Functional activity_options[name], plus a plugin-wide default_summary_fn (overridable per node).
  • execute_in="activity" nodes: the result is set as the activity summary (carried in user_metadata on each ActivityTaskScheduledEvent, visible in UI/CLI/history).
  • execute_in="workflow" nodes: there is no activity, so the result updates the workflow's current details via workflow.set_current_details (last-writer-wins).
  • The static summary activity option already flowed through to execute_activity; this is now documented. Setting both a static summary and a summary_fn on the same node raises ValueError.
  • summary_fn runs in workflow context, so it must be deterministic and must not raise. It is replay-safe: summaries ride in user_metadata, which is not part of command/history matching.

Test plan

  • New 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_fn raises; summary_fn not leaked into node config["metadata"]; workflow-node current_details via __temporal_workflow_metadata; replay safety.
  • tests/contrib/langgraph suite: 44 passed. The lone failure, test_continue_as_new, is a pre-existing flake (an asyncio "in select" teardown timing issue that reproduces on main independently of this change).
  • ruff, pyright, basedpyright, mypy, pydocstyle clean on the touched files.
  • Ran the LangGraph samples in temporalio/samples-python (tests/langgraph_plugin/) against this branch vs. the released SDK — per-test outcomes identical (9 passed; the 2 human_in_the_loop failures occur in both runs solely from a missing ANTHROPIC_API_KEY).

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.
@DABH DABH requested review from a team as code owners June 22, 2026 19:53
Comment thread temporalio/contrib/langgraph/_plugin.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | None plus plugin-wide default_summary_fn, with validation against using static summary and summary_fn together.
  • 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.

Comment thread temporalio/contrib/langgraph/_workflow.py Outdated
Comment thread temporalio/contrib/langgraph/README.md
DABH added 2 commits June 29, 2026 17:19
- Drop the plugin-level default_summary_fn; summary_fn is now set only
  per node (Graph metadata) / per task (activity_options), matching the
  ADK plugin's per-model scoping so each node manages its own summary.
- Normalize a static summary into a summary_fn (a constant function), so
  static and dynamic summaries share one resolution path feeding the
  activity summary kwarg / set_current_details.
- Workflow-bound nodes now clear current details when summary_fn returns
  empty, so a node no longer shows a stale summary from an earlier node.

@xumaple xumaple left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both summary and summary_fn are in _LANGGRAPH_OPTION_KEYS, so either can come from the defaults or from the node. Because they're different dict keys, a default and a per-node value don't override each other — they coexist in the merged opts, and then the guard sees both and raises. Concretely:

  • default_activity_options={"summary_fn": global_fn} + one node sets a static summary → ValueError
  • default_activity_options={"summary": "label"} + one node sets summary_fn → ValueError

That first case is plausible: a single dynamic summarizer as the default, with one node wanting a fixed label. Today you can't express "node-level setting overrides the inherited default" — mixing the two types anywhere in the resolved options is rejected, which is stricter than ADK's "don't set both on the same object."

Consider resolving based on hierarchy? If a node supplies either key, drop the inherited other-key from the defaults first, so the more-specific node setting wins (the normal layering expectation). The guard then only fires when both are set at the same level. Or some other way of achieving this same of allowing a default of one type and an override of another type.

summary and summary_fn are two forms of one setting, but as separate
dict keys a default of one form and a node-level value of the other
survived the merge together and tripped the exclusivity guard (e.g.
default_activity_options={"summary_fn": fn} + one node with a static
summary raised ValueError). Merge now drops the inherited form when a
node/task supplies either, so the more-specific setting wins, and the
exclusivity check only rejects both forms set at the same level
(per-node, or in default_activity_options via a new init check).
@DABH

DABH commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

@xumaple Great catch, thanks. Fixed so that now node/task options drop an inherited summary/summary_fn of either form before merging, so the more-specific setting wins (your first example, a default summary_fn plus one node with a static summary, now works; added tests to cover both override directions). The exclusivity check only fires when both forms are set at the same level: per-node, or within default_activity_options (new init-time check).

Also considered collapsing to a single summary: str | Callable key, which would make conflicts impossible by construction, but kept the two keys so summary stays consistently str-typed with execute_activity/ActivityConfig, and summary_fn keeps the same name and semantics as the ADK plugin's.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants