Skip to content

Add internal shared core for the LangChain-family plugins#1650

Draft
DABH wants to merge 5 commits into
deepagents-pluginfrom
langchain-shared-core
Draft

Add internal shared core for the LangChain-family plugins#1650
DABH wants to merge 5 commits into
deepagents-pluginfrom
langchain-shared-core

Conversation

@DABH

@DABH DABH commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

What

Stacked on #1644. The three LangChain-family plugins (contrib.langgraph, contrib.langsmith, contrib.deepagents) duplicated machinery; this PR factors it into temporalio/contrib/_langchain/ — an internal package (underscore-prefixed, no public-API commitment) that ships unconditionally like other contrib infrastructure, with an import-free __init__ and lazy third-party imports so it loads with none of the family distributions installed (the 3.10 CI lanes, where the deepagents extra cannot install, exercise exactly that).

Prime directive applied throughout: langgraph/langsmith are released and vetted — their observable behavior is frozen; deepagents (unreleased) adapts to their semantics wherever copies disagreed.

  • _aio_to_thread — the LangSmith workflow-safety override existed in both langsmith/_interceptor.py and deepagents/_plugin.py, interacting through langsmith's single process-wide override slot. One copy now, with langsmith's install-once semantics (failures propagate, flag stays unset for retry); langsmith calls it bare, deepagents keeps its exception-tolerant wrapper.
  • _task_cache — langgraph's continue-as-new cache mechanism verbatim (task_id, 32-hex cache_key), wrapped in a TaskResultCache class so each plugin gets its own ContextVar instance and caches never share state. langgraph's module delegates with identical signatures and identity semantics; deepagents rebuilds its result cache on it (its unreleased cache-key format changes — only an in-flight continue-as-new carrying hits across a worker upgrade would notice).
  • _runnable_config — parameterized strip_runnable_config with langgraph's released output shape as the base and the selection rules (checkpoint-key whitelist vs JSON-safe filter) as parameters. Byte-identity is test-enforced: the pre-refactor langgraph implementation is embedded verbatim in the test suite and string-compared (JSON dumps, capturing key order) against the new path across a corpus of configs, including derived cache keys.
  • _messages / _converter / _activity_helpers / _passthrough — family-generic serde (dumpd/load, tool schemas), the exclude_unset Pydantic converter + compose-or-refuse builder, the activity auto-heartbeater and duck-typed LLM HTTP error translation, and the passthrough-list merge (mechanism only — each plugin keeps its own list, since the lists are released behavior).
  • workflow_streams.current_workflow_stream() (small public addition): langgraph previously imported the private publish-signal name and introspected the handler's __self__; the accessor owns that introspection with an isinstance guard. _PUBLISH_SIGNAL stays private.

Verification

  • 25 new unit tests under tests/contrib/langchain_shared/ (byte-identity oracle, cache-instance isolation incl. across copy_context(), override install-once/retry semantics, serde round-trips, converter composition, passthrough merge) + a current_workflow_stream() test.

  • All existing suites unchanged and green as regression oracles: langgraph (incl. the continue-as-new cache oracle and streaming), langsmith (incl. the 1150-line interceptor suite and test_background_io), deepagents, workflow_streams.

  • Whole repo: 1511 passed / 28 skipped; pyright, basedpyright (0 warnings), mypy (379 files), pydocstyle, pydoctor, ruff all clean repo-wide.

  • Samples verification: every LangGraph, LangSmith, and Deep Agents sample in samples-python runs green against this branch — offline suites plus live end-to-end runs (real dev server, real model calls; LangSmith scenarios verified by querying the LangSmith API for the landed trace trees). Full matrix:

    Scenario Mode Result
    langgraph_plugin offline suite (10) + langsmith_tracing offline suite + deepagents_plugin offline suite (23 tests total) pytest
    langgraph_plugin/graph_api/{hello_world, react_agent, human_in_the_loop, continue_as_new} live ✅ 4/4
    langgraph_plugin/functional_api/{hello_world, control_flow, continue_as_new, human_in_the_loop, react_agent} live ✅ 5/5
    deepagents_plugin/{hello_world, react_agent, subagents, filesystem_backend, streaming, continue_as_new, human_in_the_loop} live, real Claude ✅ 7/7
    langgraph_plugin/graph_api/langsmith_tracing + functional_api/langsmith_tracing live, keyed LangSmith ✅ full trace trees land (RunWorkflow root → activity → LangGraph → ChatAnthropic)
    deepagents_plugin/langsmith_tracing live, keyed LangSmith ✅ middleware chain + TemporalModel + ChatAnthropic spans land, correctly linked¹
    langsmith_tracing/{basic, chatbot} live, real OpenAI + keyed LangSmith ✅ full trees rooted at the client-side spans

    Human-in-the-loop scenarios were driven with real approve inputs; LANGSMITH_TRACING=true set for the tracing scenarios (the langsmith SDK's env gate — without it the plugin correctly creates no runs). A fresh-venv pip install "temporalio[…] @ git+…@langchain-shared-core" was verified to build and import all four packages.

    ¹ One pre-existing nit found (not a delta from this PR — this composition's first keyed run): the outermost in-workflow agent-root span can miss the final trace flush because the sample's single-process driver exits immediately after the workflow result; every other span posts and links correctly. Will track on the samples PR.

Commits are staged for review in dependency order: accessor → core+override → cache → config strip → serde/converter/helpers.

DABH added 5 commits July 15, 2026 15:15
The langgraph plugin located the workflow's stream by importing the
private publish-signal name and introspecting the registered handler's
__self__ — fragile coupling to another contrib package's internals.
Expose one audited accessor that owns the introspection (with an
isinstance guard) and migrate langgraph's presence check and instance
fetch onto it. The interceptor's error message is unchanged; the
workflow wrapper gains a defensive error on a path the interceptor
already makes unreachable.
The LangSmith workflow-safety override for @Traceable's aio_to_thread
seam existed twice — in contrib.langsmith and contrib.deepagents — and
the two copies interact through langsmith's single process-wide
override slot (an earlier deepagents uninstall would have stripped the
langsmith plugin's override permanently). Introduce
temporalio.contrib._langchain, an internal shared package for the
LangChain-family plugins (import-free __init__, lazy third-party
imports, ships unconditionally like other contrib infrastructure), and
move the override there with langsmith's install-once semantics:
failures propagate and leave the flag unset so a retry can succeed.
langsmith calls the shared installer bare (its dependency is
guaranteed); deepagents keeps its exception-tolerant wrapper for
workers without langsmith installed.
The langgraph and deepagents plugins each implemented the same
ContextVar-backed dict cache for carrying completed results across
continue-as-new, with different key derivations. Move the langgraph
mechanism (the released, vetted one) into the shared core as
TaskResultCache — one instance per plugin, so the caches never share
state — plus its task_id/cache_key helpers verbatim. langgraph's
_task_cache module becomes a delegating surface with identical
function signatures and semantics (identity-preserving set, same
32-hex keys); deepagents rebuilds its result cache on the shared
mechanism, keeping copy-on-set and snapshot semantics at its own
layer. deepagents cache keys change format (previously 64-hex over a
different composition) — acceptable while unreleased; only an
in-flight continue-as-new carrying cache hits across a worker upgrade
would notice.
langgraph and deepagents each stripped RunnableConfig for the activity
boundary with deliberately different selection rules: langgraph
whitelists its seven checkpoint/resumption configurable keys and
passes metadata through untouched; deepagents keeps any JSON-safe
non-dunder configurable entry and filters metadata values. Move the
mechanism to the shared core with langgraph's released output shape as
the base (tags/metadata always present, truthy run_name/run_id,
whitelist iterated in whitelist order) and the selection rules as
parameters.

langgraph's strip becomes a thin wrapper over its whitelist — a
byte-identity test embeds the pre-refactor implementation verbatim and
string-compares JSON dumps plus derived continue-as-new cache keys
across a corpus, so released payloads and carried caches are pinned.
set_langgraph_config now shares the whitelist constant instead of
duplicating the tuple. deepagents adopts the shared shape (accepted
deltas on an unreleased plugin: always-present tags/metadata, truthy
gates, no str() coercion of run ids) and re-exports the shared
rebuild/is_jsonish under its existing names.
…rough merge

Move the remaining family-generic machinery out of the deepagents
plugin into the shared core: langchain_core dumpd/load message and
object serialization plus the OpenAI tool-schema advertisement; the
exclude_unset Pydantic payload converter and its compose-or-refuse
data-converter builder (error text now names the refusing plugin); the
activity auto-heartbeater and duck-typed LLM HTTP error translation;
and the order-preserving passthrough-list merge (mechanism only — each
plugin keeps its own module list, since the lists are released
behavior for langgraph/langsmith). deepagents re-exports everything
under its existing names, so its sibling modules and tests are
unchanged. The converter module is the one core module with an eager
contrib.pydantic import; it is only imported by plugins that wire the
converter, and the core __init__ stays import-free.
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.

1 participant