Compare commits

...
Author SHA1 Message Date
William FHandGitHub 9b9bf88aee fix(checkpoint-postgres): Remove python invalid escape warning (#5441) 2025-07-10 22:38:05 +00:00
William FHandGitHub 67a86f2dc2 fix[docs]: Add missing flags for langgraph up command (#5429) 2025-07-10 02:07:48 +00:00
Sam CrowderandGitHub ad44d1fe66 docs: [LangGraph Changelog Bot] Changelog updates for new version(s) (#5427) 2025-07-09 18:41:03 -07:00
Sam Crowder 5c45f7c330 Update changelog via LangGraph Changelog Bot 2025-07-09 18:06:04 -07:00
Sam CrowderandGitHub bb2f448175 docs: [LangGraph Changelog Bot] Changelog updates for new version(s) (#5425) 2025-07-09 13:55:14 -07:00
Sam Crowder 8a9f3dbf6f Update changelog via LangGraph Changelog Bot 2025-07-09 13:34:04 -07:00
Sam Crowder 63f051ad28 Update changelog via LangGraph Changelog Bot 2025-07-09 13:25:47 -07:00
Sydney RunkleandGitHub 0ab9770056 release(langgraph): v0.5.2 (#5421)
langgraph bump
2025-07-09 19:10:20 +00:00
Eugene YurtsevandGitHub 8ca5e56f52 fix(docs): links in examples file (#5420)
Fix broken links in examples
2025-07-09 14:59:41 -04:00
0733ec65ad docs: introduce LangGraph Server changelog (#5417)
* introduce changelog

* fix spelling

* Update docs/mkdocs.yml

Co-authored-by: Lauren Hirata Singh <lauren@langchain.dev>

* Update docs/docs/cloud/reference/langgraph_server_changelog.md

Co-authored-by: Lauren Hirata Singh <lauren@langchain.dev>

* Update docs/docs/cloud/reference/langgraph_server_changelog.md

Co-authored-by: Lauren Hirata Singh <lauren@langchain.dev>

* Update langgraph_server_changelog.md

---------

Co-authored-by: Lauren Hirata Singh <lauren@langchain.dev>
2025-07-09 14:37:39 -04:00
Sydney RunkleandGitHub 543cbe9032 chore: add PR title linter (#5416) 2025-07-09 14:12:00 -04:00
Sydney RunkleandGitHub 6eace78c53 patch[langgraph]: Fix hint for invoke/stream to allow for Command and None (#5414)
use Command and None as well
2025-07-09 13:23:28 -04:00
jitoandGitHub 1240f8bdca fix: correct troubleshooting link path (#5411)
fix: correct troubleshooting link path from index.md.md to index.md

Signed-off-by: jitokim <pigberger70@gmail.com>
2025-07-09 16:20:33 +00:00
Eugene YurtsevandGitHub 4d7c107bb8 Update config.yml (#5412) 2025-07-09 12:19:49 -04:00
Andrew NguonlyandGitHub 7a4fd25185 docs: Add RESUMABLE_STREAM_TTL_SECONDS to env vars list (#5413)
* Add RESUMABLE_STREAM_TTL_SECONDS to env vars list.

* Update default value for BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS.

* Update LANGGRAPH_POSTGRES_POOL_MAX_SIZE description.
2025-07-09 10:53:21 -04:00
Kai-WendelandGitHub 7a66213535 Fix typo in types.py in the interrupt example (#5407)
Update types.py

This example still lacked to include `Command`.
2025-07-08 20:49:04 +00:00
nlimpidandGitHub c8d32f104d fix(doc): remove incorrect navigation title overrides for mobile (#5399) 2025-07-08 20:09:57 +00:00
William FHandGitHub 2fee649980 chore: (cli) Update description of disable_meta (#5406) 2025-07-08 19:55:10 +00:00
William FHandGitHub 0d8a8c5847 feat: [CLI] Add arg to retain build deps (setuptools, pip, wheel) (#5404) 2025-07-08 19:41:47 +00:00
Michael LiandGitHub 9cb6365914 docs: update file paths to make the examples more robust (#5382)
* cli: update file paths to make the examples more robust

* fix: fix the prompt path
2025-07-08 18:58:04 +00:00
Jake BroekhuizenandGitHub fb1c0ae9f9 docs: Updating mcp_tools_node fn & referencing runtime graph rebuild docs (#5328)
Fix: Updating mcp_tools_node fn & referencing runtime graph rebuild docs
2025-07-08 12:55:15 -04:00
Lauren Hirata SinghandGitHub 4de1bd6e66 docs: cleanup (#5401)
* docs: cleanup

* fix nav

* fix

* fix nits
2025-07-08 12:33:14 -04:00
+8
Lauren Hirata SinghGitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>sydney-runkleSydney RunkleAndrew NguonlyMichael LijitoSerhii Polishchukhari-dhanushkodidependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Fadel AkramDavidYoussef Ahmed Mohamed Abdelrahmangithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Nick RileyEugene Yurtsevccurme
f21fc056bf docs: Convert example notebooks (#5381)
* Agentic RAG

* Fix formatting

* Agent supervisor

* fix links

* SQL agent

* Graph Runs in LS

* fix format

* Autogen + LG tutorial

* fixes

* update sql

* remove old notebooks

* docs: Add section about data region for LGP data plane (#5378)

Add section about data region.

* fix: remove empty notebook (#5379)

* Fix docstring for _unset_config_context function (#5374)

Signed-off-by: jitokim <pigberger70@gmail.com>

* Fix typo in StreamMode debug description: checlkpoints → checkpoints (#5371)

Signed-off-by: jitokim <pigberger70@gmail.com>

* fix: remove unused import in generate_llms_text.py (#5380)

* dcos: Fix deprecation of TavilySearch (#5375)

Fix deprecation: The class `TavilySearchResults` was deprecated in LangChain 0.3.25 and will be removed in 1.0

* Fix typo: funtion → function (#5370)

fix typos

Signed-off-by: jitokim <pigberger70@gmail.com>

* docs: feedback edits (#5387)

* docs: update lgp deployment metric list (#5388)

* chore(deps): bump peter-evans/create-pull-request from 6 to 7 (#5365)

Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6 to 7.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v6...v7)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* docs: correct link in docs/docs/how-tos/graph-api.md (#5377)

Update graph-api.md

* docs: Update quick_start.md Rest API Guide (#5368)

Update quick_start.md Rest API Guide

The curl command in the quick start needs some minor changes to work out of the box. I hope by adding these changes then new users can get started more quickly

* Remove duplicate CONFIG_KEY_CHECKPOINT_MAP from RESERVED set (#5372)

Signed-off-by: jitokim <pigberger70@gmail.com>

* docs: fix typo in persistence (#5329)

* docs: fix typo in application_structure

* docs: fix typo in persistence

* chore[deps]: upgrade dependencies with `uv lock --upgrade` (#5358)

* chore: upgrade dependencies with `uv lock --upgrade`

* linting

* upgrade PR title

---------

Co-authored-by: sydney-runkle <54324534+sydney-runkle@users.noreply.github.com>
Co-authored-by: Sydney Runkle <sydneymarierunkle@gmail.com>

* Updated examples for SummarizationNode to account for serde with persistence layers (#5257)

* docs: move script into scripts (#5384)

* docs: update sql tutorial (#5389)

---------

Signed-off-by: jitokim <pigberger70@gmail.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Andrew Nguonly <andrewnguonly@users.noreply.github.com>
Co-authored-by: Michael Li <michaelli65535@gmail.com>
Co-authored-by: jito <pigberger70@gmail.com>
Co-authored-by: Serhii Polishchuk <serhii.polishchuk@gelato.com>
Co-authored-by: hari-dhanushkodi <hari@langchain.dev>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Fadel Akram <af8356207@gmail.com>
Co-authored-by: David <31293924+dreadn0ught@users.noreply.github.com>
Co-authored-by: Youssef Ahmed Mohamed Abdelrahman <109446360+unauthorised-401@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: sydney-runkle <54324534+sydney-runkle@users.noreply.github.com>
Co-authored-by: Sydney Runkle <sydneymarierunkle@gmail.com>
Co-authored-by: Nick Riley <nick@sparkida.com>
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
Co-authored-by: ccurme <chester.curme@gmail.com>
2025-07-08 10:36:47 -04:00
lc-arjunandGitHub 1f1d032430 docs: add tunnel flag to node server docs (#5398) 2025-07-08 06:59:02 -07:00
lc-arjunandGitHub 6f86a8c4cb Revert "docs: add -tunnel flag for node server" (#5397)
Revert "docs: add -tunnel flag for node server (#5373)"

This reverts commit 4abf948462.
2025-07-08 06:49:13 -07:00
Nick RileyandGitHub d5ab8b42e0 Updated examples for SummarizationNode to account for serde with persistence layers (#5257) 2025-07-07 16:31:55 -07:00
87f2e69395 chore[deps]: upgrade dependencies with uv lock --upgrade (#5358)
* chore: upgrade dependencies with `uv lock --upgrade`

* linting

* upgrade PR title

---------

Co-authored-by: sydney-runkle <54324534+sydney-runkle@users.noreply.github.com>
Co-authored-by: Sydney Runkle <sydneymarierunkle@gmail.com>
2025-07-07 23:24:32 +00:00
Youssef Ahmed Mohamed AbdelrahmanandGitHub 4c73b176ff docs: fix typo in persistence (#5329)
* docs: fix typo in application_structure

* docs: fix typo in persistence
2025-07-07 23:22:53 +00:00
jitoandGitHub 4321ed0f87 Remove duplicate CONFIG_KEY_CHECKPOINT_MAP from RESERVED set (#5372)
Signed-off-by: jitokim <pigberger70@gmail.com>
2025-07-07 19:19:16 -04:00
DavidandGitHub cba4d9e3bc docs: Update quick_start.md Rest API Guide (#5368)
Update quick_start.md Rest API Guide

The curl command in the quick start needs some minor changes to work out of the box. I hope by adding these changes then new users can get started more quickly
2025-07-07 19:18:55 -04:00
Fadel AkramandGitHub fa36a50444 docs: correct link in docs/docs/how-tos/graph-api.md (#5377)
Update graph-api.md
2025-07-07 23:14:17 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5413f9db9f chore(deps): bump peter-evans/create-pull-request from 6 to 7 (#5365)
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6 to 7.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v6...v7)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-07 19:00:51 -04:00
ccurmeandGitHub 8118e90543 docs: update sql tutorial (#5389) 2025-07-07 18:02:02 -04:00
hari-dhanushkodiandGitHub c13c474626 docs: update lgp deployment metric list (#5388) 2025-07-07 17:44:57 -04:00
Lauren Hirata SinghandGitHub cd58fad69d docs: feedback edits (#5387) 2025-07-07 17:03:05 -04:00
jitoandGitHub f3a7925d86 Fix typo: funtion → function (#5370)
fix typos

Signed-off-by: jitokim <pigberger70@gmail.com>
2025-07-07 20:09:55 +00:00
Serhii PolishchukandGitHub de6c25689d dcos: Fix deprecation of TavilySearch (#5375)
Fix deprecation: The class `TavilySearchResults` was deprecated in LangChain 0.3.25 and will be removed in 1.0
2025-07-07 16:00:57 -04:00
Michael LiandGitHub 141afa8c62 fix: remove unused import in generate_llms_text.py (#5380) 2025-07-07 15:54:02 -04:00
jitoandGitHub e99f6292c5 Fix typo in StreamMode debug description: checlkpoints → checkpoints (#5371)
Signed-off-by: jitokim <pigberger70@gmail.com>
2025-07-07 19:27:09 +00:00
jitoandGitHub 1800df7048 Fix docstring for _unset_config_context function (#5374)
Signed-off-by: jitokim <pigberger70@gmail.com>
2025-07-07 19:25:06 +00:00
Eugene YurtsevandGitHub 7f57e00975 docs: move script into scripts (#5384) 2025-07-07 15:10:01 -04:00
Michael LiandGitHub 844417591d fix: remove empty notebook (#5379) 2025-07-07 19:09:00 +00:00
Andrew NguonlyandGitHub c9966c4feb docs: Add section about data region for LGP data plane (#5378)
Add section about data region.
2025-07-07 12:39:44 -04:00
lc-arjunandGitHub 4abf948462 docs: add -tunnel flag for node server (#5373)
add -tunnel flag for node server
2025-07-07 08:05:46 -07:00
jessicaouandGitHub 269590c4d9 docs: update case studies (#5353) 2025-07-06 20:37:54 -04:00
Shivang AgarwalandGitHub d8756f257e Updated TAVILY Key Setup (#5346) 2025-07-07 00:36:28 +00:00
Chris GandGitHub cac0cd5522 docs: update name of CompiledGraph CompiledStateGraph (#5348)
The name (and type) has changed. See:
https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph.compile
2025-07-07 00:35:37 +00:00
David DuongandGitHub c91208429e fix(docs): invalid command for creating langgraph app (#5349) 2025-07-04 16:30:06 +02:00
Tat Dat Duong 690b6f4ea1 fix(docs): invalid command for creating langgraph app
Closes https://github.com/langchain-ai/langgraphjs/issues/1331
2025-07-04 16:29:38 +02:00
Andrew NguonlyandGitHub 07cd4d83e1 docs: Add note about preemptive compute infra (#5339)
* Add note about preemtive compute infra.

* Fix spelling error.
2025-07-03 16:04:27 -07:00
David DuongandGitHub fcdfc1d5e4 chore: move sdk-js to langgraphjs (#5334) 2025-07-03 16:58:31 +02:00
Tat Dat Duong dc95d1af88 Add a README.md 2025-07-03 16:52:35 +02:00
Tat Dat Duong 042e8ef315 chore: remove sdk-js
`sdk-js` has been moved here: https://github.com/langchain-ai/langgraphjs/tree/main/libs/sdk
2025-07-03 16:50:51 +02:00
Eugene YurtsevandGitHub 37d1ac1dce ci: one more workflow without explicit permissions (#5326) 2025-07-02 22:26:32 -04:00
Josh RogersandGitHub ecfabdf73a Adding disable_webhook to cli docs (#5320)
* Adding disable_webhook to cli docs
* Adding disable_webhook config
2025-07-02 22:14:01 -04:00
Lauren Hirata SinghandGitHub 543e4c4e7e docs: Convert notebooks (#5322)
* docs: Convert subgraphs notebook

* graph api conversion

* fix examples

* add

* fixes

* fix links

* fix links

* be gone!

* multi-agent conversion

* fix link

* fix links

* fix link
2025-07-02 23:28:42 +00:00
22e09d2739 Create user_agent_auth.md (#5299)
* Create user_agent_auth.md

Adding documentation for agent authentication on behalf of a user

* Update user_agent_auth.md

* Rename user_agent_auth.md to user-agent-auth.md

* break content out to separate guides

* add links/overview

* edits

* fix sentence

* Fix broken links

* Fix: Change 'get_user_config' fn name to 'my_node'

* Fix: Add reference to custom auth in MCP docs example

---------

Co-authored-by: Lauren Hirata Singh <lauren@langchain.dev>
2025-07-02 22:05:07 +00:00
Andrew NguonlyandGitHub 89451f4ea2 docs: Add LGP control plane API docs page (#5319)
Add control plane API docs page.
2025-07-02 14:30:00 -07:00
Sydney RunkleandGitHub 813a1d6d0c langgraph: release v0.5.1 (#5323)
* bump
* lock
2025-07-02 21:06:05 +00:00
Eugene YurtsevandGitHub e28af0ffc3 ci: set explicit workflow permissions to read (should be a no-op) (#5318)
* We're using restricted GITHUB_TOKENS by default.
* This is expected to be a no-op operation for codeql.
2025-07-02 17:02:17 -04:00
Sydney RunkleandGitHub 339de4c204 langgraph[fix]: remove deprecated pydantic logic + fix schema gen behavior for typed dicts (#5296) 2025-07-02 20:08:48 +00:00
lc-arjunandGitHub 0885e7833b docs: cli data storage handling (#5188)
Write up initial docs on how data is managed in the langgraph server, what telemetry is collected (and why), and how to opt-out.
2025-07-02 12:19:46 -07:00
Josh RogersandGitHub 669cf817e8 Bump js sdk to 0.0.89 (#5313) 2025-07-02 09:56:04 -07:00
Sydney RunkleandGitHub 000f5c3043 fix[deps]: update lockfiles / deps bounds for internal tools (#5301)
update lockfiles / deps bounds
2025-07-02 10:30:55 -04:00
Sydney RunkleandGitHub b3708bd7f6 ci: add automated uv lock --upgrade workflow (#5307) 2025-07-02 10:10:01 -04:00
Sydney RunkleandGitHub 8271e39e00 dependabot: no kafka (#5306)
* fix list of dirs
* another patch
2025-07-02 13:15:00 +00:00
Sydney RunkleandGitHub 60560ea755 dependabot: fix list of dirs for pip updates (#5305)
fix list of dirs
2025-07-02 13:12:22 +00:00
waqarahmed6095andGitHub e2acfb24cc Update use_stream_react.md (#5304)
Problem of two times heading 
"How to integrate LangGraph into your React application"
2025-07-02 13:10:57 +00:00
Sydney RunkleandGitHub 191192b142 upgrade dependabot scope (#5303) 2025-07-02 09:09:11 -04:00
Josh RogersandGitHub df368bdd30 Updating message types to include all base message fields (#5298) 2025-07-01 15:40:13 -07:00
Josh RogersandGitHub 4ec897033f Update LGP api reference docs (#5297) 2025-07-01 11:45:30 -07:00
c16e42e6d5 fix broken link (#5291)
* fix broken link

* Apply suggestions from code review

Fix link

Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com>

---------

Co-authored-by: Lauren Hirata Singh <lauren@langchain.dev>
Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com>
2025-07-01 13:52:26 +00:00
David DuongandGitHub 376469ea90 release(sdk-js): 0.0.88 (#5294) 2025-07-01 14:30:37 +02:00
Tat Dat Duong 7e2af0ce8d release(sdk-js): 0.0.88 2025-07-01 14:21:11 +02:00
Youssef Ahmed Mohamed AbdelrahmanandGitHub 1b205a99cb docs: fix typo in application_structure (#5289) 2025-07-01 12:09:14 +00:00
Sam CrowderandGitHub 0a8ba20f5f docs: remove beta flag on self hosted plane (#5288) 2025-06-30 22:41:05 -04:00
Sam CrowderandGitHub 048cb3584c self hosted control plane no longer in beta (#5286)
* self hosted control plane no longer in beta

* accidental changes
2025-06-30 17:34:18 -07:00
Sam CrowderandGitHub 22c35b7bc8 switch order of MCP methods in API spec (#5287) 2025-06-30 17:33:59 -07:00
David DuongandGitHub f3ed32e611 feat(react): enhance useStream with initialValues, newThreadId, and onStop callback for improved UX (#5111) 2025-07-01 01:53:28 +02:00
Tat Dat Duong 276675b618 Make sure to spread stream values 2025-07-01 01:39:53 +02:00
Tat Dat Duong 882de42996 Fix non-existent assistantId 2025-07-01 01:35:59 +02:00
Tat Dat Duong 70be50f37b Fix typo 2025-07-01 01:33:08 +02:00
Tat Dat Duong 1c7234e9c5 Update README.md 2025-07-01 01:32:27 +02:00
Tat Dat Duong 3d88f75254 Cleanup 2025-07-01 01:19:07 +02:00
Lauren Hirata SinghandGitHub 407abbe9ff Add forum links (#5282) 2025-06-30 16:11:46 -04:00
ccurmeandGitHub 6182cd1dcb prebuilt: release 0.5.2 (#5280) 2025-06-30 15:50:21 -04:00
Lauren Hirata SinghandGitHub 1d276dd753 docs: cronjob nav (#5281) 2025-06-30 15:34:29 -04:00
ccurmeandGitHub a48d8cb69b prebuilt[patch]: import recognized tool message content block types from langchain-core (#5275) 2025-06-30 15:22:19 -04:00
Lauren Hirata SinghandGitHub a05a251caf docs: Fix nav (#5279) 2025-06-30 15:18:43 -04:00
MauritsBrinkmanandTat Dat Duong c7bbb26ac0 test: add useStream onStop callback tests 2025-06-30 16:43:48 +02:00
MauritsBrinkmanandTat Dat Duong ac9b6c416e feat: add onStop callback to useStream for custom stop behavior
Add onStop callback to useStream hook enabling developers to customize
UI behavior when streams are stopped. This is especially useful for
UI messages with loading states that need to show "stopped" status
instead of remaining in infinite loading state.

The callback provides the same mutate function as onCustomEvent for
immediate local state updates, while users can optionally update
server thread state using the threads client.

Example usage:
```typescript
const stream = useStream({
  assistantId: "my-assistant",
  onStop: async ({ mutate }) => {
    // Immediate UI update - stop loading components
    mutate((prev) => ({
      ...prev,
      ui: prev.ui?.map(component =>
        component.props?.isLoading
          ? {
              ...component,
              props: {
                ...component.props,
                isLoading: false,
                isStopped: true
              }
            }
          : component
      )
    }));

    // Optional server thread state update
    if (stream.threadId) {
      await stream.client.threads.updateState(stream.threadId, {
        values: {
          ui: prev.ui // persist stopped state to server
        }
      });
    }
  }
});
```

This is especially useful for cases where gen UI components have loading states,
where we don't want the loading state to persist on cancellation.
2025-06-30 16:43:13 +02:00
MauritsBrinkmanandTat Dat Duong d4b4eebe4a fix(sdk-js): convert SSE classes to factory functions to resolve tree shaking
- Convert BytesLineDecoder and SSEDecoder from classes extending TransformStream to factory functions
- Fixes tree shaking failures that prevented build completion
- Maintains identical API functionality, just removes 'new' keyword usage
- All tests continue to pass

Resolves tree shaking side effect detection issues with TransformStream extension
2025-06-30 16:43:13 +02:00
MauritsBrinkmanandTat Dat Duong f8e1e803e1 docs(react): add documentation and tests for initialValues and newThreadId options
- Document initialValues for cached thread display
- Document newThreadId for optimistic thread creation
- Add comprehensive test coverage for both features
2025-06-30 16:43:12 +02:00
MauritsBrinkmanandTat Dat Duong 141a6af4f7 feat(react): add initialValues option to useStream for cached thread display
Add initialValues parameter to UseStreamOptions to enable immediate display
of cached thread data while official history is being fetched from the server.

This addresses the common use case where applications cache thread data
locally (IndexedDB, localStorage, etc.) and want to show it instantly when
users navigate to existing threads, providing better UX with faster loading.

Key changes:
- Add initialValues?: Partial<StateType> | null to UseStreamOptions interface
- Update values precedence: streamValues > initialValues > historyValues
- Ensure optimisticValues properly override initialValues during submission
- Maintain full backward compatibility with existing API

Example usage:
```typescript
const stream = useStream({
  threadId,
  assistantId: 'my-assistant',
  initialValues: cachedThreadData?.values // Show cached data immediately
});
```

The values flow now follows this priority:
1. Initial load: shows initialValues while history loads
2. During submit: optimisticValues take precedence
3. After server response: official history replaces all
2025-06-30 16:43:12 +02:00
MauritsBrinkmanandTat Dat Duong 8a763ad358 feat(react): add newThreadId option to useStream for optimistic UI
Add optional newThreadId parameter to useStream hook that allows specifying
a thread ID for new thread creation while keeping threadId null. This enables
optimistic UI patterns where developers need to know the thread ID beforehand
for routing/navigation without causing 404 errors from attempting to fetch
non-existent thread history.

Usage:
- Set threadId: null and newThreadId: "predetermined-id"
- Submit message to create thread with specified ID
- Use onThreadId callback to update threadId after creation

This solves the UX problem of having to await thread creation before
enabling optimistic navigation to e.g. /[threadId] routes.
2025-06-30 16:43:12 +02:00
David DuongandGitHub 16d02e63a1 fix: Allow configuring stream mode in useStream.joinStream() (#5146) 2025-06-30 16:37:56 +02:00
David DuongandGitHub 03421c2b04 chore(sdk-js): use embed LGP server for MSW mocking (#5174) 2025-06-30 16:33:47 +02:00
Tat Dat Duong 2508aa45ea Use published package 2025-06-30 13:51:32 +02:00
Sam CrowderandGitHub 9e035264f8 slightly more explanation when we say dont use in serverless (#5245)
slightly more explanation
2025-06-29 21:59:48 -04:00
Tat Dat Duong 9a0cee5cd0 chore(sdk-js): use embed LGP server for MSW mocking 2025-06-24 02:14:19 +02:00
bracesproul 78bef6bc0c formatting 2025-06-19 11:20:17 -07:00
bracesproul 7bd364c457 fix: Allow configuring stream mode in useStream.joinStream() 2025-06-19 11:14:59 -07:00
198 changed files with 13351 additions and 22403 deletions
+3 -10
View File
@@ -1,15 +1,8 @@
blank_issues_enabled: true
version: 2.1
contact_links:
- name: 🤔 Question or Problem
about: Ask a question or ask about a problem in GitHub Discussions.
url: https://github.com/langchain-ai/langgraph/discussions/categories/q-a
- name: Feature Request
url: https://github.com/langchain-ai/langgraph/discussions/categories/ideas
about: Suggest a feature or an idea
- name: Show and tell
about: Show what you built with LangChain
url: https://github.com/langchain-ai/langgraph/discussions/categories/show-and-tell
- name: Slack
url: https://www.langchain.com/join-community
about: General community discussions
- name: LangChain Forum
url: https://forum.langchain.com/
about: General community discussions and support
+12 -5
View File
@@ -1,11 +1,18 @@
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# and
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "pip"
directories:
- "libs/checkpoint"
- "libs/checkpoint-postgres"
- "libs/checkpoint-sqlite"
- "libs/cli"
- "libs/langgraph"
- "libs/prebuilt"
- "libs/sdk-py"
schedule:
interval: "weekly"
+3
View File
@@ -3,6 +3,9 @@ name: CLI integration test
on:
workflow_call:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
+3
View File
@@ -8,6 +8,9 @@ on:
type: string
description: "From which folder this pipeline executes"
permissions:
contents: read
env:
# This env var allows us to get inline annotations when ruff has complaints.
RUFF_OUTPUT_FORMAT: github
+3
View File
@@ -8,6 +8,9 @@ on:
type: string
description: "From which folder this pipeline executes"
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
+3
View File
@@ -3,6 +3,9 @@ name: test
on:
workflow_call:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
+3
View File
@@ -11,6 +11,9 @@ on:
env:
PYTHON_VERSION: "3.10"
permissions:
contents: read
jobs:
build:
if: github.ref == 'refs/heads/main'
+3
View File
@@ -7,6 +7,9 @@ on:
paths:
- "libs/**"
permissions:
contents: read
jobs:
benchmark:
runs-on: ubuntu-latest
+3
View File
@@ -5,6 +5,9 @@ on:
paths:
- "libs/**"
permissions:
contents: read
jobs:
benchmark:
runs-on: ubuntu-latest
+11 -59
View File
@@ -6,6 +6,9 @@ on:
branches: [main]
pull_request:
permissions:
contents: read
# If another push to the same PR or branch happens while this workflow is still running,
# cancel the earlier run in favor of the next run.
#
@@ -21,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
python: ${{ steps.filter.outputs.python }}
sdk-js: ${{ steps.filter.outputs.sdk-js }}
deps: ${{ steps.filter.outputs.deps }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
@@ -36,8 +39,9 @@ jobs:
- 'libs/checkpoint-sqlite/**'
- 'libs/checkpoint-postgres/**'
- 'libs/prebuilt/**'
sdk-js:
- 'libs/sdk-js/**'
deps:
- '**/pyproject.toml'
- '**/uv.lock'
lint:
needs: changes
@@ -55,7 +59,7 @@ jobs:
"libs/prebuilt",
]
if: needs.changes.outputs.python == 'true'
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
uses: ./.github/workflows/_lint.yml
with:
working-directory: ${{ matrix.working-directory }}
@@ -74,7 +78,7 @@ jobs:
"libs/checkpoint-postgres",
"libs/prebuilt",
]
if: needs.changes.outputs.python == 'true'
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
uses: ./.github/workflows/_test.yml
with:
working-directory: ${{ matrix.working-directory }}
@@ -83,7 +87,7 @@ jobs:
# NOTE: we're testing langgraph separately because it requires a different matrix
test-langgraph:
needs: changes
if: needs.changes.outputs.python == 'true'
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
name: "cd libs/langgraph"
uses: ./.github/workflows/_test_langgraph.yml
secrets: inherit
@@ -140,73 +144,21 @@ jobs:
integration-test:
needs: changes
if: needs.changes.outputs.python == 'true'
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
name: CLI integration test
uses: ./.github/workflows/_integration_test.yml
secrets: inherit
lint-js:
needs: changes
if: needs.changes.outputs.sdk-js == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
working-directory:
- "libs/sdk-js"
defaults:
run:
working-directory: ${{ matrix.working-directory }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js (LTS)
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "yarn"
cache-dependency-path: ${{ matrix.working-directory }}/yarn.lock
- name: Install dependencies
run: yarn install
- name: Run lint
run: yarn lint
- name: Build
run: yarn build
test-js:
needs: changes
if: needs.changes.outputs.sdk-js == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
working-directory:
- "libs/sdk-js"
defaults:
run:
working-directory: ${{ matrix.working-directory }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js (LTS)
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "yarn"
cache-dependency-path: ${{ matrix.working-directory }}/yarn.lock
- name: Install dependencies
run: yarn install
- name: Run tests
run: yarn test
ci_success:
name: "CI Success"
needs:
[
lint,
lint-js,
test,
test-langgraph,
check-sdk-methods,
check-schema,
integration-test,
test-js,
]
if: |
always()
+3
View File
@@ -11,6 +11,9 @@ on:
- cron: "0 5 * * *"
workflow_dispatch:
permissions:
contents: read
jobs:
markdown-link-check:
runs-on: ubuntu-latest
+44
View File
@@ -0,0 +1,44 @@
name: PR Title Lint
permissions:
pull-requests: read
on:
pull_request:
types: [opened, edited, synchronize]
jobs:
lint-pr-title:
runs-on: ubuntu-latest
steps:
- name: Validate PR Title
uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
docs
style
refactor
perf
test
build
ci
chore
revert
release
scopes: |
checkpoint
checkpoint-postgres
checkpoint-sqlite
cli
langgraph
prebuilt
scheduler-kafka
sdk-py
docs
requireScope: false
ignoreLabels: |
ignore-lint-pr-title
+3
View File
@@ -8,6 +8,9 @@ on:
type: string
default: "libs/langgraph"
permissions:
contents: read
env:
PYTHON_VERSION: "3.11"
-38
View File
@@ -1,38 +0,0 @@
name: JS Release
on:
workflow_dispatch:
jobs:
publish:
# Disallow publishing from branches that aren't `main`.
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
strategy:
matrix:
working-directory:
- "libs/sdk-js"
defaults:
run:
working-directory: ${{ matrix.working-directory }}
steps:
- uses: actions/checkout@v4
# JS Build
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "yarn"
cache-dependency-path: ${{ matrix.working-directory }}/yarn.lock
- name: Install dependencies
run: yarn install
- name: Build
run: yarn build
- name: Publish package to NPM
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc
npm publish
+3
View File
@@ -11,6 +11,9 @@ on:
schedule:
- cron: "0 13 * * *"
permissions:
contents: read
defaults:
run:
working-directory: docs
+45
View File
@@ -0,0 +1,45 @@
name: UV Lock Upgrade
on:
schedule:
# run at midnight every Sunday
- cron: '0 0 * * 0'
# allow manual triggering
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
upgrade-dependencies:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
# use minimum supported Python version
python-version: "3.9"
enable-cache: true
cache-suffix: "uv-lock-upgrade"
- name: Run uv lock --upgrade in all Python packages
run: make lock-upgrade
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore[deps]: upgrade dependencies with `uv lock --upgrade`"
title: "chore[deps]: upgrade dependencies with `uv lock --upgrade`"
body: |
This PR updates the dependencies in all Python packages using `uv lock --upgrade`.
This is an automated PR created by the UV Lock Upgrade workflow.
branch: deps/uv-lock-upgrade
delete-branch: true
labels: |
dependencies
+1 -1
View File
@@ -60,7 +60,7 @@ In LangGraph, these are often higher level guides that show off end-to-end use c
Some examples include:
- [Build a Customer Support Bot](https://langchain-ai.github.io/langgraph/tutorials/customer-support/customer-support/)
- [Build a SQL Agent](https://langchain-ai.github.io/langgraph/tutorials/sql-agent/)
- [Build a SQL Agent](https://langchain-ai.github.io/langgraph/tutorials/sql/sql-agent/)
Here are some high-level tips on writing a good tutorial:
+10
View File
@@ -47,6 +47,16 @@ lock:
fi; \
done
# Lock all projects and upgrade dependencies
.PHONY: lock-upgrade
lock-upgrade:
@for dir in $(LIBS_DIRS); do \
if [ -f $$dir/Makefile ]; then \
echo "Running lock-upgrade in $$dir"; \
(cd $$dir && uv lock --upgrade); \
fi; \
done
# Test all projects
.PHONY: test
test:
+1 -1
View File
@@ -63,7 +63,7 @@ LangGraph provides low-level supporting infrastructure for *any* long-running, s
While LangGraph can be used standalone, it also integrates seamlessly with any LangChain product, giving developers a full suite of tools for building agents. To improve your LLM application development, pair LangGraph with:
- [LangSmith](http://www.langchain.com/langsmith) — Helpful for agent evals and observability. Debug poor-performing LLM app runs, evaluate agent trajectories, gain visibility in production, and improve performance over time.
- [LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/#langgraph-platform) — Deploy and scale agents effortlessly with a purpose-built deployment platform for long running, stateful workflows. Discover, reuse, configure, and share agents across teams — and iterate quickly with visual prototyping in [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/).
- [LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) — Deploy and scale agents effortlessly with a purpose-built deployment platform for long running, stateful workflows. Discover, reuse, configure, and share agents across teams — and iterate quickly with visual prototyping in [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/).
- [LangChain](https://python.langchain.com/docs/introduction/) Provides integrations and composable components to streamline LLM application development.
> [!NOTE]
-1
View File
@@ -1,4 +1,3 @@
site/
docs/cloud/reference/sdk/js_ts_sdk_ref.md
.vercel
+3 -9
View File
@@ -1,10 +1,4 @@
.PHONY: lint-docs format-docs build-docs serve-docs serve-clean-docs clean-docs codespell build-typedoc llms-text build-prebuilt tests
build-typedoc:
cd ../libs/sdk-js && yarn install --include-dev && yarn typedoc
cd ../libs/sdk-js && yarn --silent concat-md --decrease-title-levels --ignore=js_ts_sdk_ref.md --start-title-level-at 2 docs > ../../docs/docs/cloud/reference/sdk/js_ts_sdk_ref.md 2>/dev/null
# Add links to the monorepo
sed -e '1,10s|@langchain/langgraph-sdk|[@langchain/langgraph-sdk](https://github.com/langchain-ai/langgraph/tree/main/libs/sdk-js)|g' docs/cloud/reference/sdk/js_ts_sdk_ref.md > temp_file && mv temp_file docs/cloud/reference/sdk/js_ts_sdk_ref.md
.PHONY: lint-docs format-docs build-docs serve-docs serve-clean-docs clean-docs codespell llms-text build-prebuilt tests
build-prebuilt:
# Use to create an update to date prebuilt page.
@@ -21,7 +15,7 @@ build-prebuilt:
fi
uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md --language python
build-docs: build-typedoc build-prebuilt
build-docs: build-prebuilt
uv run python -m mkdocs build --clean -f mkdocs.yml --strict
llms-text:
@@ -45,7 +39,7 @@ vercel-build-docs: install-vercel-deps
serve-clean-docs: clean-docs
uv run python -m mkdocs serve -c -f mkdocs.yml --strict -w ../libs/langgraph
serve-docs: build-typedoc
serve-docs:
uv run python -m mkdocs serve -f mkdocs.yml -w ../libs/langgraph -w ../libs/checkpoint -w ../libs/sdk-py --dirty
clean-docs:
+1 -4
View File
@@ -14,10 +14,7 @@ from mkdocs.structure.pages import Page
from pydantic import BaseModel, Field
from yaml import SafeLoader
from _scripts.notebook_hooks import (
_on_page_markdown_with_config,
_apply_conditional_rendering,
)
from _scripts.notebook_hooks import _on_page_markdown_with_config
HERE = os.path.dirname(os.path.abspath(__file__))
# Get source directory (parent of HERE / docs)
+20 -19
View File
@@ -34,20 +34,20 @@ REDIRECT_MAP = {
"how-tos/streaming-from-final-node.ipynb": "how-tos/streaming-specific-nodes.ipynb",
"how-tos/streaming-events-from-within-tools-without-langchain.ipynb": "how-tos/streaming-events-from-within-tools.ipynb#example-without-langchain",
# graph-api
"how-tos/state-reducers.ipynb": "how-tos/graph-api#define-and-update-state",
"how-tos/sequence.ipynb": "how-tos/graph-api#create-a-sequence-of-steps",
"how-tos/branching.ipynb": "how-tos/graph-api#create-branches",
"how-tos/recursion-limit.ipynb": "how-tos/graph-api#create-and-control-loops",
"how-tos/visualization.ipynb": "how-tos/graph-api#visualize-your-graph",
"how-tos/input_output_schema.ipynb": "how-tos/graph-api#define-input-and-output-schemas",
"how-tos/pass_private_state.ipynb": "how-tos/graph-api#pass-private-state-between-nodes",
"how-tos/state-model.ipynb": "how-tos/graph-api#use-pydantic-models-for-graph-state",
"how-tos/map-reduce.ipynb": "how-tos/graph-api/#map-reduce-and-the-send-api",
"how-tos/command.ipynb": "how-tos/graph-api/#combine-control-flow-and-state-updates-with-command",
"how-tos/configuration.ipynb": "how-tos/graph-api/#add-runtime-configuration",
"how-tos/node-retries.ipynb": "how-tos/graph-api/#add-retry-policies",
"how-tos/return-when-recursion-limit-hits.ipynb": "how-tos/graph-api/#impose-a-recursion-limit",
"how-tos/async.ipynb": "how-tos/graph-api/#async",
"how-tos/state-reducers.ipynb": "how-tos/graph-api.md#define-and-update-state",
"how-tos/sequence.ipynb": "how-tos/graph-api.md#create-a-sequence-of-steps",
"how-tos/branching.ipynb": "how-tos/graph-api.md#create-branches",
"how-tos/recursion-limit.ipynb": "how-tos/graph-api.md#create-and-control-loops",
"how-tos/visualization.ipynb": "how-tos/graph-api.md#visualize-your-graph",
"how-tos/input_output_schema.ipynb": "how-tos/graph-api.md#define-input-and-output-schemas",
"how-tos/pass_private_state.ipynb": "how-tos/graph-api.md#pass-private-state-between-nodes",
"how-tos/state-model.ipynb": "how-tos/graph-api.md#use-pydantic-models-for-graph-state",
"how-tos/map-reduce.ipynb": "how-tos/graph-api.md#map-reduce-and-the-send-api",
"how-tos/command.ipynb": "how-tos/graph-api.md#combine-control-flow-and-state-updates-with-command",
"how-tos/configuration.ipynb": "how-tos/graph-api.md#add-runtime-configuration",
"how-tos/node-retries.ipynb": "how-tos/graph-api.md#add-retry-policies",
"how-tos/return-when-recursion-limit-hits.ipynb": "how-tos/graph-api.md#impose-a-recursion-limit",
"how-tos/async.ipynb": "how-tos/graph-api.md#async",
# memory how-tos
"how-tos/memory/manage-conversation-history.ipynb": "how-tos/memory/add-memory.md",
"how-tos/memory/delete-messages.ipynb": "how-tos/memory/add-memory.md#delete-messages",
@@ -55,8 +55,8 @@ REDIRECT_MAP = {
"how-tos/memory.ipynb": "how-tos/memory/add-memory.md",
"agents/memory.ipynb": "how-tos/memory/add-memory.md",
# subgraph how-tos
"how-tos/subgraph-transform-state.ipynb": "how-tos/subgraph.ipynb#different-state-schemas",
"how-tos/subgraphs-manage-state.ipynb": "how-tos/subgraph.ipynb#add-persistence",
"how-tos/subgraph-transform-state.ipynb": "how-tos/subgraph.md#different-state-schemas",
"how-tos/subgraphs-manage-state.ipynb": "how-tos/subgraph.md#add-persistence",
# persistence how-tos
"how-tos/persistence_postgres.ipynb": "how-tos/memory/add-memory.md#use-in-production",
"how-tos/persistence_mongodb.ipynb": "how-tos/memory/add-memory.md#use-in-production",
@@ -72,10 +72,11 @@ REDIRECT_MAP = {
"how-tos/pass-config-to-tools.ipynb": "how-tos/tool-calling.ipynb#access-config",
"how-tos/pass-run-time-values-to-tools.ipynb": "how-tos/tool-calling.ipynb#read-state",
"how-tos/update-state-from-tools.ipynb": "how-tos/tool-calling.ipynb#update-state",
"agents/tools.md": "how-tos/tool-calling.md",
# multi-agent how-tos
"how-tos/agent-handoffs.ipynb": "how-tos/multi_agent.ipynb#handoffs",
"how-tos/multi-agent-network.ipynb": "how-tos/multi_agent.ipynb#use-in-a-multi-agent-system",
"how-tos/multi-agent-multi-turn-convo.ipynb": "how-tos/multi_agent.ipynb#multi-turn-conversation",
"how-tos/agent-handoffs.ipynb": "how-tos/multi_agent.md#handoffs",
"how-tos/multi-agent-network.ipynb": "how-tos/multi_agent.md#use-in-a-multi-agent-system",
"how-tos/multi-agent-multi-turn-convo.ipynb": "how-tos/multi_agent.md#multi-turn-conversation",
# cloud redirects
"cloud/index.md": "index.md",
"cloud/how-tos/index.md": "concepts/langgraph_platform",
+11
View File
@@ -0,0 +1,11 @@
# Additional resources
This section contains additional resources for LangGraph.
- [Community agents](../agents/prebuilt.md): A collection of prebuilt libraries that you can use in your LangGraph applications.
- [LangGraph Academy](https://academy.langchain.com/courses/intro-to-langgraph): A collection of courses that teach you how to use LangGraph.
- [Case studies](../adopters.md): A collection of case studies that show how LangGraph is used in production.
- [FAQ](../concepts/faq.md): A collection of frequently asked questions about LangGraph.
- [llms.txt](../llms-txt-overview.md): A list of documentation files in the `llms.txt` format that allow LLMs and agents to access our documentation.
- [LangChain Forum](https://forum.langchain.com/): A place to ask questions and get help from other LangGraph users.
- [Troubleshooting](../troubleshooting/errors/index.md): A collection of troubleshooting guides for common issues.
+23 -6
View File
@@ -8,24 +8,41 @@ This list of companies using LangGraph and their success stories is compiled fro
| [AirTop](https://www.airtop.ai/) | Software & Technology (GenAI Native) | Browser automation for AI agents | [Case study, 2024](https://blog.langchain.dev/customers-airtop/) |
| [AppFolio](https://www.appfolio.com/) | Real Estate | Copilot for domain-specific task | [Case study, 2024](https://blog.langchain.dev/customers-appfolio/) |
| [Athena Intelligence](https://www.athenaintel.com/) | Software & Technology (GenAI Native) | Research & summarization | [Case study, 2024](https://blog.langchain.dev/customers-athena-intelligence/) |
| [BlackRock](https://www.blackrock.com/) | Financial Services | Copilot for domain-specific task | [Interrupt talk, 2025](https://youtu.be/oyqeCHFM5U4?feature=shared) |
| [Captide](https://www.captide.co/) | Software & Technology (GenAI Native) | Data extraction | [Case study, 2025](https://blog.langchain.dev/how-captide-is-redefining-equity-research-with-agentic-workflows-built-on-langgraph-and-langsmith/) |
| [Cisco Outshift](https://outshift.cisco.com/) | Software & Technology | DevOps | [Blog post, 2025](https://outshift.cisco.com/blog/build-react-agent-application-for-devops-tasks-using-rest-apis) |
| [Cisco CX](https://www.cisco.com/site/us/en/services/modern-data-center/index.html?CCID=cc005911&DTID=eivtotr001480&OID=srwsas032775) | Software & Technology | Customer support | [Interrupt Talk, 2025](https://youtu.be/gPhyPRtIMn0?feature=shared) |
| [Cisco Outshift](https://outshift.cisco.com/) | Software & Technology | DevOps | [Video story, 2025](https://www.youtube.com/watch?v=htcb-vGR_x0); [Case study, 2025](https://blog.langchain.com/cisco-outshift/); [Blog post, 2025](https://outshift.cisco.com/blog/build-react-agent-application-for-devops-tasks-using-rest-apis) |
| [Cisco TAC](https://www.cisco.com/c/en/us/support/index.html) | Software & Technology | Customer support | [Video story, 2025](https://youtu.be/EAj0HBDGqaE?feature=shared) |
| [City of Hope](https://www.cityofhope.org/) | Non-profit | Copilot for domain-specific task | [Video story, 2025](https://youtu.be/9ABwtK2gIZU?feature=shared) |
| [C.H. Robinson](https://www.chrobinson.com/en-us/) | Logistics | Automation | [Case study, 2025](https://blog.langchain.dev/customers-chrobinson/) |
| [Definely](https://www.definely.com/) | Legal | Copilot for domain-specific task | [Case study, 2025](https://blog.langchain.com/customers-definely/) |
| [Docent Pro](https://docentpro.com/) | Travel | GenAI embedded product experiences | [Case study, 2025](https://blog.langchain.com/customers-docentpro/) |
| [Elastic](https://www.elastic.co/) | Software & Technology | Copilot for domain-specific task | [Blog post, 2025](https://www.elastic.co/blog/elastic-security-generative-ai-features) |
| [Exa](https://exa.ai/) | Software & Technology (GenAI Native) | Search | [Case study, 2025](https://blog.langchain.com/exa/) |
| [GitLab](https://about.gitlab.com/) | Software & Technology | Code generation | [Duo workflow docs](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/duo_workflow/) |
| [Harmonic](https://harmonic.ai/) | Software & Technology | Search | [Case study, 2025](https://blog.langchain.com/customers-harmonic/) |
| [Inconvo](https://inconvo.ai/?ref=blog.langchain.dev) | Software & Technology | Code generation | [Case study, 2025](https://blog.langchain.dev/customers-inconvo/) |
| [Infor](https://infor.com/) | Software & Technology | GenAI embedded product experiences; customer support; copilot | [Case study, 2025](https://blog.langchain.dev/customers-infor/) |
| [J.P. Morgan](https://www.jpmorganchase.com/) | Financial Services | Copilot for domain-specific task | [Interrupt talk, 2025](https://youtu.be/yMalr0jiOAc?feature=shared) |
| [Klarna](https://www.klarna.com/) | Fintech | Copilot for domain-specific task | [Case study, 2025](https://blog.langchain.dev/customers-klarna/) |
| [Komodo Health](https://www.komodohealth.com/) | Healthcare | Copilot for domain-specific task | [Blog post](https://www.komodohealth.com/perspectives/new-gen-ai-assistant-empowers-the-enterprise/) |
| [LinkedIn](https://www.linkedin.com/) | Social Media | Code generation; Search & discovery | [Blog post, 2025](https://www.linkedin.com/blog/engineering/ai/practical-text-to-sql-for-data-analytics); [Blog post, 2024](https://www.linkedin.com/blog/engineering/generative-ai/behind-the-platform-the-journey-to-create-the-linkedin-genai-application-tech-stack) |
| [LinkedIn](https://www.linkedin.com/) | Social Media | Code generation; Search & discovery | [Interrupt talk, 2025](https://youtu.be/NmblVxyBhi8?feature=shared); [Blog post, 2025](https://www.linkedin.com/blog/engineering/ai/practical-text-to-sql-for-data-analytics); [Blog post, 2024](https://www.linkedin.com/blog/engineering/generative-ai/behind-the-platform-the-journey-to-create-the-linkedin-genai-application-tech-stack) |
| [Minimal](https://gominimal.ai/) | E-commerce | Customer support | [Case study, 2025](https://blog.langchain.dev/how-minimal-built-a-multi-agent-customer-support-system-with-langgraph-langsmith/) |
| [Modern Treasury](https://www.moderntreasury.com/) | Fintech | GenAI embedded product experiences | [Video story, 2025](https://youtu.be/AwAiffXqaCU?feature=shared) |
| [Monday](https://monday.com/) | Software & Technology | GenAI embedded product experiences | [Interrupt talk, 2025](https://blog.langchain.dev/how-minimal-built-a-multi-agent-customer-support-system-with-langgraph-langsmith/) |
| [Morningstar](https://www.morningstar.com/) | Financial Services | Research & summarization | [Video story, 2025](https://youtu.be/6LidoFXCJPs?feature=shared) |
| [OpenRecovery](https://www.openrecovery.com/) | Healthcare | Copilot for domain-specific task | [Case study, 2024](https://blog.langchain.dev/customers-openrecovery/) |
| [Pigment](https://www.pigment.com/) | Fintech | GenAI embedded product experiences | [Video story, 2025](https://youtu.be/5JVSO2KYOmE?feature=shared) |
| [Prosper](https://www.prosper.com/) | Fintech | Customer support | [Video story, 2025](https://youtu.be/9RFNOYtkwsc?feature=shared) |
| [Qodo](https://www.qodo.ai/) | Software & Technology (GenAI Native) | Code generation | [Blog post, 2025](https://www.qodo.ai/blog/why-we-chose-langgraph-to-build-our-coding-agent/) |
| [Rakuten](https://www.rakuten.com/) | E-commerce / Fintech | Copilot for domain-specific task | [Blog post, 2025](https://rakuten.today/blog/from-ai-hype-to-real-world-tools-rakuten-teams-up-with-langchain.html) |
| [Rakuten](https://www.rakuten.com/) | E-commerce / Fintech | Copilot for domain-specific task | [Video story, 2025](https://youtu.be/gD1LIjCkuA8?feature=shared); [Blog post, 2025](https://rakuten.today/blog/from-ai-hype-to-real-world-tools-rakuten-teams-up-with-langchain.html) |
| [Replit](https://replit.com/) | Software & Technology | Code generation | [Blog post, 2024](https://blog.langchain.dev/customers-replit/); [Breakout agent story, 2024](https://www.langchain.com/breakoutagents/replit); [Fireside chat video, 2024](https://www.youtube.com/watch?v=ViykMqljjxU) |
| [Rexera](https://www.rexera.com/) | Real Estate (GenAI Native) | Copilot for domain-specific task | [Case study, 2024](https://blog.langchain.dev/customers-rexera/) |
| [Abu Dhabi Government](https://www.tamm.abudhabi/) | Government | Search | [Case study, 2025](https://blog.langchain.com/customers-abu-dhabi-government/) |
| [Tradestack](https://www.tradestack.uk/) | Software & Technology (GenAI Native) | Copilot for domain-specific task | [Case study, 2024](https://blog.langchain.dev/customers-tradestack/) |
| [Uber](https://www.uber.com/) | Transportation | Developer productivity; Code generation | [Presentation, 2024](https://dpe.org/sessions/ty-smith-adam-huda/this-year-in-ubers-ai-driven-developer-productivity-revolution/); [Video, 2024](https://www.youtube.com/watch?v=8rkA5vWUE4Y) |
| [Unify](https://www.unifygtm.com/) | Software & Technology (GenAI Native) | Copilot for domain-specific task | [Blog post, 2024](https://blog.langchain.dev/unify-launches-agents-for-account-qualification-using-langgraph-and-langsmith/) |
| [Vizient](https://www.vizientinc.com/) | Healthcare | Copilot for domain-specific task | [Case study, 2025](https://blog.langchain.dev/p/3d2cd58c-13a5-4df9-bd84-7d54ed0ed82c/) |
| [Uber](https://www.uber.com/) | Transportation | Developer productivity; Code generation | [Interrupt talk, 2025](https://youtu.be/Bugs0dVcNI8?feature=shared); [Presentation, 2024](https://dpe.org/sessions/ty-smith-adam-huda/this-year-in-ubers-ai-driven-developer-productivity-revolution/); [Video, 2024](https://www.youtube.com/watch?v=8rkA5vWUE4Y) |
| [Unify](https://www.unifygtm.com/) | Software & Technology (GenAI Native) | Copilot for domain-specific task | [Interrupt talk, 2025](https://youtu.be/pKk-LfhujwI?feature=shared); [Blog post, 2024](https://blog.langchain.dev/unify-launches-agents-for-account-qualification-using-langgraph-and-langsmith/) |
| [Vizient](https://www.vizientinc.com/) | Healthcare | Copilot for domain-specific task | [Video story, 2025](https://www.youtube.com/watch?v=vrjJ6NuyTWA); [Case study, 2025](https://blog.langchain.dev/p/3d2cd58c-13a5-4df9-bd84-7d54ed0ed82c/) |
| [Vodafone](https://www.vodafone.com/) | Telecommunications | Code generation; internal search | [Case study, 2025](https://blog.langchain.dev/customers-vodafone/) |
| [WebToon](https://www.webtoons.com/en/) | Media & Entertainment | Data extraction | [Case study, 2025](https://blog.langchain.com/customers-webtoon/) |
| [11x](https://www.11x.ai/) | Software & Technology (GenAI Native) | Research & outreach | [Interrupt talk, 2025](https://youtu.be/fegwPmaAPQk?feature=shared) |
+1 -1
View File
@@ -52,7 +52,7 @@ agent.invoke(
)
```
1. Define a tool for the agent to use. Tools can be defined as vanilla Python functions. For more advanced tool usage and customization, check the [tools](./tools.md) page.
1. Define a tool for the agent to use. Tools can be defined as vanilla Python functions. For more advanced tool usage and customization, check the [tools](../how-tos/tool-calling.md) page.
2. Provide a language model for the agent to use. To learn more about configuring language models for the agents, check the [models](./models.md) page.
3. Provide a list of tools for the model to use.
4. Provide a system prompt (instructions) to the language model used by the agent.
+1 -10
View File
@@ -9,21 +9,12 @@ hide:
# Use MCP
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that standardizes how applications provide tools and context to language models. LangGraph agents can use tools defined on MCP servers through the `langchain-mcp-adapters` library.
![MCP](./assets/mcp.png)
Install the `langchain-mcp-adapters` library to use MCP tools in LangGraph:
```bash
pip install langchain-mcp-adapters
```
The Model Context Protocol (MCP) is an open protocol that standardizes how applications provide tools and context to language models. LangGraph agents can use tools defined on MCP servers through the `langchain-mcp-adapters` library.
## Use MCP tools
The `langchain-mcp-adapters` package enables agents to use tools defined across one or more MCP servers.
=== "In an agent"
```python title="Agent using tools defined on MCP servers"
+2 -2
View File
@@ -30,7 +30,7 @@ LangGraph includes several capabilities essential for building robust, productio
- [**Memory integration**](../how-tos/memory/add-memory.md): Native support for *short-term* (session-based) and *long-term* (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants.
- [**Human-in-the-loop control**](../concepts/human_in_the_loop.md): Execution can pause *indefinitely* to await human feedback—unlike websocket-based solutions limited to real-time interaction. This enables asynchronous approval, correction, or intervention at any point in the workflow.
- [**Streaming support**](../how-tos/streaming.md): Real-time streaming of agent state, model tokens, tool outputs, or combined streams.
- [**Deployment tooling**](./deployment.md): Includes infrastructure-free deployment tools. [**LangGraph Platform**](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) supports testing, debugging, and deployment.
- [**Deployment tooling**](../tutorials/langgraph-platform/local-server.md): Includes infrastructure-free deployment tools. [**LangGraph Platform**](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) supports testing, debugging, and deployment.
- **[Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/)**: A visual IDE for inspecting and debugging workflows.
- Supports multiple [**deployment options**](https://langchain-ai.github.io/langgraph/concepts/deployment_options.md) for production.
@@ -60,7 +60,7 @@ Use the following tool to visualize the graph generated by
and to view an outline of the corresponding code.
It allows you to explore the infrastructure of the agent as defined by the presence of:
* [`tools`](../agents/tools.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
* [`tools`](../how-tos/tool-calling.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
* [`pre_model_hook`](../how-tos/create-react-agent-manage-message-history.ipynb): A function that is called before the model is invoked. It can be used to condense messages or perform other preprocessing tasks.
* `post_model_hook`: A function that is called after the model is invoked. It can be used to implement guardrails, human-in-the-loop flows, or other postprocessing tasks.
* [`response_format`](../agents/agents.md#6-configure-structured-output): A data structure used to constrain the type of the final output, e.g., a `pydantic` `BaseModel`.
-310
View File
@@ -1,310 +0,0 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Tools
[Tools](https://python.langchain.com/docs/concepts/tools/) are a way to encapsulate a function and its input schema in a way that can be passed to a chat model that supports tool calling. This allows the model to request the execution of this function with specific inputs.
You can either [define your own tools](#define-simple-tools) or use [prebuilt integrations](#prebuilt-tools) that LangChain provides.
## Define simple tools
You can pass a vanilla function to `create_react_agent` to use as a tool:
```python
from langgraph.prebuilt import create_react_agent
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
create_react_agent(
model="anthropic:claude-3-7-sonnet",
tools=[multiply]
)
```
`create_react_agent` automatically converts vanilla functions to [LangChain tools](https://python.langchain.com/docs/concepts/tools/#tool-interface).
## Customize tools
For more control over tool behavior, use the `@tool` decorator:
```python
# highlight-next-line
from langchain_core.tools import tool
# highlight-next-line
@tool("multiply_tool", parse_docstring=True)
def multiply(a: int, b: int) -> int:
"""Multiply two numbers.
Args:
a: First operand
b: Second operand
"""
return a * b
```
You can also define a custom input schema using Pydantic:
```python
from pydantic import BaseModel, Field
class MultiplyInputSchema(BaseModel):
"""Multiply two numbers"""
a: int = Field(description="First operand")
b: int = Field(description="Second operand")
# highlight-next-line
@tool("multiply_tool", args_schema=MultiplyInputSchema)
def multiply(a: int, b: int) -> int:
return a * b
```
For additional customization, refer to the [custom tools guide](https://python.langchain.com/docs/how_to/custom_tools/).
## Hide arguments from the model
Some tools require runtime-only arguments (e.g., user ID or session context) that should not be controllable by the model.
You can put these arguments in the `state` or `config` of the agent, and access
this information inside the tool:
```python
from langgraph.prebuilt import InjectedState
from langgraph.prebuilt.chat_agent_executor import AgentState
from langchain_core.runnables import RunnableConfig
def my_tool(
# This will be populated by an LLM
tool_arg: str,
# access information that's dynamically updated inside the agent
# highlight-next-line
state: Annotated[AgentState, InjectedState],
# access static data that is passed at agent invocation
# highlight-next-line
config: RunnableConfig,
) -> str:
"""My tool."""
do_something_with_state(state["messages"])
do_something_with_config(config)
...
```
## Disable parallel tool calling
Some model providers support executing multiple tools in parallel, but
allow users to disable this feature.
For supported providers, you can disable parallel tool calling by setting `parallel_tool_calls=False` via the `model.bind_tools()` method:
```python
from langchain.chat_models import init_chat_model
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
model = init_chat_model("anthropic:claude-3-5-sonnet-latest", temperature=0)
tools = [add, multiply]
agent = create_react_agent(
# disable parallel tool calls
# highlight-next-line
model=model.bind_tools(tools, parallel_tool_calls=False),
tools=tools
)
agent.invoke(
{"messages": [{"role": "user", "content": "what's 3 + 5 and 4 * 7?"}]}
)
```
## Return tool results directly
Use `return_direct=True` to return tool results immediately and stop the agent loop:
```python
from langchain_core.tools import tool
# highlight-next-line
@tool(return_direct=True)
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[add]
)
agent.invoke(
{"messages": [{"role": "user", "content": "what's 3 + 5?"}]}
)
```
## Force tool use
To force the agent to use specific tools, you can set the `tool_choice` option in `model.bind_tools()`:
```python
from langchain_core.tools import tool
# highlight-next-line
@tool(return_direct=True)
def greet(user_name: str) -> int:
"""Greet user."""
return f"Hello {user_name}!"
tools = [greet]
agent = create_react_agent(
# highlight-next-line
model=model.bind_tools(tools, tool_choice={"type": "tool", "name": "greet"}),
tools=tools
)
agent.invoke(
{"messages": [{"role": "user", "content": "Hi, I am Bob"}]}
)
```
!!! Warning "Avoid infinite loops"
Forcing tool usage without stopping conditions can create infinite loops. Use one of the following safeguards:
- Mark the tool with [`return_direct=True`](#return-tool-results-directly) to end the loop after execution.
- Set [`recursion_limit`](../concepts/low_level.md#recursion-limit) to restrict the number of execution steps.
## Handle tool errors
By default, the agent will catch all exceptions raised during tool calls and will pass those as tool messages to the LLM. To control how the errors are handled, you can use the prebuilt [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] — the node that executes tools inside `create_react_agent` — via its `handle_tool_errors` parameter:
=== "Enable error handling (default)"
```python
from langgraph.prebuilt import create_react_agent
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
if a == 42:
raise ValueError("The ultimate error")
return a * b
# Run with error handling (default)
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[multiply]
)
agent.invoke(
{"messages": [{"role": "user", "content": "what's 42 x 7?"}]}
)
```
=== "Disable error handling"
```python
from langgraph.prebuilt import create_react_agent, ToolNode
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
if a == 42:
raise ValueError("The ultimate error")
return a * b
# highlight-next-line
tool_node = ToolNode(
[multiply],
# highlight-next-line
handle_tool_errors=False # (1)!
)
agent_no_error_handling = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=tool_node
)
agent_no_error_handling.invoke(
{"messages": [{"role": "user", "content": "what's 42 x 7?"}]}
)
```
1. This disables error handling (enabled by default). See all available strategies in the [API reference][langgraph.prebuilt.tool_node.ToolNode].
=== "Custom error handling"
```python
from langgraph.prebuilt import create_react_agent, ToolNode
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
if a == 42:
raise ValueError("The ultimate error")
return a * b
# highlight-next-line
tool_node = ToolNode(
[multiply],
# highlight-next-line
handle_tool_errors=(
"Can't use 42 as a first operand, you must switch operands!" # (1)!
)
)
agent_custom_error_handling = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=tool_node
)
agent_custom_error_handling.invoke(
{"messages": [{"role": "user", "content": "what's 42 x 7?"}]}
)
```
1. This provides a custom message to send to the LLM in case of an exception. See all available strategies in the [API reference][langgraph.prebuilt.tool_node.ToolNode].
See [API reference][langgraph.prebuilt.tool_node.ToolNode] for more information on different tool error handling options.
## Working with memory
LangGraph allows access to short-term and long-term memory from tools. See [Memory](../how-tos/memory/add-memory.md) guide for more information on:
* how to [read](../how-tos/memory/add-memory.md#read-short-term) from and [write](../how-tos/memory/add-memory.md#write-short-term) to **short-term** memory
* how to [read](../how-tos/memory/add-memory.md#read-long-term) from and [write](../how-tos/memory/add-memory.md#write-long-term) to **long-term** memory
## Prebuilt tools
You can use prebuilt tools from model providers by passing a dictionary with tool specs to the `tools` parameter of `create_react_agent`. For example, to use the `web_search_preview` tool from OpenAI:
```python
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(
model="openai:gpt-4o-mini",
tools=[{"type": "web_search_preview"}]
)
response = agent.invoke(
{"messages": ["What was a positive news story from today?"]}
)
```
Additionally, LangChain supports a wide range of prebuilt tool integrations for interacting with APIs, databases, file systems, web data, and more. These tools extend the functionality of agents and enable rapid development.
You can browse the full list of available integrations in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/tools/).
Some commonly used tool categories include:
- **Search**: Bing, SerpAPI, Tavily
- **Code interpreters**: Python REPL, Node.js REPL
- **Databases**: SQL, MongoDB, Redis
- **Web data**: Web scraping and browsing
- **APIs**: OpenWeatherMap, NewsAPI, and others
These integrations can be configured and added to your agents using the same `tools` parameter shown in the examples above.
+2 -2
View File
@@ -13,7 +13,7 @@ You can use a prebuilt chat UI for interacting with any LangGraph agent through
## Run agent in UI
First, set up LangGraph API server [locally](./deployment.md#launch-langgraph-server-locally) or deploy your agent on [LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/quick_start/).
First, set up LangGraph API server [locally](../tutorials/langgraph-platform/local-server.md) or deploy your agent on [LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/quick_start/).
Then, navigate to [Agent Chat UI](https://agentchat.vercel.app), or clone the repository and [run the dev server locally](https://github.com/langchain-ai/agent-chat-ui?tab=readme-ov-file#setup):
@@ -25,7 +25,7 @@ Then, navigate to [Agent Chat UI](https://agentchat.vercel.app), or clone the re
## Add human-in-the-loop
Agent Chat UI has full support for [human-in-the-loop](../concepts/human_in_the_loop.md) workflows. To try it out, replace the agent code in `src/agent/graph.py` (from the [deployment](./deployment.md) guide) with this [agent implementation](../how-tos/human_in_the_loop/add-human-in-the-loop.md#add-interrupts-to-any-tool):
Agent Chat UI has full support for [human-in-the-loop](../concepts/human_in_the_loop.md) workflows. To try it out, replace the agent code in `src/agent/graph.py` (from the [deployment](../tutorials/langgraph-platform/local-server.md) guide) with this [agent implementation](../how-tos/human_in_the_loop/add-human-in-the-loop.md#add-interrupts-to-any-tool):
<video controls src="../assets/interrupt-chat-ui.mp4" type="video/mp4"></video>
@@ -0,0 +1,54 @@
# Data Storage and Privacy
This document describes how data is processed in the LangGraph CLI and the LangGraph Server for both the in-memory server (`langgraph dev`) and the local Docker server (`langgraph up`). It also describes what data is tracked when interacting with the hosted LangGraph Studio frontend.
## CLI
LangGraph **CLI** is the command-line interface for building and running LangGraph applications; see the [CLI guide](../../concepts/langgraph_cli.md) to learn more.
By default, calls to most CLI commands log a single analytics event upon invocation. This helps us better prioritize improvements to the CLI experience. Each telemetry event contains the calling process's OS, OS version, Python version, the CLI version, the command name (`dev`, `up`, `run`, etc.), and booleans representing whether a flag was passed to the command. You can see the full analytics logic [here](https://github.com/langchain-ai/langgraph/blob/main/libs/cli/langgraph_cli/analytics.py).
You can disable all CLI telemetry by setting `LANGGRAPH_CLI_NO_ANALYTICS=1`.
## LangGraph Server (in-memory & docker)
The [LangGraph Server](../../concepts/langgraph_server.md) provides a durable execution runtime that relies on persisting checkpoints of your application state, long-term memories, thread metadata, assistants, and similar resources to the local file system or a database. Unless you have deliberately customized the storage location, this information is either written to local disk (for `langgraph dev`) or a PostgreSQL database (for `langgraph up` and in all deployments).
### LangSmith Tracing
When running the LangGraph server (either in-memory or in Docker), LangSmith tracing may be enabled to facilitate faster debugging and offer observability of graph state and LLM prompts in production. You can always disable tracing by setting `LANGSMITH_TRACING=false` in your server's runtime environment.
### In-memory development server (`langgraph dev`)
`langgraph dev` runs an [in-memory development server](../../tutorials/langgraph-platform/local-server.md) as a single Python process, designed for quick development and testing. It saves all checkpointing and memory data to disk within a `.langgraph_api` directory in the current working directory. Apart from the telemetry data described in the [CLI](#cli) section, no data leaves the machine unless you have enabled tracing or your graph code explicitly contacts an external service.
### Standalone Container (`langgraph up`)
`langgraph up` builds your local package into a Docker image and runs the server as a [standalone container](../../concepts/deployment_options.md#standalone-container) consisting of three containers: the API server, a PostgreSQL container, and a Redis container. All persistent data (checkpoints, assistants, etc.) are stored in the PostgreSQL database. Redis is used as a pubsub connection for real-time streaming of events. You can encrypt all checkpoints before saving to the database by setting a valid `LANGGRAPH_AES_KEY` environment variable. You can also specify [TTLs](../../how-tos/ttl/configure_ttl.md) for checkpoints and cross-thread memories in `langgraph.json` to control how long data is stored. All persisted threads, memories, and other data can be deleted via the relevant API endpoints.
Additional API calls are made to confirm that the server has a valid license and to track the number of executed runs and tasks. Periodically, the API server validates the provided license key (or API key).
If you've disabled [tracing](#langsmith-tracing), no user data is persisted externally unless your graph code explicitly contacts an external service.
## Studio
[LangGraph Studio](../../concepts/langgraph_studio.md) is a graphical interface for interacting with your LangGraph server. It does not persist any private data (the data you send to your server is not sent to LangSmith). Though the studio interface is served at [smith.langchain.com](https://smith.langchain.com), it is run in your browser and connects directly to your local LangGraph server so that no data needs to be sent to LangSmith.
If you are logged in, LangSmith does collect some usage analytics to help improve studio's user experience. This includes:
- Page visits and navigation patterns
- User actions (button clicks)
- Browser type and version
- Screen resolution and viewport size
Importantly, no application data or code (or other sensitive configuration details) are collected. All of that is stored in the persistence layer of your LangGraph server. When using Studio anonymously, no account creation is required and usage analytics are not collected.
## Quick reference
In summary, you can opt-out of server-side telemetry by turning off CLI analytics and disabling tracing.
| Variable | Purpose | Default |
| ------------------------------ | ------------------------- | -------------------------------- |
| `LANGGRAPH_CLI_NO_ANALYTICS=1` | Disable CLI analytics | Analytics enabled |
| `LANGSMITH_API_KEY` | Enable LangSmith tracing | Tracing disabled |
| `LANGSMITH_TRACING=false` | Disable LangSmith tracing | Depends on environment |
-12
View File
@@ -1,12 +0,0 @@
# Threads
A thread contains the accumulated state of a sequence of [runs](../../concepts/assistants.md#execution). When a run is executed, the [state](../../concepts/low_level.md#state) of the underlying graph of the assistant will be persisted to the thread.
A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run.
The state of a thread at a particular point in time is called a [checkpoint](../../concepts/persistence.md#checkpoints). Checkpoints are persisted and can be used to restore the state of a thread at a later time.
## Learn more
* For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](../../concepts/persistence.md).
* The LangGraph Platform API provides several endpoints for creating and managing threads and thread state. See the [API reference](../../cloud/reference/api/api_ref.html#tag/threads) for more details.
@@ -3,7 +3,7 @@
Before deploying, review the [conceptual guide for the Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployment option.
!!! info "Important"
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan.
The Self-Hosted Control Plane deployment option requires an [Enterprise](../../concepts/plans.md) plan.
## Prerequisites
@@ -3,7 +3,7 @@
Before deploying, review the [conceptual guide for the Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) deployment option.
!!! info "Important"
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan.
The Self-Hosted Data Plane deployment option requires an [Enterprise](../../concepts/plans.md) plan.
## Prerequisites
+69 -1
View File
@@ -1,4 +1,4 @@
How to integrate LangGraph into your React application# How to integrate LangGraph into your React application
# How to integrate LangGraph into your React application
!!! info "Prerequisites"
@@ -503,6 +503,74 @@ const handleSubmit = (text: string) => {
};
```
### Cached Thread Display
Use the `initialValues` option to display cached thread data immediately while the history is being loaded from the server. This improves user experience by showing cached data instantly when navigating to existing threads.
```tsx
import { useStream } from "@langchain/langgraph-sdk/react";
const CachedThreadExample = ({ threadId, cachedThreadData }) => {
const stream = useStream({
apiUrl: "http://localhost:2024",
assistantId: "agent",
threadId,
// Show cached data immediately while history loads
initialValues: cachedThreadData?.values,
messagesKey: "messages",
});
return (
<div>
{stream.messages.map((message) => (
<div key={message.id}>{message.content as string}</div>
))}
</div>
);
};
```
### Optimistic Thread Creation
Use the `threadId` option in `submit` function to enable optimistic UI patterns where you need to know the thread ID before the thread is actually created.
```tsx
import { useState } from "react";
import { useStream } from "@langchain/langgraph-sdk/react";
const OptimisticThreadExample = () => {
const [threadId, setThreadId] = useState<string | null>(null);
const [optimisticThreadId] = useState(() => crypto.randomUUID());
const stream = useStream({
apiUrl: "http://localhost:2024",
assistantId: "agent",
threadId,
onThreadId: setThreadId, // (3) Updated after thread has been created.
messagesKey: "messages",
});
const handleSubmit = (text: string) => {
// (1) Perform a soft navigation to /threads/${optimisticThreadId}
// without waiting for thread creation.
window.history.pushState({}, "", `/threads/${optimisticThreadId}`);
// (2) Submit message to create thread with the predetermined ID.
stream.submit(
{ messages: [{ type: "human", content: text }] },
{ threadId: optimisticThreadId }
);
};
return (
<div>
<p>Thread ID: {threadId ?? optimisticThreadId}</p>
{/* Rest of component */}
</div>
);
};
```
### TypeScript
The `useStream()` hook is friendly for apps written in TypeScript and you can specify types for the state to get better type safety and IDE support.
+2 -1
View File
@@ -154,8 +154,9 @@ You can now test the API:
```bash
curl -s --request POST \
--url <DEPLOYMENT_URL> \
--url <DEPLOYMENT_URL>/runs/stream \
--header 'Content-Type: application/json' \
--header "X-Api-Key: <LANGSMITH API KEY> \
--data "{
\"assistant_id\": \"agent\",
\"input\": {
+4 -4
View File
@@ -1,12 +1,12 @@
# API Reference
# LangGraph Server API Reference
The LangGraph Platform API reference is available with each deployment at the `/docs` URL path (e.g. `http://localhost:8124/docs`).
The LangGraph Server API reference is available within each deployment at the `/docs` endpoint (e.g. `http://localhost:8124/docs`).
Click <a href="/langgraph/cloud/reference/api/api_ref.html" target="_blank">here</a> to view the API reference.
## Authentication
For deployments to LangGraph Platform, authentication is required. Pass the `X-Api-Key` header with each request to the LangGraph Platform API. The value of the header should be set to a valid LangSmith API key for the organization where the API is deployed.
For deployments to LangGraph Platform, authentication is required. Pass the `X-Api-Key` header with each request to the LangGraph Server. The value of the header should be set to a valid LangSmith API key for the organization where the LangGraph Server is deployed.
Example `curl` command:
```shell
@@ -18,5 +18,5 @@ curl --request POST \
"metadata": {},
"limit": 10,
"offset": 0
}'
}'
```
@@ -0,0 +1,247 @@
# LangGraph Control Plane API Reference
The LangGraph Control Plane API is used to programmatically create and manage LangGraph Server deployments. For example, the APIs can be orchestrated to create custom CI/CD workflows.
Click <a href="https://api.host.langchain.com/docs" target="_blank">here</a> to view the API reference.
## Host
LangGraph Control Plane hosts for Cloud SaaS data regions:
| US | EU |
|----|----|
| `https://api.host.langchain.com` | `https://eu.api.host.langchain.com` |
**Note**: Self-hosted deployments of LangGraph Platform will have a custom host for the LangGraph Control Plane.
## Authentication
To authenticate with the LangGraph Control Plane API, set the `X-Api-Key` header to a valid LangSmith API key.
Example `curl` command:
```shell
curl --request GET \
--url http://localhost:8124/v2/deployments \
--header 'X-Api-Key: LANGSMITH_API_KEY'
```
## Versioning
Each endpoint path is prefixed with a version (e.g. `v1`, `v2`).
## Quick Start
1. Call `POST /v2/deployments` to create a new Deployment. The response body contains the Deployment ID (`id`) and the ID of the latest (and first) revision (`latest_revision_id`).
1. Call `GET /v2/deployments/{deployment_id}` to retrieve the Deployment. Set `deployment_id` in the URL to the value of Deployment ID (`id`).
1. Poll for revision `status` until `status` is `DEPLOYED` by calling `GET /v2/deployments/{deployment_id}/revisions/{latest_revision_id}`.
1. Call `PATCH /v2/deployments/{deployment_id}` to update the deployment.
## Example Code
Below is example Python code that demonstrates how to orchestrate the LangGraph Control Plane APIs to create a deployment, update the deployment, and delete the deployment.
```python
import os
import time
import requests
from dotenv import load_dotenv
load_dotenv()
# required environment variables
CONTROL_PLANE_HOST = os.getenv("CONTROL_PLANE_HOST")
LANGSMITH_API_KEY = os.getenv("LANGSMITH_API_KEY")
INTEGRATION_ID = os.getenv("INTEGRATION_ID")
MAX_WAIT_TIME = 1800 # 30 mins
def get_headers() -> dict:
"""Return common headers for requests to LangGraph Control Plane API."""
return {
"X-Api-Key": LANGSMITH_API_KEY,
}
def create_deployment() -> str:
"""Create deployment. Return deployment ID."""
headers = get_headers()
headers["Content-Type"] = "application/json"
deployment_name = "my_deployment"
request_body = {
"name": deployment_name,
"source": "github",
"source_config": {
"integration_id": INTEGRATION_ID,
"repo_url": "https://github.com/langchain-ai/langgraph-example",
"deployment_type": "dev",
"build_on_push": False,
"custom_url": None,
"resource_spec": None,
},
"source_revision_config": {
"repo_ref": "main",
"langgraph_config_path": "langgraph.json",
"image_uri": None,
},
"secrets": [
{
"name": "OPENAI_API_KEY",
"value": "test_openai_api_key",
},
{
"name": "ANTHROPIC_API_KEY",
"value": "test_anthropic_api_key",
},
{
"name": "TAVILY_API_KEY",
"value": "test_tavily_api_key",
},
],
}
response = requests.post(
url=f"{CONTROL_PLANE_HOST}/v2/deployments",
headers=headers,
json=request_body,
)
if response.status_code != 201:
raise Exception(f"Failed to create deployment: {response.text}")
deployment_id = response.json()["id"]
print(f"Created deployment {deployment_name} ({deployment_id})")
return deployment_id
def get_deployment(deployment_id: str) -> dict:
"""Get deployment."""
response = requests.get(
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}",
headers=get_headers(),
)
if response.status_code != 200:
raise Exception(f"Failed to get deployment ID {deployment_id}: {response.text}")
return response.json()
def list_revisions(deployment_id: str) -> list[dict]:
"""List revisions.
Return list is sorted by created_at in descending order (latest first).
"""
response = requests.get(
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}/revisions",
headers=get_headers(),
)
if response.status_code != 200:
raise Exception(
f"Failed to list revisions for deployment ID {deployment_id}: {response.text}"
)
return response.json()
def get_revision(
deployment_id: str,
revision_id: str,
) -> dict:
"""Get revision."""
response = requests.get(
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}/revisions/{revision_id}",
headers=get_headers(),
)
if response.status_code != 200:
raise Exception(f"Failed to get revision ID {revision_id}: {response.text}")
return response.json()
def patch_deployment(deployment_id: str) -> None:
"""Patch deployment."""
headers = get_headers()
headers["Content-Type"] = "application/json"
response = requests.patch(
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}",
headers=headers,
json={
"source_config": {
"build_on_push": True,
},
"source_revision_config": {
"repo_ref": "main",
"langgraph_config_path": "langgraph.json",
},
},
)
if response.status_code != 200:
raise Exception(f"Failed to patch deployment: {response.text}")
print(f"Patched deployment ID {deployment_id}")
def wait_for_deployment(deployment_id: str, revision_id: str) -> None:
"""Wait for revision status to be DEPLOYED."""
start_time = time.time()
revision, status = None, None
while time.time() - start_time < MAX_WAIT_TIME:
revision = get_revision(deployment_id, revision_id)
status = revision["status"]
if status == "DEPLOYED":
break
elif "FAILED" in status:
raise Exception(f"Revision ID {revision_id} failed: {revision}")
print(f"Waiting for revision ID {revision_id} to be DEPLOYED...")
time.sleep(60)
if status != "DEPLOYED":
raise Exception(
f"Timeout waiting for revision ID {revision_id} to be DEPLOYED: {revision}"
)
def delete_deployment(deployment_id: str) -> None:
"""Delete deployment."""
response = requests.delete(
url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}",
headers=get_headers(),
)
if response.status_code != 204:
raise Exception(
f"Failed to delete deployment ID {deployment_id}: {response.text}"
)
print(f"Deployment ID {deployment_id} deleted")
if __name__ == "__main__":
# create deployment and get the latest revision
deployment_id = create_deployment()
revisions = list_revisions(deployment_id)
latest_revision = revisions["resources"][0]
latest_revision_id = latest_revision["id"]
# wait for latest revision to be DEPLOYED
wait_for_deployment(deployment_id, latest_revision_id)
# patch the deployment and get the latest revision
patch_deployment(deployment_id)
revisions = list_revisions(deployment_id)
latest_revision = revisions["resources"][0]
latest_revision_id = latest_revision["id"]
# wait for latest revision to be DEPLOYED
wait_for_deployment(deployment_id, latest_revision_id)
# delete the deployment
delete_deployment(deployment_id)
```
File diff suppressed because it is too large Load Diff
+8 -3
View File
@@ -51,9 +51,10 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
| <span style="white-space: nowrap;">`node_version`</span> | Specify `node_version: 20` to use LangGraph.js. |
| <span style="white-space: nowrap;">`pip_config_file`</span> | Path to `pip` config file. |
| <span style="white-space: nowrap;">`pip_installer`</span> | _(Added in v0.3)_ Optional. Python package installer selector. It can be set to `"auto"`, `"pip"`, or `"uv"`. From version&nbsp;0.3 onward the default strategy is to run `uv pip`, which typically delivers faster builds while remaining a drop-in replacement. In the uncommon situation where `uv` cannot handle your dependency graph or the structure of your `pyproject.toml`, specify `"pip"` here to revert to the earlier behaviour. |
| <span style="white-space: nowrap;">`keep_pkg_tools`</span> | _(Added in v0.3.4)_ Optional. Control whether to retain Python packaging tools (`pip`, `setuptools`, `wheel`) in the final image. Accepted values: <ul><li><code>true</code> : Keep all three tools (skip uninstall).</li><li><code>false</code> / omitted : Uninstall all three tools (default behaviour).</li><li><code>list[str]</code> : Names of tools <strong>to retain</strong>. Each value must be one of "pip", "setuptools", "wheel".</li></ul>. By default, all three tools are uninstalled. |
| <span style="white-space: nowrap;">`dockerfile_lines`</span> | Array of additional lines to add to Dockerfile following the import from parent image. |
| <span style="white-space: nowrap;">`checkpointer`</span> | Configuration for the checkpointer. Contains a `ttl` field which is an object with the following keys: <ul><li>`strategy`: How to handle expired checkpoints (e.g., `"delete"`).</li><li>`sweep_interval_minutes`: How often to check for expired checkpoints (integer).</li><li>`default_ttl`: Default time-to-live for checkpoints in **minutes** (integer). Defines how long checkpoints are kept before the specified strategy is applied.</li></ul> |
| <span style="white-space: nowrap;">`http`</span> | HTTP server configuration with the following fields: <ul><li>`app`: Path to custom Starlette/FastAPI app (e.g., `"./src/agent/webapp.py:app"`). See [custom routes guide](../../how-tos/http/custom_routes.md).</li><li>`disable_assistants`: Disable `/assistants` routes</li><li>`disable_threads`: Disable `/threads` routes</li><li>`disable_runs`: Disable `/runs` routes</li><li>`disable_store`: Disable `/store` routes</li><li>`disable_meta`: Disable `/ok`, `/info`, `/metrics`, and `/docs` routes</li><li>`disable_mcp`: Disable `/mcp` routes</li><li>`cors`: CORS configuration with fields for `allow_origins`, `allow_methods`, `allow_headers`, etc.</li><li>`configurable_headers`: Define which request headers to exclude or include as a run's configurable values.</li></ul> |
| <span style="white-space: nowrap;">`http`</span> | HTTP server configuration with the following fields: <ul><li>`app`: Path to custom Starlette/FastAPI app (e.g., `"./src/agent/webapp.py:app"`). See [custom routes guide](../../how-tos/http/custom_routes.md).</li><li>`cors`: CORS configuration with fields for `allow_origins`, `allow_methods`, `allow_headers`, etc.</li><li>`configurable_headers`: Define which request headers to exclude or include as a run's configurable values.</li><li>`disable_assistants`: Disable `/assistants` routes</li><li>`disable_mcp`: Disable `/mcp` routes</li><li>`disable_meta`: Disable `/ok`, `/info`, `/metrics`, and `/docs` routes</li><li>`disable_runs`: Disable `/runs` routes</li><li>`disable_store`: Disable `/store` routes</li><li>`disable_threads`: Disable `/threads` routes</li><li>`disable_ui`: Disable `/ui` routes</li><li>`disable_webhooks`: Disable webhooks calls on run completion in all routes</li><li>`mount_prefix`: Prefix for mounted routes (e.g., "/my-deployment/api")</li></ul> |
=== "JS"
@@ -395,7 +396,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
=== "Python"
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform closed beta. Requires a license key for production use.
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform. Requires a license key for production use.
**Usage**
@@ -408,6 +409,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
| Option | Default | Description |
| ---------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `--wait` | | Wait for services to start before returning. Implies --detach |
| `--base-image TEXT` | `langchain/langgraph-api` | Base image to use for the LangGraph API server. Pin to specific versions using version tags. |
| `--image TEXT` | | Docker image to use for the langgraph-api service. If specified, skips building and uses this image directly. |
| `--postgres-uri TEXT` | Local database | Postgres URI to use for the database. |
| `--watch` | | Restart on file changes |
| `--debugger-base-url TEXT` | `http://127.0.0.1:[PORT]` | URL used by the debugger to access LangGraph API. |
@@ -422,7 +425,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
=== "JS"
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform closed beta. Requires a license key for production use.
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform. Requires a license key for production use.
**Usage**
@@ -435,6 +438,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
| Option | Default | Description |
| ---------------------------------------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| <span style="white-space: nowrap;">`--wait`</span> | | Wait for services to start before returning. Implies --detach |
| <span style="white-space: nowrap;">`--base-image TEXT`</span> | <span style="white-space: nowrap;">`langchain/langgraph-api`</span> | Base image to use for the LangGraph API server. Pin to specific versions using version tags. |
| <span style="white-space: nowrap;">`--image TEXT`</span> | | Docker image to use for the langgraph-api service. If specified, skips building and uses this image directly. |
| <span style="white-space: nowrap;">`--postgres-uri TEXT`</span> | Local database | Postgres URI to use for the database. |
| <span style="white-space: nowrap;">`--watch`</span> | | Restart on file changes |
| <span style="white-space: nowrap;">`-c, --config FILE`</span> | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
+37 -23
View File
@@ -10,6 +10,10 @@ This environment variable should be set to `True` if the implementation of a gra
Defaults to `False`.
## `BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS`
Specifies, in seconds, how long the server will wait for background jobs to finish after the queue receives a shutdown signal. After this period, the server will force termination. Defaults to `180` seconds. Set this to ensure jobs have enough time to complete cleanly during shutdown. Added in `langgraph-api==0.2.16`.
## `BG_JOB_TIMEOUT_SECS`
The timeout of a background run can be increased. However, the infrastructure for a Cloud SaaS deployment enforces a 1 hour timeout limit for API requests. This means the connection between client and server will timeout after 1 hour. This is not configurable.
@@ -18,10 +22,6 @@ A background run can execute for longer than 1 hour, but a client must reconnect
Defaults to `3600`.
## `BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS`
Specifies, in seconds, how long the server will wait for background jobs to finish after the queue receives a shutdown signal. After this period, the server will force termination. Defaults to `3600` seconds. Set this to ensure jobs have enough time to complete cleanly during shutdown. Added in `langgraph-api==0.2.16`.
## `DD_API_KEY`
Specify `DD_API_KEY` (your [Datadog API Key](https://docs.datadoghq.com/account_management/api-app-keys/)) to automatically enable Datadog tracing for the deployment. Specify other [`DD_*` environment variables](https://ddtrace.readthedocs.io/en/stable/configuration.html) to configure the tracing instrumentation.
@@ -40,6 +40,14 @@ Type of authentication for the LangGraph Server deployment. Valid values: `langs
For deployments to LangGraph Platform, this environment variable is set automatically. For local development or deployments where authentication is handled externally (e.g. self-hosted), set this environment variable to `noop`.
## `LANGGRAPH_POSTGRES_POOL_MAX_SIZE`
Beginning with langgraph-api version `0.2.12`, the maximum size of the Postgres connection pool (per replica) can be controlled using the `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` environment variable. By setting this variable, you can determine the upper bound on the number of simultaneous connections the server will establish with the Postgres database.
For example, if a deployment is scaled up to 10 replicas and `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` is configured to `150`, then up to `1500` connections to Postgres can be established. This is particularly useful for deployments where database resources are limited (or more available) or where you need to tune connection behavior for performance or scaling reasons.
Defaults to `150` connections.
## `LANGSMITH_RUNS_ENDPOINTS`
For deployments with [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) only.
@@ -54,6 +62,10 @@ Set `LANGSMITH_TRACING` to `false` to disable tracing to LangSmith.
Defaults to `true`.
## `LOG_COLOR`
This is mainly relevant in the context of using the dev server via the `langgraph dev` command. Set `LOG_COLOR` to `true` to enable ANSI-colored console output when using the default console renderer. Disabling color output by setting this variable to `false` produces monochrome logs. Defaults to `true`.
## `LOG_LEVEL`
Configure [log level](https://docs.python.org/3/library/logging.html#logging-levels). Defaults to `INFO`.
@@ -62,9 +74,14 @@ Configure [log level](https://docs.python.org/3/library/logging.html#logging-lev
Set `LOG_JSON` to `true` to render all log messages as JSON objects using the configured `JSONRenderer`. This produces structured logs that can be easily parsed or ingested by log management systems. Defaults to `false`.
## `LOG_COLOR`
## `MOUNT_PREFIX`
This is mainly relevant in the context of using the dev server via the `langgraph dev` command. Set `LOG_COLOR` to `true` to enable ANSI-colored console output when using the default console renderer. Disabling color output by setting this variable to `false` produces monochrome logs. Defaults to `true`.
!!! info "Only Allowed in Self-Hosted Deployments"
The `MOUNT_PREFIX` environment variable is only allowed in Self-Hosted Deployment models, LangGraph Platform SaaS will not allow this environment variable.
Set `MOUNT_PREFIX` to serve the LangGraph Server under a specific path prefix. This is useful for deployments where the server is behind a reverse proxy or load balancer that requires a specific path prefix.
For example, if the server is to be served under `https://example.com/langgraph`, set `MOUNT_PREFIX` to `/langgraph`.
## `N_JOBS_PER_WORKER`
@@ -94,16 +111,14 @@ Database Connectivity:
- The custom Postgres instance must be accessible by the LangGraph Server. The user is responsible for ensuring connectivity.
## `LANGGRAPH_POSTGRES_POOL_MAX_SIZE`
## `REDIS_CLUSTER`
Beginning with langgraph-api version `0.2.12`, the maximum size of the Postgres connection pool can be controlled using the `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` environment variable. By setting this variable, you can determine the upper bound on the number of simultaneous connections the server will establish with the Postgres database. This is particularly useful for deployments where database resources are limited (or more available) or where you need to tune connection behavior for performance or scaling reasons. If not specified, the pool size defaults to 150 connections.
!!! info "Only Allowed in Self-Hosted Deployments"
Redis Cluster mode is only available in Self-Hosted Deployment models, LangGraph Platform SaaS will provision a redis instance for you by default.
## `REDIS_URI_CUSTOM`
Set `REDIS_CLUSTER` to `True` to enable Redis Cluster mode. When enabled, the system will connect to Redis using cluster mode. This is useful when connecting to a Redis Cluster deployment.
!!! info "Only for Self-Hosted Data Plane and Self-Hosted Control Plane"
Custom Redis instances are only available for [Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployments.
Specify `REDIS_URI_CUSTOM` to use a custom Redis instance. The value of `REDIS_URI_CUSTOM` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
Defaults to `False`.
## `REDIS_KEY_PREFIX`
@@ -114,20 +129,19 @@ Specify a prefix for Redis keys. This allows multiple LangGraph Server instances
Defaults to `''`.
## `REDIS_CLUSTER`
## `REDIS_URI_CUSTOM`
!!! info "Only Allowed in Self-Hosted Deployments"
Redis Cluster mode is only available in Self-Hosted Deployment models, LangGraph Platform SaaS will provision a redis instance for you by default.
!!! info "Only for Self-Hosted Data Plane and Self-Hosted Control Plane"
Custom Redis instances are only available for [Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployments.
Set `REDIS_CLUSTER` to `True` to enable Redis Cluster mode. When enabled, the system will connect to Redis using cluster mode. This is useful when connecting to a Redis Cluster deployment.
Specify `REDIS_URI_CUSTOM` to use a custom Redis instance. The value of `REDIS_URI_CUSTOM` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
Defaults to `False`.
## `RESUMABLE_STREAM_TTL_SECONDS`
## `MOUNT_PREFIX`
Time-to-live in seconds for resumable stream data in Redis.
!!! info "Only Allowed in Self-Hosted Deployments"
The `MOUNT_PREFIX` environment variable is only allowed in Self-Hosted Deployment models, LangGraph Platform SaaS will not allow this environment variable.
When a run is created and the output is streamed, the stream can be configured to be resumable (e.g. `stream_resumable=True`). If a stream is resumable, output from the stream is temporarily stored in Redis. The TTL for this data can be configured by setting `RESUMABLE_STREAM_TTL_SECONDS`.
Set `MOUNT_PREFIX` to serve the LangGraph Server under a specific path prefix. This is useful for deployments where the server is behind a reverse proxy or load balancer that requires a specific path prefix.
See the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.RunsClient.stream) and [JS/TS](https://langchain-ai.github.io/langgraphjs/reference/classes/sdk_client.RunsClient.html#stream) SDKs for more details on how to implement resumable streams.
For example, if the server is to be served under `https://example.com/langgraph`, set `MOUNT_PREFIX` to `/langgraph`.
Defaults to `120` seconds.
@@ -0,0 +1,148 @@
# LangGraph Server Changelog
[LangGraph Server](../../concepts/langgraph_server.md) is an API platform for creating and managing agent-based applications. It provides built-in persistence, a task queue, and supports deploying, configuring, and running assistants (agentic workflows) at scale. This changelog documents all notable updates, features, and fixes to LangGraph Server releases.
---
## v0.2.85 (2025-07-10)
- Added support for the `on_disconnect` field to `runs/wait` and included disconnect logs for better debugging.
## v0.2.84 (2025-07-09)
- Removed unnecessary status updates to streamline thread handling and updated version to 0.2.84.
## v0.2.83 (2025-07-09)
- Reduced the default time-to-live for resumable streams to 2 minutes.
- Enhanced data submission logic to send data to both Beacon and LangSmith instance based on license configuration.
- Enabled submission of self-hosted data to a Langsmith instance when the endpoint is configured.
## v0.2.82 (2025-07-03)
- Addressed a race condition in background runs by implementing a lock using join, ensuring reliable execution across CTEs.
## v0.2.81 (2025-07-03)
- Optimized run streams by reducing initial wait time to improve responsiveness for older or non-existent runs.
## v0.2.80 (2025-07-03)
- Corrected parameter passing in the `logger.ainfo()` API call to resolve a TypeError.
## v0.2.79 (2025-07-02)
- Fixed a JsonDecodeError in checkpointing with remote graph by correcting JSON serialization to handle trailing slashes properly.
- Introduced a configuration flag to disable webhooks globally across all routes.
## v0.2.78 (2025-07-02)
- Added timeout retries to webhook calls to improve reliability.
- Added HTTP request metrics, including a request count and latency histogram, for enhanced monitoring capabilities.
## v0.2.77 (2025-07-02)
- Added HTTP metrics to improve performance monitoring.
- Changed the Redis cache delimiter to reduce conflicts with subgraph message names and updated caching behavior.
## v0.2.76 (2025-07-01)
- Updated Redis cache delimiter to prevent conflicts with subgraph messages.
## v0.2.74 (2025-06-30)
- Scheduled webhooks in an isolated loop to ensure thread-safe operations and prevent errors with PYTHONASYNCIODEBUG=1.
## v0.2.73 (2025-06-27)
- Fixed an infinite frame loop issue and removed the dict_parser due to structlog's unexpected behavior.
- Throw a 409 error on deadlock occurrence during run cancellations to handle lock conflicts gracefully.
## v0.2.72 (2025-06-27)
- Ensured compatibility with future langgraph versions.
- Implemented a 409 response status to handle deadlock issues during cancellation.
## v0.2.71 (2025-06-26)
- Improved logging for better clarity and detail regarding log types.
## v0.2.70 (2025-06-26)
- Improved error handling to better distinguish and log TimeoutErrors caused by users from internal run timeouts.
## v0.2.69 (2025-06-26)
- Added sorting and pagination to the crons API and updated schema definitions for improved accuracy.
## v0.2.66 (2025-06-26)
- Fixed a 404 error when creating multiple runs with the same thread_id using `on_not_exist="create"`.
## v0.2.65 (2025-06-25)
- Ensured that only fields from `assistant_versions` are returned when necessary.
- Ensured consistent data types for in-memory and PostgreSQL users, improving internal authentication handling.
## v0.2.64 (2025-06-24)
- Added descriptions to version entries for better clarity.
## v0.2.62 (2025-06-23)
- Improved user handling for custom authentication in the JS Studio.
- Added Prometheus-format run statistics to the metrics endpoint for better monitoring.
- Added run statistics in Prometheus format to the metrics endpoint.
## v0.2.61 (2025-06-20)
- Set a maximum idle time for Redis connections to prevent unnecessary open connections.
## v0.2.60 (2025-06-20)
- Enhanced error logging to include traceback details for dictionary operations.
- Added a `/metrics` endpoint to expose queue worker metrics for monitoring.
## v0.2.57 (2025-06-18)
- Removed CancelledError from retriable exceptions to allow local interrupts while maintaining retriability for workers.
- Introduced middleware to gracefully shut down the server after completing in-flight requests upon receiving a SIGINT.
- Reduced metadata stored in checkpoint to only include necessary information.
- Improved error handling in join runs to return error details when present.
## v0.2.56 (2025-06-17)
- Improved application stability by adding a handler for SIGTERM signals.
## v0.2.55 (2025-06-17)
- Improved the handling of cancellations in the queue entrypoint.
- Improved cancellation handling in the queue entry point.
## v0.2.54 (2025-06-16)
- Enhanced error message for LuaLock timeout during license validation.
- Fixed the $contains filter in custom auth by requiring an explicit ::text cast and updated tests accordingly.
- Ensured project and tenant IDs are formatted as UUIDs for consistency.
## v0.2.53 (2025-06-13)
- Resolved a timing issue to ensure the queue starts only after the graph is registered.
- Improved performance by setting thread and run status in a single query and enhanced error handling during checkpoint writes.
- Reduced the default background grace period to 3 minutes.
## v0.2.52 (2025-06-12)
- Now logging expected graphs when one is omitted to improve traceability.
- Implemented a time-to-live (TTL) feature for resumable streams.
- Improved query efficiency and consistency by adding a unique index and optimizing row locking.
## v0.2.51 (2025-06-12)
- Handled `CancelledError` by marking tasks as ready to retry, improving error management in worker processes.
- Added LG API version and request ID to metadata and logs for better tracking.
- Added LG API version and request ID to metadata and logs to improve traceability.
- Improved database performance by creating indexes concurrently.
- Ensured postgres write is committed only after the Redis running marker is set to prevent race conditions.
- Enhanced query efficiency and reliability by adding a unique index on thread_id/running, optimizing row locks, and ensuring deterministic run selection.
- Resolved a race condition by ensuring Postgres updates only occur after the Redis running marker is set.
## v0.2.46 (2025-06-07)
- Introduced a new connection for each operation while preserving transaction characteristics in Threads state `update()` and `bulk()` commands.
## v0.2.45 (2025-06-05)
- Enhanced streaming feature by incorporating tracing contexts.
- Removed an unnecessary query from the Crons.search function.
- Resolved connection reuse issue when scheduling next run for multiple cron jobs.
- Removed an unnecessary query in the Crons.search function to improve efficiency.
- Resolved an issue with scheduling the next cron run by improving connection reuse.
## v0.2.44 (2025-06-04)
- Enhanced the worker logic to exit the pipeline before continuing when the Redis message limit is reached.
- Introduced a ceiling for Redis message size with an option to skip messages larger than 128 MB for improved performance.
- Ensured the pipeline always closes properly to prevent resource leaks.
## v0.2.43 (2025-06-04)
- Improved performance by omitting logs in metadata calls and ensuring output schema compliance in value streaming.
- Ensured the connection is properly closed after use.
- Aligned output format to strictly adhere to the specified schema.
- Stopped sending internal logs in metadata requests to improve privacy.
## v0.2.42 (2025-06-04)
- Added timestamps to track the start and end of a request's run.
- Added tracer information to the configuration settings.
- Added support for streaming with tracing contexts.
## v0.2.41 (2025-06-03)
- Added locking mechanism to prevent errors in pipelined executions.
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -97,7 +97,7 @@ Parallel processing is vital for efficient multi-agent systems and complex tasks
- Implementation of map-reduce-like operations
- Efficient handling of independent subtasks
For practical implementation, see our [map-reduce tutorial](../how-tos/graph-api.ipynb#map-reduce-and-the-send-api)
For practical implementation, see our [map-reduce tutorial](../how-tos/graph-api.md#map-reduce-and-the-send-api)
### Subgraphs
@@ -107,7 +107,7 @@ For practical implementation, see our [map-reduce tutorial](../how-tos/graph-api
- Hierarchical organization of agent teams
- Controlled communication between agents and the main system
Subgraphs communicate with the parent graph through overlapping keys in the state schema. This enables flexible, modular agent design. For implementation details, refer to our [subgraph how-to guide](../how-tos/subgraph.ipynb).
Subgraphs communicate with the parent graph through overlapping keys in the state schema. This enables flexible, modular agent design. For implementation details, refer to our [subgraph how-to guide](../how-tos/subgraph.md).
### Reflection
+2 -2
View File
@@ -48,7 +48,7 @@ Below are examples of directory structures for Python and JavaScript application
│ ├── utils # utilities for your graph
│ │ ├── __init__.py
│ │ ├── tools.py # tools for your graph
│ │ ├── nodes.py # node functions for you graph
│ │ ├── nodes.py # node functions for your graph
│ │ └── state.py # state definition of your graph
│ ├── __init__.py
│ └── agent.py # code for constructing your graph
@@ -64,7 +64,7 @@ Below are examples of directory structures for Python and JavaScript application
├── src # all project code lies within here
│ ├── utils # optional utilities for your graph
│ │ ├── tools.ts # tools for your graph
│ │ ├── nodes.ts # node functions for you graph
│ │ ├── nodes.ts # node functions for your graph
│ │ └── state.ts # state definition of your graph
│ └── agent.ts # code for constructing your graph
├── package.json # package dependencies
+48
View File
@@ -143,6 +143,54 @@ The returned user information is available:
In many of our tutorials, we will just show the "authorization" parameter to be concise, but you can opt to accept more information as needed
to implement your custom authentication scheme.
### Agent authentication
Custom authentication permits delegated access. The values you return in `@auth.authenticate` are added to the run context, giving agents user-scoped credentials lets them access resources on the users behalf.
```mermaid
sequenceDiagram
%% Actors
participant ClientApp as Client
participant AuthProv as Auth Provider
participant LangGraph as LangGraph Backend
participant SecretStore as Secret Store
participant ExternalService as External Service
%% Platform login / AuthN
ClientApp ->> AuthProv: 1. Login (username / password)
AuthProv -->> ClientApp: 2. Return token
ClientApp ->> LangGraph: 3. Request with token
Note over LangGraph: 4. Validate token (@auth.authenticate)
LangGraph -->> AuthProv: 5. Fetch user info
AuthProv -->> LangGraph: 6. Confirm validity
%% Fetch user tokens from secret store
LangGraph ->> SecretStore: 6a. Fetch user tokens
SecretStore -->> LangGraph: 6b. Return tokens
Note over LangGraph: 7. Apply access control (@auth.on.*)
%% External Service round-trip
LangGraph ->> ExternalService: 8. Call external service (with header)
Note over ExternalService: 9. External service validates header and executes action
ExternalService -->> LangGraph: 10. Service response
%% Return to caller
LangGraph -->> ClientApp: 11. Return resources
```
After authentication, the platform creates a special configuration object that is passed to your graph and all nodes via the configurable context.
This object contains information about the current user, including any custom fields you return from your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler.
To enable an agent to act on behalf of the user, use [custom authentication middleware](../how-tos/auth/custom_auth.md). This will allow the agent to interact with external systems like MCP servers, external databases, and even other agents on behalf of the user.
For more information, see the [Use custom auth](../how-tos/auth/custom_auth.md#enable-agent-authentication) guide.
### Agent authentication with MCP
For information on how to authenticate an agent to an MCP server, see the [MCP conceptual guide](../concepts/mcp.md).
## Authorization
After authentication, LangGraph calls your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers to control access to specific resources (e.g., threads, assistants, crons). These handlers can:
+4 -4
View File
@@ -18,9 +18,9 @@ There are 4 main options for deploying with the [LangGraph Platform](langgraph_p
1. [Cloud SaaS](#cloud-saas)
1. [Self-Hosted Data Plane<sup>(Beta)</sup>](#self-hosted-data-plane)
1. [Self-Hosted Data Plane](#self-hosted-data-plane)
1. [Self-Hosted Control Plane<sup>(Beta)</sup>](#self-hosted-control-plane)
1. [Self-Hosted Control Plane](#self-hosted-control-plane)
1. [Standalone Container](#standalone-container)
@@ -50,7 +50,7 @@ For more information, please see:
## Self-Hosted Data Plane
!!! info "Important"
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](../concepts/plans.md) plan.
The Self-Hosted Data Plane deployment option requires an [Enterprise](../concepts/plans.md) plan.
The [Self-Hosted Data Plane](./langgraph_self_hosted_data_plane.md) deployment option is a "hybrid" model for deployment where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud. This option provides a way to securely manage your data plane infrastructure, while offloading control plane management to us.
@@ -66,7 +66,7 @@ For more information, please see:
## Self-Hosted Control Plane
!!! info "Important"
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](../concepts/plans.md) plan.
The Self-Hosted Control Plane deployment option requires an [Enterprise](../concepts/plans.md) plan.
The [Self-Hosted Control Plane](./langgraph_self_hosted_control_plane.md) deployment option is a fully self-hosted model for deployment where you manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in your cloud. This option gives you full control and responsibility of the control plane and data plane infrastructure.
+1 -1
View File
@@ -47,7 +47,7 @@ LangGraph is a stateful, orchestration framework that brings added control to ag
No. LangGraph Platform is proprietary software.
There is a free, self-hosted version of LangGraph Platform with access to basic features. The Cloud SaaS deployment option is free while in beta, but will eventually be a paid service. We will always give ample notice before charging for a service and reward our early adopters with preferential pricing. The Self-Hosted deployment options are paid services. [Contact our sales team](https://www.langchain.com/contact-sales) to learn more.
There is a free, self-hosted version of LangGraph Platform with access to basic features. The Cloud SaaS deployment option and the Self-Hosted deployment options are paid services. [Contact our sales team](https://www.langchain.com/contact-sales) to learn more.
For more information, see our [LangGraph Platform pricing page](https://www.langchain.com/pricing-langgraph-platform).
+30 -13
View File
@@ -26,7 +26,7 @@ The Control Plane UI is embedded in [LangSmith](https://docs.smith.langchain.com
## Control Plane API
This section describes data model of the control plane API. The API is used to create, update, and delete deployments. However, they are not publicly accessible.
This section describes the data model of the control plane API. The API is used to create, update, and delete deployments. See the [control plane API reference](../cloud/reference/api/api_ref_control_plane.md) for more details.
### Deployment
@@ -34,11 +34,7 @@ A deployment is an instance of a LangGraph Server. A single deployment can have
### Revision
A revision is an iteration of a deployment. When a new deployment is created, an initial revision is automatically created. To deploy code changes or update environment variables for a deployment, a new revision must be created.
### Environment Variable
Environment variables are set for a deployment. All environment variables are stored as secrets (i.e. saved in a secrets store).
A revision is an iteration of a deployment. When a new deployment is created, an initial revision is automatically created. To deploy code changes or update secrets for a deployment, a new revision must be created.
## Control Plane Features
@@ -50,21 +46,40 @@ For simplicity, the control plane offers two deployment types with different res
| **Deployment Type** | **CPU/Memory** | **Scaling** | **Database** |
|---------------------|-----------------|---------------------|----------------------------------------------------------------------------------|
| Development | 1 CPU, 1 GB RAM | Up to 1 container | 10 GB disk, no backups |
| Production | 2 CPU, 2 GB RAM | Up to 10 containers | Autoscaling disk, automatic backups, highly available (multi-zone configuration) |
| Development | 1 CPU, 1 GB RAM | Up to 1 replica | 10 GB disk, no backups |
| Production | 2 CPU, 2 GB RAM | Up to 10 replicas | Autoscaling disk, automatic backups, highly available (multi-zone configuration) |
CPU and memory resources are per container.
CPU and memory resources are per replica.
!!! warning "Immutable Deployment Type"
Once a deployment is created, the deployment type cannot be changed.
!!! info "Resource Customization"
For `Production` type deployments, resources can be manually increased on a case-by-case basis depending on use case and capacity constraints. Contact support@langchain.dev to request an increase in resources.
!!! info "Self-Hosted Deployment"
Resources for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments can be fully customized. Deployment types are only applicable for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
For `Development` types deployments, database disk size can be manually increased on a case-by-case basis depending on use case and capacity constraints. For most use cases, [TTLs](../how-tos/ttl/configure_ttl.md) should be configured to manage disk usage. Contact support@langchain.dev to request an increase in resources.
#### Production
Resources for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments can be fully customized.
`Production` type deployments are suitable for "production" workloads. For example, select `Production` for customer-facing applications in the critical path.
Resources for `Production` type deployments can be manually increased on a case-by-case basis depending on use case and capacity constraints. Contact support@langchain.dev to request an increase in resources.
#### Development
`Development` type deployments are suitable development and testing. For example, select `Development` for internal testing environments. `Development` type deployments are not suitable for "production" workloads.
!!! danger "Preemptible Compute Infrastructure"
`Development` type deployments (API server, queue server, and database) are provisioned on preemptible compute infrastructure. This means the compute infrastructure **may be terminated at any time without notice**. This may result in intermittent...
- Redis connection timeouts/errors
- Postgres connection timeouts/errors
- Failed or retrying background runs
This behavior is expected. Preemptible compute infrastructure **significantly reduces the cost to provision a `Development` type deployment**. By design, LangGraph Server is fault-tolerant. The implementation will automatically attempt to recover from Redis/Postgres connection errors and retry failed background runs.
`Production` type deployments are provisioned on durable compute infrastructure, not preemptible compute infrastructure.
Database disk size for `Development` type deployments can be manually increased on a case-by-case basis depending on use case and capacity constraints. For most use cases, [TTLs](../how-tos/ttl/configure_ttl.md) should be configured to manage disk usage. Contact support@langchain.dev to request an increase in resources.
### Database Provisioning
@@ -97,6 +112,8 @@ After a deployment is ready, the control plane monitors the deployment and recor
- Number of container restarts.
- Number of replicas (this will increase with [autoscaling](../concepts/langgraph_data_plane.md#autoscaling)).
- [Postgres](../concepts/langgraph_data_plane.md#postgres) CPU, memory usage, and disk usage.
- [LangGraph Server queue](../concepts/langgraph_server.md#persistence-and-task-queue) pending/active run count.
- [LangGraph Server API](../concepts/langgraph_server.md) success response count, error response count, and latency.
These metrics are displayed as charts in the Control Plane UI.
@@ -50,6 +50,15 @@ Runs in a LangGraph Server may be retried for specific failures (currently only
This section describes various features of the data plane.
### Data Region
!!! info "Only for Cloud SaaS"
Data regions are only applicable for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
Deployments can be created in 2 data regions: US and EU
The data region for a deployment is implied by the data region of the LangSmith organization where the deployment is created. Deployments and the underlying database for the deployments cannot be migrated between data regions.
### Autoscaling
[`Production` type](../concepts/langgraph_control_plane.md#deployment-types) deployments automatically scale up to 10 containers. Scaling is based on 3 metrics:
@@ -3,7 +3,7 @@
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
!!! info "Important"
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan.
The Self-Hosted Control Plane deployment option requires an [Enterprise](plans.md) plan.
## Requirements
@@ -8,7 +8,7 @@ search:
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
!!! info "Important"
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan.
The Self-Hosted Data Plane deployment option requires an [Enterprise](plans.md) plan.
## Requirements
@@ -19,7 +19,7 @@ The Standalone Container deployment option is the least restrictive model for de
!!! warning
LangGraph Platform should not be deployed in serverless environments.
LangGraph Platform should not be deployed in serverless environments. Scale to zero may cause task loss and scaling up will not work reliably.
## Architecture
+10 -10
View File
@@ -45,9 +45,9 @@ The first thing you do when you define a graph is define the `State` of the grap
### Schema
The main documented way to specify the schema of a graph is by using `TypedDict`. However, we also support [using a Pydantic BaseModel](../how-tos/graph-api.ipynb#use-pydantic-models-for-graph-state) as your graph state to add **default values** and additional data validation.
The main documented way to specify the schema of a graph is by using `TypedDict`. However, we also support [using a Pydantic BaseModel](../how-tos/graph-api.md#use-pydantic-models-for-graph-state) as your graph state to add **default values** and additional data validation.
By default, the graph will have the same input and output schemas. If you want to change this, you can also specify explicit input and output schemas directly. This is useful when you have a lot of keys, and some are explicitly for input and others for output. See the [guide here](../how-tos/graph-api.ipynb#define-input-and-output-schemas) for how to use.
By default, the graph will have the same input and output schemas. If you want to change this, you can also specify explicit input and output schemas directly. This is useful when you have a lot of keys, and some are explicitly for input and others for output. See the [guide here](../how-tos/graph-api.md#define-input-and-output-schemas) for how to use.
#### Multiple schemas
@@ -56,9 +56,9 @@ Typically, all graph nodes communicate with a single schema. This means that the
- Internal nodes can pass information that is not required in the graph's input / output.
- We may also want to use different input / output schemas for the graph. The output might, for example, only contain a single relevant output key.
It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`. See [this guide](../how-tos/graph-api.ipynb#pass-private-state-between-nodes) for more detail.
It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`. See [this guide](../how-tos/graph-api.md#pass-private-state-between-nodes) for more detail.
It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains _all_ keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this guide](../how-tos/graph-api.ipynb#define-input-and-output-schemas) for more detail.
It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains _all_ keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this guide](../how-tos/graph-api.md#define-input-and-output-schemas) for more detail.
Let's look at an example:
@@ -406,7 +406,7 @@ def my_node(state: State) -> Command[Literal["my_other_node"]]:
When returning `Command` in your node functions, you must add return type annotations with the list of node names the node is routing to, e.g. `Command[Literal["my_other_node"]]`. This is necessary for the graph rendering and tells LangGraph that `my_node` can navigate to `my_other_node`.
Check out this [how-to guide](../how-tos/graph-api.ipynb#combine-control-flow-and-state-updates-with-command) for an end-to-end example of how to use `Command`.
Check out this [how-to guide](../how-tos/graph-api.md#combine-control-flow-and-state-updates-with-command) for an end-to-end example of how to use `Command`.
### When should I use Command instead of conditional edges?
@@ -433,17 +433,17 @@ def my_node(state: State) -> Command[Literal["other_subgraph"]]:
!!! important "State updates with `Command.PARENT`"
When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](#schema), you **must** define a [reducer](#reducers) for the key you're updating in the parent graph state. See this [example](../how-tos/graph-api.ipynb#navigate-to-a-node-in-a-parent-graph).
When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](#schema), you **must** define a [reducer](#reducers) for the key you're updating in the parent graph state. See this [example](../how-tos/graph-api.md#navigate-to-a-node-in-a-parent-graph).
This is particularly useful when implementing [multi-agent handoffs](./multi_agent.md#handoffs).
Check out [this guide](../how-tos/graph-api.ipynb#navigate-to-a-node-in-a-parent-graph) for detail.
Check out [this guide](../how-tos/graph-api.md#navigate-to-a-node-in-a-parent-graph) for detail.
### Using inside tools
A common use case is updating graph state from inside a tool. For example, in a customer support application you might want to look up customer information based on their account number or ID in the beginning of the conversation.
Refer to [this guide](../how-tos/graph-api.ipynb#use-inside-tools) for detail.
Refer to [this guide](../how-tos/graph-api.md#use-inside-tools) for detail.
### Human-in-the-loop
@@ -489,7 +489,7 @@ def node_a(state, config):
...
```
See [this guide](../how-tos/graph-api.ipynb#add-runtime-configuration) for a full breakdown on configuration.
See [this guide](../how-tos/graph-api.md#add-runtime-configuration) for a full breakdown on configuration.
### Recursion Limit
@@ -503,4 +503,4 @@ Read [this how-to](https://langchain-ai.github.io/langgraph/how-tos/recursion-li
## Visualization
It's often nice to be able to visualize graphs, especially as they get more complex. LangGraph comes with several built-in ways to visualize graphs. See [this how-to guide](../how-tos/graph-api.ipynb#visualize-your-graph) for more info.
It's often nice to be able to visualize graphs, especially as they get more complex. LangGraph comes with several built-in ways to visualize graphs. See [this how-to guide](../how-tos/graph-api.md#visualize-your-graph) for more info.
+57
View File
@@ -0,0 +1,57 @@
# MCP
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that standardizes how applications provide tools and context to language models. LangGraph agents can use tools defined on MCP servers through the `langchain-mcp-adapters` library.
![MCP](../agents/assets/mcp.png)
Install the `langchain-mcp-adapters` library to use MCP tools in LangGraph:
```bash
pip install langchain-mcp-adapters
```
## Authenticate to an MCP server
You can set up [custom authentication middleware](../how-tos/auth/custom_auth.md) to authenticate a user with an MCP server to get access to user-scoped tools within your LangGraph Platform deployment.
!!! note
Custom authentication is a LangGraph Platform feature.
An example architecture for this flow:
```mermaid
sequenceDiagram
%% Actors
participant ClientApp as Client
participant AuthProv as Auth Provider
participant LangGraph as LangGraph Backend
participant SecretStore as Secret Store
participant MCPServer as MCP Server
%% Platform login / AuthN
ClientApp ->> AuthProv: 1. Login (username / password)
AuthProv -->> ClientApp: 2. Return token
ClientApp ->> LangGraph: 3. Request with token
Note over LangGraph: 4. Validate token (@auth.authenticate)
LangGraph -->> AuthProv: 5. Fetch user info
AuthProv -->> LangGraph: 6. Confirm validity
%% Fetch user tokens from secret store
LangGraph ->> SecretStore: 6a. Fetch user tokens
SecretStore -->> LangGraph: 6b. Return tokens
Note over LangGraph: 7. Apply access control (@auth.on.*)
%% MCP round-trip
Note over LangGraph: 8. Build MCP client with user token
LangGraph ->> MCPServer: 9. Call MCP tool (with header)
Note over MCPServer: 10. MCP validates header and runs tool
MCPServer -->> LangGraph: 11. Tool response
%% Return to caller
LangGraph -->> ClientApp: 12. Return resources / tool output
```
For more information, see [MCP endpoint in LangGraph Server](../concepts/server-mcp.md#use-user-scoped-mcp-tools-in-your-deployment).
+5 -5
View File
@@ -26,7 +26,7 @@ The primary benefits of using multi-agent systems are:
There are several ways to connect agents in a multi-agent system:
- **Network**: each agent can communicate with [every other agent](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/multi-agent-collaboration/). Any agent can decide which other agent to call next.
- **Supervisor**: each agent communicates with a single [supervisor](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/) agent. Supervisor agent makes decisions on which agent should be called next.
- **Supervisor**: each agent communicates with a single [supervisor](../tutorials/multi_agent/agent_supervisor.md) agent. Supervisor agent makes decisions on which agent should be called next.
- **Supervisor (tool-calling)**: this is a special case of supervisor architecture. Individual agents can be represented as tools. In this case, a supervisor agent uses a tool-calling LLM to decide which of the agent tools to call, as well as the arguments to pass to those agents.
- **Hierarchical**: you can define a multi-agent system with [a supervisor of supervisors](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/). This is a generalization of the supervisor architecture and allows for more complex control flows.
- **Custom multi-agent workflow**: each agent communicates with only a subset of agents. Parts of the flow are deterministic, and only some agents can decide which other agents to call next.
@@ -166,7 +166,7 @@ network = builder.compile()
### Supervisor
In this architecture, we define agents as nodes and add a supervisor node (LLM) that decides which agent nodes should be called next. We use [`Command`](./low_level.md#command) to route execution to the appropriate agent node based on supervisor's decision. This architecture also lends itself well to running multiple agents in parallel or using [map-reduce](../how-tos/graph-api.ipynb#map-reduce-and-the-send-api) pattern.
In this architecture, we define agents as nodes and add a supervisor node (LLM) that decides which agent nodes should be called next. We use [`Command`](./low_level.md#command) to route execution to the appropriate agent node based on supervisor's decision. This architecture also lends itself well to running multiple agents in parallel or using [map-reduce](../how-tos/graph-api.md#map-reduce-and-the-send-api) pattern.
```python
from typing import Literal
@@ -211,7 +211,7 @@ builder.add_edge(START, "supervisor")
supervisor = builder.compile()
```
Check out this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/) for an example of supervisor multi-agent architecture.
Check out this [tutorial](../tutorials/multi_agent/agent_supervisor.md) for an example of supervisor multi-agent architecture.
### Supervisor (tool-calling)
@@ -414,5 +414,5 @@ There are two high-level approaches to achieve that:
An agent might need to have a different state schema from the rest of the agents. For example, a search agent might only need to keep track of queries and retrieved documents. There are two ways to achieve this in LangGraph:
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, its important to [add input / output transformations](../how-tos/subgraph.ipynb#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
- Define agent node functions with a [private input state schema](../how-tos/graph-api.ipynb/#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, its important to [add input / output transformations](../how-tos/subgraph.md#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
- Define agent node functions with a [private input state schema](../how-tos/graph-api.md/#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
+2 -2
View File
@@ -5,7 +5,7 @@ search:
# Persistence
LangGraph has a built-in persistence layer, implemented through checkpointers. When you compile graph with a checkpointer, the checkpointer saves a `checkpoint` of the graph state at every super-step. Those checkpoints are saved to a `thread`, which can be accessed after graph execution. Because `threads` allow access to graph's state after execution, several powerful capabilities including human-in-the-loop, memory, time travel, and fault-tolerance are all possible. Below, we'll discuss each of these concepts in more detail.
LangGraph has a built-in persistence layer, implemented through checkpointers. When you compile a graph with a checkpointer, the checkpointer saves a `checkpoint` of the graph state at every super-step. Those checkpoints are saved to a `thread`, which can be accessed after graph execution. Because `threads` allow access to graph's state after execution, several powerful capabilities including human-in-the-loop, memory, time travel, and fault-tolerance are all possible. Below, we'll discuss each of these concepts in more detail.
![Checkpoints](img/persistence/checkpoints.jpg)
@@ -78,7 +78,7 @@ After we run the graph, we expect to see exactly 4 checkpoints:
* checkpoint with the outputs of `node_a` `{'foo': 'a', 'bar': ['a']}` and `node_b` as the next node to be executed
* checkpoint with the outputs of `node_b` `{'foo': 'b', 'bar': ['a', 'b']}` and no next nodes to be executed
Note that we `bar` channel values contain outputs from both nodes as we have a reducer for `bar` channel.
Note that the `bar` channel values contain outputs from both nodes as we have a reducer for `bar` channel.
### Get state
+111 -77
View File
@@ -8,8 +8,7 @@ hide:
# MCP endpoint in LangGraph Server
The **Model Context Protocol (MCP)** is an open protocol for describing tools and data sources in a model-agnostic format, enabling LLMs to discover
and use them via a structured API.
The [Model Context Protocol (MCP)](./mcp.md) is an open protocol for describing tools and data sources in a model-agnostic format, enabling LLMs to discover and use them via a structured API.
[LangGraph Server](./langgraph_server.md) implements MCP using the [Streamable HTTP transport](https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/transports/#streamable-http). This allows LangGraph **agents** to be exposed as **MCP tools**, making them usable with any MCP-compliant client supporting Streamable HTTP.
@@ -28,79 +27,6 @@ Install them with:
pip install "langgraph-api>=0.2.3" "langgraph-sdk>=0.1.61"
```
## Exposing an agent as MCP tool
When deployed, your agent will appear as a tool in the MCP endpoint
with this configuration:
- **Tool name**: The agent's name.
- **Tool description**: The agent's description.
- **Tool input schema**: The agent's input schema.
### Setting name and description
You can set the name and description of your agent in `langgraph.json`:
```json
{
"graphs": {
"my_agent": {
"path": "./my_agent/agent.py:graph",
"description": "A description of what the agent does"
}
},
"env": ".env"
}
```
After deployment, you can update the name and description using the LangGraph SDK.
### Schema
Define clear, minimal input and output schemas to avoid exposing unnecessary internal complexity to the LLM.
The default [MessagesState](./low_level.md#messagesstate) uses `AnyMessage`, which supports many message types but is too general for direct LLM exposure.
Instead, define **custom agents or workflows** that use explicitly typed input and output structures.
For example, a workflow answering documentation questions might look like this:
```python
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
# Define input schema
class InputState(TypedDict):
question: str
# Define output schema
class OutputState(TypedDict):
answer: str
# Combine input and output
class OverallState(InputState, OutputState):
pass
# Define the processing node
def answer_node(state: InputState):
# Replace with actual logic and do something useful
return {"answer": "bye", "question": state["question"]}
# Build the graph with explicit schemas
builder = StateGraph(OverallState, input_schema=InputState, output_schema=OutputState)
builder.add_node(answer_node)
builder.add_edge(START, "answer_node")
builder.add_edge("answer_node", END)
graph = builder.compile()
# Run the graph
print(graph.invoke({"question": "hi"}))
```
For more details, see the [low-level concepts guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#state).
## Usage overview
To enable MCP:
@@ -201,6 +127,114 @@ Use an MCP-compliant client to connect to the LangGraph server. The following ex
asyncio.run(main())
```
## Expose an agent as MCP tool
When deployed, your agent will appear as a tool in the MCP endpoint
with this configuration:
- **Tool name**: The agent's name.
- **Tool description**: The agent's description.
- **Tool input schema**: The agent's input schema.
### Setting name and description
You can set the name and description of your agent in `langgraph.json`:
```json
{
"graphs": {
"my_agent": {
"path": "./my_agent/agent.py:graph",
"description": "A description of what the agent does"
}
},
"env": ".env"
}
```
After deployment, you can update the name and description using the LangGraph SDK.
### Schema
Define clear, minimal input and output schemas to avoid exposing unnecessary internal complexity to the LLM.
The default [MessagesState](./low_level.md#messagesstate) uses `AnyMessage`, which supports many message types but is too general for direct LLM exposure.
Instead, define **custom agents or workflows** that use explicitly typed input and output structures.
For example, a workflow answering documentation questions might look like this:
```python
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
# Define input schema
class InputState(TypedDict):
question: str
# Define output schema
class OutputState(TypedDict):
answer: str
# Combine input and output
class OverallState(InputState, OutputState):
pass
# Define the processing node
def answer_node(state: InputState):
# Replace with actual logic and do something useful
return {"answer": "bye", "question": state["question"]}
# Build the graph with explicit schemas
builder = StateGraph(OverallState, input_schema=InputState, output_schema=OutputState)
builder.add_node(answer_node)
builder.add_edge(START, "answer_node")
builder.add_edge("answer_node", END)
graph = builder.compile()
# Run the graph
print(graph.invoke({"question": "hi"}))
```
For more details, see the [low-level concepts guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#state).
## Use user-scoped MCP tools in your deployment
!!! tip "Prerequisites"
You have added your own [custom auth middleware](https://langchain-ai.github.io/langgraph/how-tos/auth/custom_auth/) that populates the `langgraph_auth_user` object, making it accessible through configurable context for every node in your graph.
To make user-scoped tools available to your LangGraph Platform deployment, start with implementing a snippet like the following:
```python
from langchain_mcp_adapters.client import MultiServerMCPClient
def mcp_tools_node(state, config):
user = config["configurable"].get("langgraph_auth_user")
# e.g., user["github_token"], user["email"], etc.
client = MultiServerMCPClient({
"github": {
"transport": "streamable_http", # (1)
"url": "https://my-github-mcp-server/mcp", # (2)
"headers": {
"Authorization": f"Bearer {user['github_token']}"
}
}
})
tools = await client.get_tools() # (3)
# Your tool-calling logic here
tool_messages = ...
return {"messages": tool_messages}
```
1. MCP only supports adding headers to requests made to `streamable_http` and `sse` `transport` servers.
2. Your MCP server URL.
3. Get available tools from your MCP server.
_This can also be done by [rebuilding your graph at runtime](https://langchain-ai.github.io/langgraph/cloud/deployment/graph_rebuild/) to have a different configuration for a new run_
## Session behavior
@@ -210,7 +244,7 @@ The current LangGraph MCP implementation does not support sessions. Each `/mcp`
The `/mcp` endpoint uses the same authentication as the rest of the LangGraph API. Refer to the [authentication guide](./auth.md) for setup details.
## Disabling MCP
## Disable MCP
To disable the MCP endpoint, set `disable_mcp` to `true` in your `langgraph.json` configuration file:
@@ -222,4 +256,4 @@ To disable the MCP endpoint, set `disable_mcp` to `true` in your `langgraph.json
}
```
This will prevent the server from exposing the `/mcp` endpoint.
This will prevent the server from exposing the `/mcp` endpoint.
+2 -2
View File
@@ -12,7 +12,7 @@ Some reasons for using subgraphs are:
The main question when adding subgraphs is how the parent graph and subgraph communicate, i.e. how they pass the [state](./low_level.md#state) between each other during the graph execution. There are two scenarios:
* parent and subgraph have **shared state keys** in their state [schemas](./low_level.md#state). In this case, you can [include the subgraph as a node in the parent graph](../how-tos/subgraph.ipynb#shared-state-schemas)
* parent and subgraph have **shared state keys** in their state [schemas](./low_level.md#state). In this case, you can [include the subgraph as a node in the parent graph](../how-tos/subgraph.md#shared-state-schemas)
```python
from langgraph.graph import StateGraph, MessagesState, START
@@ -40,7 +40,7 @@ The main question when adding subgraphs is how the parent graph and subgraph com
graph.invoke({"messages": [{"role": "user", "content": "hi!"}]})
```
* parent graph and subgraph have **different schemas** (no shared state keys in their state [schemas](./low_level.md#state)). In this case, you have to [call the subgraph from inside a node in the parent graph](../how-tos/subgraph.ipynb#different-state-schemas): this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph
* parent graph and subgraph have **different schemas** (no shared state keys in their state [schemas](./low_level.md#state)). In this case, you have to [call the subgraph from inside a node in the parent graph](../how-tos/subgraph.md#different-state-schemas): this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph
```python
from typing_extensions import TypedDict, Annotated
+1 -1
View File
@@ -64,7 +64,7 @@ To create a new app from a template, use the `langgraph new` command.
=== "JS"
```bash
npx @langchain/langgraph-cli new
npm create langgraph@latest
```
## Next Steps
+23
View File
@@ -0,0 +1,23 @@
# Examples
The pages in this section provide end-to-end examples for the following topics:
## General
- [Template Applications](../concepts/template_applications.md): Create a LangGraph application from a template.
- [Agentic RAG](../tutorials/rag/langgraph_agentic_rag.md): Build a retrieval agent that can decide when to use a retriever tool.
- [Agent Supervisor](../tutorials/multi_agent/agent_supervisor.md): Build a supervisor agent that can manage a team of agents.
- [SQL agent](../tutorials/sql/sql-agent.md): Build a SQL agent that can execute SQL queries and return the results.
- [Prebuilt chat UI](../agents/ui.md): Use a prebuilt chat UI to interact with any LangGraph agent.
- [Graph runs in LangSmith](../how-tos/run-id-langsmith.md): Use LangSmith to track and analyze graph runs.
## LangGraph Platform
- [Set up custom authentication](../tutorials/auth/getting_started.md): Set up custom authentication for your LangGraph application.
- [Make conversations private](../tutorials/auth/resource_auth.md): Make conversations private by using resource-based authentication.
- [Connect an authentication provider](../tutorials/auth/add_auth_server.md): Connect an authentication provider to your LangGraph application.
- [Rebuild graph at runtime](../cloud/deployment/graph_rebuild.md): Rebuild a graph at runtime.
- [Use RemoteGraph](../how-tos/use-remote-graph.md): Use RemoteGraph to deploy your LangGraph application to a remote server.
- [Deploy CrewAI, AutoGen, and other frameworks](../how-tos/autogen-integration.md): Deploy CrewAI, AutoGen, and other frameworks with LangGraph.
- [Integrate LangGraph into a React app](../cloud/how-tos/use_stream_react.md)
- [Implement Generative User Interfaces with LangGraph](../cloud/how-tos/generative_ui_react.md)
+41
View File
@@ -0,0 +1,41 @@
# Guides
The pages in this section provide a conceptual overview and how-tos for the following topics:
## LangGraph APIs
- [Graph API](../concepts/low_level.md): Use the Graph API to define workflows using a graph paradigm.
- [Functional API](../concepts/functional_api.md): Use Functional API to build workflows using a functional paradigm without thinking about the graph structure.
- [Runtime](../concepts/pregel.md): Pregel implements LangGraph's runtime, managing the execution of LangGraph applications.
## Core capabilities
These capabilities are available in both LangGraph OSS and the LangGraph Platform.
- [Streaming](../concepts/streaming.md): Stream outputs from a LangGraph graph.
- [Persistence](../concepts/persistence.md): Persist the state of a LangGraph graph.
- [Durable execution](../concepts/durable_execution.md): Save progress at key points in the graph execution.
- [Memory](../concepts/memory.md): Remember information about previous interactions.
- [Context](../agents/context.md): Pass outside data to a LangGraph graph to provide context for the graph execution.
- [Models](../agents/models.md): Integrate various LLMs into your LangGraph application.
- [Tools](../concepts/tools.md): Interface directly with external systems.
- [Human-in-the-loop](../concepts/human_in_the_loop.md): Enable human intervention at any point in a workflow.
- [Breakpoints](../concepts/breakpoints.md): Pause the execution of a LangGraph graph at a specific point.
- [Time travel](../concepts/time-travel.md): Travel back in time to a specific point in the execution of a LangGraph graph.
- [Subgraphs](../concepts/subgraphs.md): Build modular graphs.
- [Multi-agent](../concepts/multi_agent.md): Break down a complex workflow into multiple agents.
- [MCP](../concepts/mcp.md): Use MCP servers in a LangGraph graph.
- [Evaluation](../agents/evals.md): Use LangSmith to evaluate your graph's performance.
## Platform-only capabilities
These capabilities are only available in [LangGraph Platform](../concepts/langgraph_platform.md).
- [Authentication and access control](../concepts/auth.md): Authenticate and authorize users to access a Langraph graph.
- [Assistants](../concepts/assistants.md): Build assistants that can be used to interact with a LangGraph graph.
- [Double-texting](../concepts/double_texting.md): Handle double-texting (consecutive messages before a first response is returned) in a LangGraph graph.
- [Webhooks](../cloud/concepts/webhooks.md): Send webhooks to a LangGraph graph.
- [Cron jobs](../cloud/concepts/cron_jobs.md): Schedule jobs to run at a specific time.
- [Server customization](../how-tos/http/custom_lifespan.md): Customize the server that runs a LangGraph graph.
- [Data management](../cloud/concepts/data_storage_and_privacy.md): Manage data in a LangGraph graph.
- [Deployment](../concepts/deployment_options.md): Deploy a LangGraph graph to a server.
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

+137 -128
View File
@@ -1,138 +1,147 @@
# Add custom authentication
!!! tip "Prerequisites"
This guide assumes familiarity with the following concepts:
* [**Authentication & Access Control**](../../concepts/auth.md)
* [**LangGraph Platform**](../../concepts/langgraph_platform.md)
For a more guided walkthrough, see [**setting up custom authentication**](../../tutorials/auth/getting_started.md) tutorial.
???+ note "Support by deployment type"
Custom auth is supported for all deployments in the **managed LangGraph Platform**, as well as **Enterprise** self-hosted plans. It is not supported for **Lite** self-hosted plans.
This guide shows how to add custom authentication to your LangGraph Platform application. This guide applies to both LangGraph Platform and self-hosted deployments. It does not apply to isolated usage of the LangGraph open source library in your own custom server.
## 1. Implement authentication
!!! note
Custom auth is supported for all **managed LangGraph Platform** deployments, as well as **Enterprise** self-hosted plans. It is not supported for **Lite** self-hosted plans.
## Add custom authentication to your deployment
To leverage custom authentication and access user-level metadata in your deployments, set up custom authentication to automatically populate the `config["configurable"]["langgraph_auth_user"]` object through a custom authentication handler. You can then access this object in your graph with the `langgraph_auth_user` key to [allow an agent to perform authenticated actions on behalf of the user](#enable-agent-authentication).
1. Implement authentication:
!!! note
Without a custom `@auth.authenticate` handler, LangGraph sees only the API-key owner (usually the developer), so requests arent scoped to individual end-users. To propagate custom tokens, you must implement your own handler.
```python
from langgraph_sdk import Auth
import requests
auth = Auth()
def is_valid_key(api_key: str) -> bool:
is_valid = # your API key validation logic
return is_valid
@auth.authenticate # (1)!
async def authenticate(headers: dict) -> Auth.types.MinimalUserDict:
api_key = headers.get("x-api-key")
if not api_key or not is_valid_key(api_key):
raise Auth.exceptions.HTTPException(status_code=401, detail="Invalid API key")
# Fetch user-specific tokens from your secret store
user_tokens = await fetch_user_tokens(api_key)
return { # (2)!
"identity": api_key, # fetch user ID from LangSmith
"github_token" : user_tokens.github_token
"jira_token" : user_tokens.jira_token
# ... custom fields/secrets here
}
```
1. This handler receives the request (headers, etc.), validates the user, and returns a dictionary with at least an identity field.
2. You can add any custom fields you want (e.g., OAuth tokens, roles, org IDs, etc.).
2. In your `langgraph.json`, add the path to your auth file:
```json hl_lines="7-9"
{
"dependencies": ["."],
"graphs": {
"agent": "./agent.py:graph"
},
"env": ".env",
"auth": {
"path": "./auth.py:my_auth"
}
}
```
3. Once you've set up authentication in your server, requests must include the required authorization information based on your chosen scheme. Assuming you are using JWT token authentication, you could access your deployments using any of the following methods:
=== "Python Client"
```python
from langgraph_sdk import get_client
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
client = get_client(
url="http://localhost:2024",
headers={"Authorization": f"Bearer {my_token}"}
)
threads = await client.threads.search()
```
=== "Python RemoteGraph"
```python
from langgraph.pregel.remote import RemoteGraph
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
remote_graph = RemoteGraph(
"agent",
url="http://localhost:2024",
headers={"Authorization": f"Bearer {my_token}"}
)
threads = await remote_graph.ainvoke(...)
```
=== "JavaScript Client"
```javascript
import { Client } from "@langchain/langgraph-sdk";
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
const client = new Client({
apiUrl: "http://localhost:2024",
defaultHeaders: { Authorization: `Bearer ${my_token}` },
});
const threads = await client.threads.search();
```
=== "JavaScript RemoteGraph"
```javascript
import { RemoteGraph } from "@langchain/langgraph/remote";
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
const remoteGraph = new RemoteGraph({
graphId: "agent",
url: "http://localhost:2024",
headers: { Authorization: `Bearer ${my_token}` },
});
const threads = await remoteGraph.invoke(...);
```
=== "CURL"
```bash
curl -H "Authorization: Bearer ${your-token}" http://localhost:2024/threads
```
## Enable agent authentication
After [authentication](#add-custom-authentication-to-your-deployment), the platform creates a special configuration object (`config`) that is passed to LangGraph Platform deployment. This object contains information about the current user, including any custom fields you return from your `@auth.authenticate` handler.
To allow an agent to perform authenticated actions on behalf of the user, access this object in your graph with the `langgraph_auth_user` key:
```python
from langgraph_sdk import Auth
my_auth = Auth()
@my_auth.authenticate
async def authenticate(authorization: str) -> str:
token = authorization.split(" ", 1)[-1] # "Bearer <token>"
try:
# Verify token with your auth provider
user_id = await verify_token(token)
return user_id
except Exception:
raise Auth.exceptions.HTTPException(
status_code=401,
detail="Invalid token"
)
# Add authorization rules to actually control access to resources
@my_auth.on
async def add_owner(
ctx: Auth.types.AuthContext,
value: dict,
):
"""Add owner to resource metadata and filter by owner."""
filters = {"owner": ctx.user.identity}
metadata = value.setdefault("metadata", {})
metadata.update(filters)
return filters
# Assumes you organize information in store like (user_id, resource_type, resource_id)
@my_auth.on.store()
async def authorize_store(ctx: Auth.types.AuthContext, value: dict):
namespace: tuple = value["namespace"]
assert namespace[0] == ctx.user.identity, "Not authorized"
def my_node(state, config):
user_config = config["configurable"].get("langgraph_auth_user")
# token was resolved during the @auth.authenticate function
token = user_config.get("github_token","")
...
```
## 2. Update configuration
!!! note
Fetch user credentials from a secure secret store. Storing secrets in graph state is not recommended.
In your `langgraph.json`, add the path to your auth file:
## Learn more
```json hl_lines="7-9"
{
"dependencies": ["."],
"graphs": {
"agent": "./agent.py:graph"
},
"env": ".env",
"auth": {
"path": "./auth.py:my_auth"
}
}
```
## 3. Connect from the client
Once you've set up authentication in your server, requests must include the required authorization information based on your chosen scheme.
Assuming you are using JWT token authentication, you could access your deployments using any of the following methods:
=== "Python Client"
```python
from langgraph_sdk import get_client
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
client = get_client(
url="http://localhost:2024",
headers={"Authorization": f"Bearer {my_token}"}
)
threads = await client.threads.search()
```
=== "Python RemoteGraph"
```python
from langgraph.pregel.remote import RemoteGraph
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
remote_graph = RemoteGraph(
"agent",
url="http://localhost:2024",
headers={"Authorization": f"Bearer {my_token}"}
)
threads = await remote_graph.ainvoke(...)
```
=== "JavaScript Client"
```javascript
import { Client } from "@langchain/langgraph-sdk";
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
const client = new Client({
apiUrl: "http://localhost:2024",
defaultHeaders: { Authorization: `Bearer ${my_token}` },
});
const threads = await client.threads.search();
```
=== "JavaScript RemoteGraph"
```javascript
import { RemoteGraph } from "@langchain/langgraph/remote";
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
const remoteGraph = new RemoteGraph({
graphId: "agent",
url: "http://localhost:2024",
headers: { Authorization: `Bearer ${my_token}` },
});
const threads = await remoteGraph.invoke(...);
```
=== "CURL"
```bash
curl -H "Authorization: Bearer ${your-token}" http://localhost:2024/threads
```
* [Authentication & Access Control](../../concepts/auth.md)
* [LangGraph Platform](../../concepts/langgraph_platform.md)
* [Setting up custom authentication tutorial](../../tutorials/auth/getting_started.md)
File diff suppressed because one or more lines are too long
+321
View File
@@ -0,0 +1,321 @@
# How to integrate LangGraph with AutoGen, CrewAI, and other frameworks
This guide shows how to integrate AutoGen agents with LangGraph to leverage features like persistence, streaming, and memory, and then deploy the integrated solution to LangGraph Platform for scalable production use. In this guide we show how to build a LangGraph chatbot that integrates with AutoGen, but you can follow the same approach with other frameworks.
Integrating AutoGen with LangGraph provides several benefits:
- Enhanced features: Add [persistence](../concepts/persistence.md), [streaming](../concepts/streaming.md), [short and long-term memory](../concepts/memory.md) and more to your AutoGen agents.
- Multi-agent systems: Build [multi-agent systems](../concepts/multi_agent.md) where individual agents are built with different frameworks.
- Production deployment: Deploy your integrated solution to [LangGraph Platform](../concepts/langgraph_platform.md) for scalable production use.
## Prerequisites
- Python 3.9+
- Autogen: `pip install autogen`
- LangGraph: `pip install langgraph`
- OpenAI API key
## Setup
Set your your environment:
```python
import getpass
import os
def _set_env(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
_set_env("OPENAI_API_KEY")
```
## 1. Define AutoGen agent
Create an AutoGen agent that can execute code. This example is adapted from AutoGen's [official tutorials](https://github.com/microsoft/autogen/blob/0.2/notebook/agentchat_web_info.ipynb):
```python
import autogen
import os
config_list = [{"model": "gpt-4o", "api_key": os.environ["OPENAI_API_KEY"]}]
llm_config = {
"timeout": 600,
"cache_seed": 42,
"config_list": config_list,
"temperature": 0,
}
autogen_agent = autogen.AssistantAgent(
name="assistant",
llm_config=llm_config,
)
user_proxy = autogen.UserProxyAgent(
name="user_proxy",
human_input_mode="NEVER",
max_consecutive_auto_reply=10,
is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE"),
code_execution_config={
"work_dir": "web",
"use_docker": False,
}, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.
llm_config=llm_config,
system_message="Reply TERMINATE if the task has been solved at full satisfaction. Otherwise, reply CONTINUE, or the reason why the task is not solved yet.",
)
```
## 2. Create the graph
We will now create a LangGraph chatbot graph that calls AutoGen agent.
```python
from langchain_core.messages import convert_to_openai_messages
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.checkpoint.memory import MemorySaver
def call_autogen_agent(state: MessagesState):
# Convert LangGraph messages to OpenAI format for AutoGen
messages = convert_to_openai_messages(state["messages"])
# Get the last user message
last_message = messages[-1]
# Pass previous message history as context (excluding the last message)
carryover = messages[:-1] if len(messages) > 1 else []
# Initiate chat with AutoGen
response = user_proxy.initiate_chat(
autogen_agent,
message=last_message,
carryover=carryover
)
# Extract the final response from the agent
final_content = response.chat_history[-1]["content"]
# Return the response in LangGraph format
return {"messages": {"role": "assistant", "content": final_content}}
# Create the graph with memory for persistence
checkpointer = MemorySaver()
# Build the graph
builder = StateGraph(MessagesState)
builder.add_node("autogen", call_autogen_agent)
builder.add_edge(START, "autogen")
# Compile with checkpointer for persistence
graph = builder.compile(checkpointer=checkpointer)
```
```python
from IPython.display import display, Image
display(Image(graph.get_graph().draw_mermaid_png()))
```
![Graph](./assets/autogen-output.png)
## 3. Test the graph locally
Before deploying to LangGraph Platform, you can test the graph locally:
```python
# pass the thread ID to persist agent outputs for future interactions
# highlight-next-line
config = {"configurable": {"thread_id": "1"}}
for chunk in graph.stream(
{
"messages": [
{
"role": "user",
"content": "Find numbers between 10 and 30 in fibonacci sequence",
}
]
},
# highlight-next-line
config,
):
print(chunk)
```
**Output:**
```
user_proxy (to assistant):
Find numbers between 10 and 30 in fibonacci sequence
--------------------------------------------------------------------------------
assistant (to user_proxy):
To find numbers between 10 and 30 in the Fibonacci sequence, we can generate the Fibonacci sequence and check which numbers fall within this range. Here's a plan:
1. Generate Fibonacci numbers starting from 0.
2. Continue generating until the numbers exceed 30.
3. Collect and print the numbers that are between 10 and 30.
...
```
Since we're leveraging LangGraph's [persistence](https://langchain-ai.github.io/langgraph/concepts/persistence/) features we can now continue the conversation using the same thread ID -- LangGraph will automatically pass previous history to the AutoGen agent:
```python
for chunk in graph.stream(
{
"messages": [
{
"role": "user",
"content": "Multiply the last number by 3",
}
]
},
# highlight-next-line
config,
):
print(chunk)
```
**Output:**
```
user_proxy (to assistant):
Multiply the last number by 3
Context:
Find numbers between 10 and 30 in fibonacci sequence
The Fibonacci numbers between 10 and 30 are 13 and 21.
These numbers are part of the Fibonacci sequence, which is generated by adding the two preceding numbers to get the next number, starting from 0 and 1.
The sequence goes: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
As you can see, 13 and 21 are the only numbers in this sequence that fall between 10 and 30.
TERMINATE
--------------------------------------------------------------------------------
assistant (to user_proxy):
The last number in the Fibonacci sequence between 10 and 30 is 21. Multiplying 21 by 3 gives:
21 * 3 = 63
TERMINATE
--------------------------------------------------------------------------------
{'call_autogen_agent': {'messages': {'role': 'assistant', 'content': 'The last number in the Fibonacci sequence between 10 and 30 is 21. Multiplying 21 by 3 gives:\n\n21 * 3 = 63\n\nTERMINATE'}}}
```
## 4. Prepare for deployment
To deploy to LangGraph Platform, create a file structure like the following:
```
my-autogen-agent/
├── agent.py # Your main agent code
├── requirements.txt # Python dependencies
└── langgraph.json # LangGraph configuration
```
=== "agent.py"
```python
import os
import autogen
from langchain_core.messages import convert_to_openai_messages
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.checkpoint.memory import MemorySaver
# AutoGen configuration
config_list = [{"model": "gpt-4o", "api_key": os.environ["OPENAI_API_KEY"]}]
llm_config = {
"timeout": 600,
"cache_seed": 42,
"config_list": config_list,
"temperature": 0,
}
# Create AutoGen agents
autogen_agent = autogen.AssistantAgent(
name="assistant",
llm_config=llm_config,
)
user_proxy = autogen.UserProxyAgent(
name="user_proxy",
human_input_mode="NEVER",
max_consecutive_auto_reply=10,
is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE"),
code_execution_config={
"work_dir": "/tmp/autogen_work",
"use_docker": False,
},
llm_config=llm_config,
system_message="Reply TERMINATE if the task has been solved at full satisfaction.",
)
def call_autogen_agent(state: MessagesState):
"""Node function that calls the AutoGen agent"""
messages = convert_to_openai_messages(state["messages"])
last_message = messages[-1]
carryover = messages[:-1] if len(messages) > 1 else []
response = user_proxy.initiate_chat(
autogen_agent,
message=last_message,
carryover=carryover
)
final_content = response.chat_history[-1]["content"]
return {"messages": {"role": "assistant", "content": final_content}}
# Create and compile the graph
def create_graph():
checkpointer = MemorySaver()
builder = StateGraph(MessagesState)
builder.add_node("autogen", call_autogen_agent)
builder.add_edge(START, "autogen")
return builder.compile(checkpointer=checkpointer)
# Export the graph for LangGraph Platform
graph = create_graph()
```
=== "requirements.txt"
```
langgraph>=0.1.0
pyautogen>=0.2.0
langchain-core>=0.1.0
langchain-openai>=0.0.5
```
=== "langgraph.json"
```json
{
"dependencies": ["."],
"graphs": {
"autogen_agent": "./agent.py:graph"
},
"env": ".env"
}
```
## 5. Deploy to LangGraph Platform
Deploy the graph with the LangGraph Platform CLI:
```
pip install -U langgraph-cli
```
```
langgraph deploy --config langgraph.json
```
@@ -1,171 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8381b6e0-29a6-48c5-b451-5d2549351249",
"metadata": {},
"source": [
"# How to use LangGraph Platform to deploy CrewAI, AutoGen, and other frameworks\n",
"\n",
"[LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) provides infrastructure for deploying agents. This integrates seamlessly with LangGraph, but can also work with other frameworks. The way to make this work is to wrap the agent in a single LangGraph node, and have that be the entire graph.\n",
"\n",
"Doing so will allow you to deploy to LangGraph Platform, and allows you to get a lot of the [benefits](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/). You get horizontally scalable infrastructure, a task queue to handle bursty operations, a persistence layer to power short term memory, and long term memory support.\n",
"\n",
"In this guide we show how to do this with an AutoGen agent, but this method should work for agents defined in other frameworks like CrewAI, LlamaIndex, and others as well."
]
},
{
"cell_type": "markdown",
"id": "1113cb16-b538-448c-924c-85731ce96ebd",
"metadata": {},
"source": [
"## Setup"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "f05993fa-9d03-4f45-bc13-0a8d87260d86",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"%pip install autogen langgraph"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f4e0ca12-1714-4776-a30a-9527e519799b",
"metadata": {},
"outputs": [],
"source": [
"import getpass\n",
"import os\n",
"\n",
"\n",
"def _set_env(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
"\n",
"\n",
"_set_env(\"OPENAI_API_KEY\")"
]
},
{
"cell_type": "markdown",
"id": "1926bbc3-6b06-41e0-9604-860a2bbf8fa3",
"metadata": {},
"source": [
"## Define autogen agent\n",
"\n",
"Here we define our AutoGen agent. From https://github.com/microsoft/autogen/blob/0.2/notebook/agentchat_web_info.ipynb"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4a14dc7-d565-4207-8788-525f85b9fb27",
"metadata": {},
"outputs": [],
"source": [
"import autogen\n",
"import os\n",
"\n",
"config_list = [{\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}]\n",
"\n",
"llm_config = {\n",
" \"timeout\": 600,\n",
" \"cache_seed\": 42,\n",
" \"config_list\": config_list,\n",
" \"temperature\": 0,\n",
"}\n",
"\n",
"autogen_agent = autogen.AssistantAgent(\n",
" name=\"assistant\",\n",
" llm_config=llm_config,\n",
")\n",
"\n",
"user_proxy = autogen.UserProxyAgent(\n",
" name=\"user_proxy\",\n",
" human_input_mode=\"NEVER\",\n",
" max_consecutive_auto_reply=10,\n",
" is_termination_msg=lambda x: x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\"),\n",
" code_execution_config={\n",
" \"work_dir\": \"web\",\n",
" \"use_docker\": False,\n",
" }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n",
" llm_config=llm_config,\n",
" system_message=\"Reply TERMINATE if the task has been solved at full satisfaction. Otherwise, reply CONTINUE, or the reason why the task is not solved yet.\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "b1170836-f23e-4e4c-ab83-ce791cd7fbd2",
"metadata": {},
"source": [
"## Wrap in LangGraph\n",
"\n",
"We now wrap the AutoGen agent in a single LangGraph node, and make that the entire graph.\n",
"The main thing this involves is defining an Input and Output schema for the node, which you would need to do if deploying this manually, so it's no extra work"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "7b417c16-ff4e-4d5c-a9a9-0aaeeef6ede5",
"metadata": {},
"outputs": [],
"source": [
"from langgraph.graph import StateGraph, MessagesState\n",
"\n",
"\n",
"def call_autogen_agent(state: MessagesState):\n",
" last_message = state[\"messages\"][-1]\n",
" response = user_proxy.initiate_chat(autogen_agent, message=last_message.content)\n",
" # get the final response from the agent\n",
" content = response.chat_history[-1][\"content\"]\n",
" return {\"messages\": {\"role\": \"assistant\", \"content\": content}}\n",
"\n",
"\n",
"graph = StateGraph(MessagesState)\n",
"graph.add_node(call_autogen_agent)\n",
"graph.set_entry_point(\"call_autogen_agent\")\n",
"graph = graph.compile()"
]
},
{
"cell_type": "markdown",
"id": "f6a18377-ac29-478f-a76a-b213f1a3c85d",
"metadata": {},
"source": [
"## Deploy with LangGraph Platform\n",
"\n",
"You can now deploy this as you normally would with LangGraph Platform. See [these instructions](https://langchain-ai.github.io/langgraph/concepts/deployment_options/) for more details."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -1351,7 +1351,7 @@ The problem with trimming or removing messages, as shown above, is that you may
```python
from langchain_anthropic import ChatAnthropic
from langmem.short_term import SummarizationNode
from langmem.short_term import SummarizationNode, RunningSummary
from langchain_core.messages.utils import count_tokens_approximately
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
@@ -1372,7 +1372,7 @@ The problem with trimming or removing messages, as shown above, is that you may
# NOTE: we're adding this key to keep track of previous summary information
# to make sure we're not summarizing on every LLM call
# highlight-next-line
context: dict[str, Any] # (2)!
context: dict[str, RunningSummary] # (2)!
checkpointer = InMemorySaver() # (3)!
@@ -1447,18 +1447,18 @@ The problem with trimming or removing messages, as shown above, is that you may
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.checkpoint.memory import InMemorySaver
# highlight-next-line
from langmem.short_term import SummarizationNode
from langmem.short_term import SummarizationNode, RunningSummary
model = init_chat_model("anthropic:claude-3-7-sonnet-latest")
summarization_model = model.bind(max_tokens=128)
class State(MessagesState):
# highlight-next-line
context: dict[str, Any] # (1)!
context: dict[str, RunningSummary] # (1)!
class LLMInputState(TypedDict): # (2)!
summarized_messages: list[AnyMessage]
context: dict[str, Any]
context: dict[str, RunningSummary]
# highlight-next-line
summarization_node = SummarizationNode(
-657
View File
@@ -1,657 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "34d3d54e-9a2b-481e-bccd-74aca7a53f9a",
"metadata": {},
"source": [
"# Build multi-agent systems"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "3f0b4f70-f14e-4026-82c0-874786789ee8",
"metadata": {},
"source": [
"A single agent might struggle if it needs to specialize in multiple domains or manage many tools. To tackle this, you can break your agent into smaller, independent agents and composing them into a [multi-agent system](../../concepts/multi_agent).\n",
"\n",
"In multi-agent systems, agents need to communicate between each other. They do so via [handoffs](#handoffs) — a primitive that describes which agent to hand control to and the payload to send to that agent.\n",
"\n",
"This guide covers the following:\n",
"\n",
"* implementing [handoffs](#handoffs) between agents\n",
"* using handoffs and the prebuilt [agent](../../agents/agents) to [build a custom multi-agent system](#build-a-multi-agent-system)\n",
"\n",
"To get started with building multi-agent systems, check out LangGraph [prebuilt implementations](#prebuilt-implementations) of two of the most popular multi-agent architectures — [supervisor](../../agents/multi-agent#supervisor) and [swarm](../../agents/multi-agent#swarm)."
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "7d43e110-16fc-4899-97f1-015d5b804b87",
"metadata": {},
"source": [
"## Handoffs\n",
"\n",
"To set up communication between the agents in a multi-agent system you can use [**handoffs**](../../concepts/multi_agent#handoffs) — a pattern where one agent *hands off* control to another. Handoffs allow you to specify:\n",
"\n",
"- **destination**: target agent to navigate to (e.g., name of the LangGraph node to go to)\n",
"- **payload**: information to pass to that agent (e.g., state update)\n",
"\n",
"### Create handoffs\n",
"\n",
"To implement handoffs, you can return [`Command`](../command) objects from your agent nodes or tools:\n",
"\n",
"```python\n",
"from typing import Annotated\n",
"from langchain_core.tools import tool, InjectedToolCallId\n",
"from langgraph.prebuilt import create_react_agent, InjectedState\n",
"from langgraph.graph import StateGraph, START, MessagesState\n",
"from langgraph.types import Command\n",
"\n",
"def create_handoff_tool(*, agent_name: str, description: str | None = None):\n",
" name = f\"transfer_to_{agent_name}\"\n",
" description = description or f\"Transfer to {agent_name}\"\n",
"\n",
" @tool(name, description=description)\n",
" def handoff_tool(\n",
" # highlight-next-line\n",
" state: Annotated[MessagesState, InjectedState], # (1)!\n",
" # highlight-next-line\n",
" tool_call_id: Annotated[str, InjectedToolCallId],\n",
" ) -> Command:\n",
" tool_message = {\n",
" \"role\": \"tool\",\n",
" \"content\": f\"Successfully transferred to {agent_name}\",\n",
" \"name\": name,\n",
" \"tool_call_id\": tool_call_id,\n",
" }\n",
" return Command( # (2)!\n",
" # highlight-next-line\n",
" goto=agent_name, # (3)!\n",
" # highlight-next-line\n",
" update={\"messages\": state[\"messages\"] + [tool_message]}, # (4)!\n",
" # highlight-next-line\n",
" graph=Command.PARENT, # (5)!\n",
" )\n",
" return handoff_tool\n",
"```\n",
"\n",
"1. Access the [state](../../concepts/low_level#state) of the agent that is calling the handoff tool using the [InjectedState][langgraph.prebuilt.InjectedState] annotation. See [this guide](../tool-calling/#read-state) for more information.\n",
"2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.\n",
"3. Name of the agent or node to hand off to.\n",
"4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.\n",
"5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.\n",
"\n",
"!!! tip\n",
"\n",
" If you want to use tools that return `Command`, you can either use prebuilt [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] / [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] components, or implement your own tool-executing node that collects `Command` objects returned by the tools and returns a list of them, e.g.:\n",
" \n",
" ```python\n",
" def call_tools(state):\n",
" ...\n",
" commands = [tools_by_name[tool_call[\"name\"]].invoke(tool_call) for tool_call in tool_calls]\n",
" return commands\n",
" ```\n",
"\n",
"!!! Important\n",
"\n",
" This handoff implementation assumes that:\n",
" \n",
" - each agent receives overall message history (across all agents) in the multi-agent system as its input. If you want more control over agent inputs, see [this section](#control-agent-inputs)\n",
" - each agent outputs its internal messages history to the overall message history of the multi-agent system. If you want more control over **how agent outputs are added**, wrap the agent in a separate node function:\n",
"\n",
" ```python\n",
" def call_hotel_assistant(state):\n",
" # return agent's final response,\n",
" # excluding inner monologue\n",
" response = hotel_assistant.invoke(state)\n",
" # highlight-next-line\n",
" return {\"messages\": response[\"messages\"][-1]}\n",
" ```"
]
},
{
"cell_type": "markdown",
"id": "3956f12d-285a-4799-a0a5-db13def58a15",
"metadata": {},
"source": [
"### Control agent inputs\n",
"\n",
"You can use the [`Send()`][langgraph.types.Send] primitive to directly send data to the worker agents during the handoff. For example, you can request that the calling agent populate a task description for the next agent:\n",
"\n",
"```python\n",
"\n",
"from typing import Annotated\n",
"from langchain_core.tools import tool, InjectedToolCallId\n",
"from langgraph.prebuilt import InjectedState\n",
"from langgraph.graph import StateGraph, START, MessagesState\n",
"# highlight-next-line\n",
"from langgraph.types import Command, Send\n",
"\n",
"def create_task_description_handoff_tool(\n",
" *, agent_name: str, description: str | None = None\n",
"):\n",
" name = f\"transfer_to_{agent_name}\"\n",
" description = description or f\"Ask {agent_name} for help.\"\n",
"\n",
" @tool(name, description=description)\n",
" def handoff_tool(\n",
" # this is populated by the calling agent\n",
" task_description: Annotated[\n",
" str,\n",
" \"Description of what the next agent should do, including all of the relevant context.\",\n",
" ],\n",
" # these parameters are ignored by the LLM\n",
" state: Annotated[MessagesState, InjectedState],\n",
" ) -> Command:\n",
" task_description_message = {\"role\": \"user\", \"content\": task_description}\n",
" agent_input = {**state, \"messages\": [task_description_message]}\n",
" return Command(\n",
" # highlight-next-line\n",
" goto=[Send(agent_name, agent_input)],\n",
" graph=Command.PARENT,\n",
" )\n",
"\n",
" return handoff_tool\n",
"```\n",
"\n",
"See the multi-agent [supervisor](../tutorials/agent_supervisor.ipynb#4-create-delegation-tasks) tutorial for a full example of using [`Send()`][langgraph.types.Send] in handoffs."
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "21511f57-7bf3-4223-9a17-ce9fc84c40ab",
"metadata": {},
"source": [
"## Build a multi-agent system\n",
"\n",
"You can use handoffs in any agents built with LangGraph. We recommend using the prebuilt [agent](../../agents/overview) or [`ToolNode`](../tool-calling#use-prebuilt-toolnode), as they natively support handoffs tools returning `Command`. Below is an example of how you can implement a multi-agent system for booking travel using handoffs:\n",
"\n",
"```python\n",
"from langgraph.prebuilt import create_react_agent\n",
"from langgraph.graph import StateGraph, START, MessagesState\n",
"\n",
"def create_handoff_tool(*, agent_name: str, description: str | None = None):\n",
" # same implementation as above\n",
" ...\n",
" return Command(...)\n",
"\n",
"# Handoffs\n",
"transfer_to_hotel_assistant = create_handoff_tool(agent_name=\"hotel_assistant\")\n",
"transfer_to_flight_assistant = create_handoff_tool(agent_name=\"flight_assistant\")\n",
"\n",
"# Define agents\n",
"flight_assistant = create_react_agent(\n",
" model=\"anthropic:claude-3-5-sonnet-latest\",\n",
" # highlight-next-line\n",
" tools=[..., transfer_to_hotel_assistant],\n",
" # highlight-next-line\n",
" name=\"flight_assistant\"\n",
")\n",
"hotel_assistant = create_react_agent(\n",
" model=\"anthropic:claude-3-5-sonnet-latest\",\n",
" # highlight-next-line\n",
" tools=[..., transfer_to_flight_assistant],\n",
" # highlight-next-line\n",
" name=\"hotel_assistant\"\n",
")\n",
"\n",
"# Define multi-agent graph\n",
"multi_agent_graph = (\n",
" StateGraph(MessagesState)\n",
" # highlight-next-line\n",
" .add_node(flight_assistant)\n",
" # highlight-next-line\n",
" .add_node(hotel_assistant)\n",
" .add_edge(START, \"flight_assistant\")\n",
" .compile()\n",
")\n",
"```\n",
"\n",
"??? example \"Full example: Multi-agent system for booking travel\"\n",
"\n",
" ```python\n",
" from typing import Annotated\n",
" from langchain_core.messages import convert_to_messages\n",
" from langchain_core.tools import tool, InjectedToolCallId\n",
" from langgraph.prebuilt import create_react_agent, InjectedState\n",
" from langgraph.graph import StateGraph, START, MessagesState\n",
" from langgraph.types import Command\n",
" \n",
" # We'll use `pretty_print_messages` helper to render the streamed agent outputs nicely\n",
" \n",
" def pretty_print_message(message, indent=False):\n",
" pretty_message = message.pretty_repr(html=True)\n",
" if not indent:\n",
" print(pretty_message)\n",
" return\n",
" \n",
" indented = \"\\n\".join(\"\\t\" + c for c in pretty_message.split(\"\\n\"))\n",
" print(indented)\n",
" \n",
" \n",
" def pretty_print_messages(update, last_message=False):\n",
" is_subgraph = False\n",
" if isinstance(update, tuple):\n",
" ns, update = update\n",
" # skip parent graph updates in the printouts\n",
" if len(ns) == 0:\n",
" return\n",
" \n",
" graph_id = ns[-1].split(\":\")[0]\n",
" print(f\"Update from subgraph {graph_id}:\")\n",
" print(\"\\n\")\n",
" is_subgraph = True\n",
" \n",
" for node_name, node_update in update.items():\n",
" update_label = f\"Update from node {node_name}:\"\n",
" if is_subgraph:\n",
" update_label = \"\\t\" + update_label\n",
" \n",
" print(update_label)\n",
" print(\"\\n\")\n",
" \n",
" messages = convert_to_messages(node_update[\"messages\"])\n",
" if last_message:\n",
" messages = messages[-1:]\n",
" \n",
" for m in messages:\n",
" pretty_print_message(m, indent=is_subgraph)\n",
" print(\"\\n\")\n",
"\n",
"\n",
" def create_handoff_tool(*, agent_name: str, description: str | None = None):\n",
" name = f\"transfer_to_{agent_name}\"\n",
" description = description or f\"Transfer to {agent_name}\"\n",
" \n",
" @tool(name, description=description)\n",
" def handoff_tool(\n",
" # highlight-next-line\n",
" state: Annotated[MessagesState, InjectedState], # (1)!\n",
" # highlight-next-line\n",
" tool_call_id: Annotated[str, InjectedToolCallId],\n",
" ) -> Command:\n",
" tool_message = {\n",
" \"role\": \"tool\",\n",
" \"content\": f\"Successfully transferred to {agent_name}\",\n",
" \"name\": name,\n",
" \"tool_call_id\": tool_call_id,\n",
" }\n",
" return Command( # (2)!\n",
" # highlight-next-line\n",
" goto=agent_name, # (3)!\n",
" # highlight-next-line\n",
" update={\"messages\": state[\"messages\"] + [tool_message]}, # (4)!\n",
" # highlight-next-line\n",
" graph=Command.PARENT, # (5)!\n",
" )\n",
" return handoff_tool\n",
" \n",
" # Handoffs\n",
" transfer_to_hotel_assistant = create_handoff_tool(\n",
" agent_name=\"hotel_assistant\",\n",
" description=\"Transfer user to the hotel-booking assistant.\",\n",
" )\n",
" transfer_to_flight_assistant = create_handoff_tool(\n",
" agent_name=\"flight_assistant\",\n",
" description=\"Transfer user to the flight-booking assistant.\",\n",
" )\n",
" \n",
" # Simple agent tools\n",
" def book_hotel(hotel_name: str):\n",
" \"\"\"Book a hotel\"\"\"\n",
" return f\"Successfully booked a stay at {hotel_name}.\"\n",
" \n",
" def book_flight(from_airport: str, to_airport: str):\n",
" \"\"\"Book a flight\"\"\"\n",
" return f\"Successfully booked a flight from {from_airport} to {to_airport}.\"\n",
" \n",
" # Define agents\n",
" flight_assistant = create_react_agent(\n",
" model=\"anthropic:claude-3-5-sonnet-latest\",\n",
" # highlight-next-line\n",
" tools=[book_flight, transfer_to_hotel_assistant],\n",
" prompt=\"You are a flight booking assistant\",\n",
" # highlight-next-line\n",
" name=\"flight_assistant\"\n",
" )\n",
" hotel_assistant = create_react_agent(\n",
" model=\"anthropic:claude-3-5-sonnet-latest\",\n",
" # highlight-next-line\n",
" tools=[book_hotel, transfer_to_flight_assistant],\n",
" prompt=\"You are a hotel booking assistant\",\n",
" # highlight-next-line\n",
" name=\"hotel_assistant\"\n",
" )\n",
" \n",
" # Define multi-agent graph\n",
" multi_agent_graph = (\n",
" StateGraph(MessagesState)\n",
" .add_node(flight_assistant)\n",
" .add_node(hotel_assistant)\n",
" .add_edge(START, \"flight_assistant\")\n",
" .compile()\n",
" )\n",
" \n",
" # Run the multi-agent graph\n",
" for chunk in multi_agent_graph.stream(\n",
" {\n",
" \"messages\": [\n",
" {\n",
" \"role\": \"user\",\n",
" \"content\": \"book a flight from BOS to JFK and a stay at McKittrick Hotel\"\n",
" }\n",
" ]\n",
" },\n",
" # highlight-next-line\n",
" subgraphs=True\n",
" ):\n",
" pretty_print_messages(chunk)\n",
" ```\n",
"\n",
" 1. Access agent's state\n",
" 2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.\n",
" 3. Name of the agent or node to hand off to.\n",
" 4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.\n",
" 5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph."
]
},
{
"cell_type": "markdown",
"id": "e8314da4-9971-429b-9e70-58b40795de74",
"metadata": {},
"source": [
"## Multi-turn conversation\n",
"\n",
"Users might want to engage in a *multi-turn conversation* with one or more agents. To build a system that can handle this, you can create a node that uses an [`interrupt`][langgraph.types.interrupt] to collect user input and routes back to the **active** agent.\n",
"\n",
"The agents can then be implemented as nodes in a graph that executes agent steps and determines the next action:\n",
"\n",
"1. **Wait for user input** to continue the conversation, or \n",
"2. **Route to another agent** (or back to itself, such as in a loop) via a [handoff](#handoffs)\n",
"\n",
"```python\n",
"def human(state) -> Command[Literal[\"agent\", \"another_agent\"]]:\n",
" \"\"\"A node for collecting user input.\"\"\"\n",
" user_input = interrupt(value=\"Ready for user input.\")\n",
"\n",
" # Determine the active agent.\n",
" active_agent = ...\n",
"\n",
" ...\n",
" return Command(\n",
" update={\n",
" \"messages\": [{\n",
" \"role\": \"human\",\n",
" \"content\": user_input,\n",
" }]\n",
" },\n",
" goto=active_agent\n",
" )\n",
"\n",
"def agent(state) -> Command[Literal[\"agent\", \"another_agent\", \"human\"]]:\n",
" # The condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.\n",
" goto = get_next_agent(...) # 'agent' / 'another_agent'\n",
" if goto:\n",
" return Command(goto=goto, update={\"my_state_key\": \"my_state_value\"})\n",
" else:\n",
" return Command(goto=\"human\") # Go to human node\n",
"```\n",
"\n",
"??? example \"Full example: multi-agent system for travel recommendations\"\n",
"\n",
" In this example, we will build a team of travel assistant agents that can communicate with each other via handoffs.\n",
" \n",
" We will create 2 agents:\n",
" \n",
" * travel_advisor: can help with travel destination recommendations. Can ask hotel_advisor for help.\n",
" * hotel_advisor: can help with hotel recommendations. Can ask travel_advisor for help.\n",
"\n",
" ```python\n",
" from langchain_anthropic import ChatAnthropic\n",
" from langgraph.graph import MessagesState, StateGraph, START\n",
" from langgraph.prebuilt import create_react_agent, InjectedState\n",
" from langgraph.types import Command, interrupt\n",
" from langgraph.checkpoint.memory import MemorySaver\n",
" \n",
" \n",
" model = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n",
"\n",
" class MultiAgentState(MessagesState):\n",
" last_active_agent: str\n",
" \n",
" \n",
" # Define travel advisor tools and ReAct agent\n",
" travel_advisor_tools = [\n",
" get_travel_recommendations,\n",
" make_handoff_tool(agent_name=\"hotel_advisor\"),\n",
" ]\n",
" travel_advisor = create_react_agent(\n",
" model,\n",
" travel_advisor_tools,\n",
" prompt=(\n",
" \"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). \"\n",
" \"If you need hotel recommendations, ask 'hotel_advisor' for help. \"\n",
" \"You MUST include human-readable response before transferring to another agent.\"\n",
" ),\n",
" )\n",
" \n",
" \n",
" def call_travel_advisor(\n",
" state: MultiAgentState,\n",
" ) -> Command[Literal[\"hotel_advisor\", \"human\"]]:\n",
" # You can also add additional logic like changing the input to the agent / output from the agent, etc.\n",
" # NOTE: we're invoking the ReAct agent with the full history of messages in the state\n",
" response = travel_advisor.invoke(state)\n",
" update = {**response, \"last_active_agent\": \"travel_advisor\"}\n",
" return Command(update=update, goto=\"human\")\n",
" \n",
" \n",
" # Define hotel advisor tools and ReAct agent\n",
" hotel_advisor_tools = [\n",
" get_hotel_recommendations,\n",
" make_handoff_tool(agent_name=\"travel_advisor\"),\n",
" ]\n",
" hotel_advisor = create_react_agent(\n",
" model,\n",
" hotel_advisor_tools,\n",
" prompt=(\n",
" \"You are a hotel expert that can provide hotel recommendations for a given destination. \"\n",
" \"If you need help picking travel destinations, ask 'travel_advisor' for help.\"\n",
" \"You MUST include human-readable response before transferring to another agent.\"\n",
" ),\n",
" )\n",
" \n",
" \n",
" def call_hotel_advisor(\n",
" state: MultiAgentState,\n",
" ) -> Command[Literal[\"travel_advisor\", \"human\"]]:\n",
" response = hotel_advisor.invoke(state)\n",
" update = {**response, \"last_active_agent\": \"hotel_advisor\"}\n",
" return Command(update=update, goto=\"human\")\n",
" \n",
" \n",
" def human_node(\n",
" state: MultiAgentState, config\n",
" ) -> Command[Literal[\"hotel_advisor\", \"travel_advisor\", \"human\"]]:\n",
" \"\"\"A node for collecting user input.\"\"\"\n",
" \n",
" user_input = interrupt(value=\"Ready for user input.\")\n",
" active_agent = state[\"last_active_agent\"]\n",
" \n",
" return Command(\n",
" update={\n",
" \"messages\": [\n",
" {\n",
" \"role\": \"human\",\n",
" \"content\": user_input,\n",
" }\n",
" ]\n",
" },\n",
" goto=active_agent,\n",
" )\n",
" \n",
" \n",
" builder = StateGraph(MultiAgentState)\n",
" builder.add_node(\"travel_advisor\", call_travel_advisor)\n",
" builder.add_node(\"hotel_advisor\", call_hotel_advisor)\n",
" \n",
" # This adds a node to collect human input, which will route\n",
" # back to the active agent.\n",
" builder.add_node(\"human\", human_node)\n",
" \n",
" # We'll always start with a general travel advisor.\n",
" builder.add_edge(START, \"travel_advisor\")\n",
" \n",
" \n",
" checkpointer = MemorySaver()\n",
" graph = builder.compile(checkpointer=checkpointer)\n",
" ```\n",
" \n",
" Let's test a multi turn conversation with this application.\n",
"\n",
" ```python\n",
" import uuid\n",
" \n",
" thread_config = {\"configurable\": {\"thread_id\": str(uuid.uuid4())}}\n",
" \n",
" inputs = [\n",
" # 1st round of conversation,\n",
" {\n",
" \"messages\": [\n",
" {\"role\": \"user\", \"content\": \"i wanna go somewhere warm in the caribbean\"}\n",
" ]\n",
" },\n",
" # Since we're using `interrupt`, we'll need to resume using the Command primitive.\n",
" # 2nd round of conversation,\n",
" Command(\n",
" resume=\"could you recommend a nice hotel in one of the areas and tell me which area it is.\"\n",
" ),\n",
" # 3rd round of conversation,\n",
" Command(\n",
" resume=\"i like the first one. could you recommend something to do near the hotel?\"\n",
" ),\n",
" ]\n",
" \n",
" for idx, user_input in enumerate(inputs):\n",
" print()\n",
" print(f\"--- Conversation Turn {idx + 1} ---\")\n",
" print()\n",
" print(f\"User: {user_input}\")\n",
" print()\n",
" for update in graph.stream(\n",
" user_input,\n",
" config=thread_config,\n",
" stream_mode=\"updates\",\n",
" ):\n",
" for node_id, value in update.items():\n",
" if isinstance(value, dict) and value.get(\"messages\", []):\n",
" last_message = value[\"messages\"][-1]\n",
" if isinstance(last_message, dict) or last_message.type != \"ai\":\n",
" continue\n",
" print(f\"{node_id}: {last_message.content}\")\n",
" ```\n",
" \n",
" ```\n",
" --- Conversation Turn 1 ---\n",
" \n",
" User: {'messages': [{'role': 'user', 'content': 'i wanna go somewhere warm in the caribbean'}]}\n",
" \n",
" travel_advisor: Based on the recommendations, Aruba would be an excellent choice for your Caribbean getaway! Aruba is known as \"One Happy Island\" and offers:\n",
" - Year-round warm weather with consistent temperatures around 82°F (28°C)\n",
" - Beautiful white sand beaches like Eagle Beach and Palm Beach\n",
" - Clear turquoise waters perfect for swimming and snorkeling\n",
" - Minimal rainfall and location outside the hurricane belt\n",
" - A blend of Caribbean and Dutch culture\n",
" - Great dining options and nightlife\n",
" - Various water sports and activities\n",
" \n",
" Would you like me to get some specific hotel recommendations in Aruba for your stay? I can transfer you to our hotel advisor who can help with accommodations.\n",
" \n",
" --- Conversation Turn 2 ---\n",
" \n",
" User: Command(resume='could you recommend a nice hotel in one of the areas and tell me which area it is.')\n",
" \n",
" hotel_advisor: Based on the recommendations, I can suggest two excellent options:\n",
" \n",
" 1. The Ritz-Carlton, Aruba - Located in Palm Beach\n",
" - This luxury resort is situated in the vibrant Palm Beach area\n",
" - Known for its exceptional service and amenities\n",
" - Perfect if you want to be close to dining, shopping, and entertainment\n",
" - Features multiple restaurants, a casino, and a world-class spa\n",
" - Located on a pristine stretch of Palm Beach\n",
" \n",
" 2. Bucuti & Tara Beach Resort - Located in Eagle Beach\n",
" - An adults-only boutique resort on Eagle Beach\n",
" - Known for being more intimate and peaceful\n",
" - Award-winning for its sustainability practices\n",
" - Perfect for a romantic getaway or peaceful vacation\n",
" - Located on one of the most beautiful beaches in the Caribbean\n",
" \n",
" Would you like more specific information about either of these properties or their locations?\n",
" \n",
" --- Conversation Turn 3 ---\n",
" \n",
" User: Command(resume='i like the first one. could you recommend something to do near the hotel?')\n",
" \n",
" travel_advisor: Near the Ritz-Carlton in Palm Beach, here are some highly recommended activities:\n",
" \n",
" 1. Visit the Palm Beach Plaza Mall - Just a short walk from the hotel, featuring shopping, dining, and entertainment\n",
" 2. Try your luck at the Stellaris Casino - It's right in the Ritz-Carlton\n",
" 3. Take a sunset sailing cruise - Many depart from the nearby pier\n",
" 4. Visit the California Lighthouse - A scenic landmark just north of Palm Beach\n",
" 5. Enjoy water sports at Palm Beach:\n",
" - Jet skiing\n",
" - Parasailing\n",
" - Snorkeling\n",
" - Stand-up paddleboarding\n",
" \n",
" Would you like more specific information about any of these activities or would you like to know about other options in the area?\n",
" ```"
]
},
{
"cell_type": "markdown",
"id": "04d18c63-a0eb-45ac-86dc-0cc5bd683973",
"metadata": {},
"source": [
"## Prebuilt implementations"
]
},
{
"cell_type": "markdown",
"id": "e0e4ce57-f8de-4f37-836e-c1e1a02dd7b7",
"metadata": {},
"source": [
"LangGraph comes with prebuilt implementations of two of the most popular multi-agent architectures:\n",
"\n",
"- [supervisor](../../agents/multi-agent#supervisor) — individual agents are coordinated by a central supervisor agent. The supervisor controls all communication flow and task delegation, making decisions about which agent to invoke based on the current context and task requirements. You can use [`langgraph-supervisor`](https://github.com/langchain-ai/langgraph-supervisor-py) library to create a supervisor multi-agent systems.\n",
"- [swarm](../../agents/multi-agent#supervisor) — agents dynamically hand off control to one another based on their specializations. The system remembers which agent was last active, ensuring that on subsequent interactions, the conversation resumes with that agent. You can use [`langgraph-swarm`](https://github.com/langchain-ai/langgraph-swarm-py) library to create a swarm multi-agent systems."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+580
View File
@@ -0,0 +1,580 @@
# Build multi-agent systems
A single agent might struggle if it needs to specialize in multiple domains or manage many tools. To tackle this, you can break your agent into smaller, independent agents and composing them into a [multi-agent system](../concepts/multi_agent.md).
In multi-agent systems, agents need to communicate between each other. They do so via [handoffs](#handoffs) — a primitive that describes which agent to hand control to and the payload to send to that agent.
This guide covers the following:
* implementing [handoffs](#handoffs) between agents
* using handoffs and the prebuilt [agent](../agents/agents.md) to [build a custom multi-agent system](#build-a-multi-agent-system)
To get started with building multi-agent systems, check out LangGraph [prebuilt implementations](#prebuilt-implementations) of two of the most popular multi-agent architectures — [supervisor](../agents/multi-agent.md#supervisor) and [swarm](../agents/multi-agent.md#swarm).
## Handoffs
To set up communication between the agents in a multi-agent system you can use [**handoffs**](../concepts/multi_agent.md#handoffs) — a pattern where one agent *hands off* control to another. Handoffs allow you to specify:
- **destination**: target agent to navigate to (e.g., name of the LangGraph node to go to)
- **payload**: information to pass to that agent (e.g., state update)
### Create handoffs
To implement handoffs, you can return `Command` objects from your agent nodes or tools:
```python
from typing import Annotated
from langchain_core.tools import tool, InjectedToolCallId
from langgraph.prebuilt import create_react_agent, InjectedState
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.types import Command
def create_handoff_tool(*, agent_name: str, description: str | None = None):
name = f"transfer_to_{agent_name}"
description = description or f"Transfer to {agent_name}"
@tool(name, description=description)
def handoff_tool(
# highlight-next-line
state: Annotated[MessagesState, InjectedState], # (1)!
# highlight-next-line
tool_call_id: Annotated[str, InjectedToolCallId],
) -> Command:
tool_message = {
"role": "tool",
"content": f"Successfully transferred to {agent_name}",
"name": name,
"tool_call_id": tool_call_id,
}
return Command( # (2)!
# highlight-next-line
goto=agent_name, # (3)!
# highlight-next-line
update={"messages": state["messages"] + [tool_message]}, # (4)!
# highlight-next-line
graph=Command.PARENT, # (5)!
)
return handoff_tool
```
1. Access the [state](../concepts/low_level.md#state) of the agent that is calling the handoff tool using the [InjectedState][langgraph.prebuilt.InjectedState] annotation.
2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.
3. Name of the agent or node to hand off to.
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
!!! tip
If you want to use tools that return `Command`, you can either use prebuilt [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] / [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] components, or implement your own tool-executing node that collects `Command` objects returned by the tools and returns a list of them, e.g.:
```python
def call_tools(state):
...
commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
return commands
```
!!! Important
This handoff implementation assumes that:
- each agent receives overall message history (across all agents) in the multi-agent system as its input. If you want more control over agent inputs, see [this section](#control-agent-inputs)
- each agent outputs its internal messages history to the overall message history of the multi-agent system. If you want more control over **how agent outputs are added**, wrap the agent in a separate node function:
```python
def call_hotel_assistant(state):
# return agent's final response,
# excluding inner monologue
response = hotel_assistant.invoke(state)
# highlight-next-line
return {"messages": response["messages"][-1]}
```
### Control agent inputs
You can use the [`Send()`][langgraph.types.Send] primitive to directly send data to the worker agents during the handoff. For example, you can request that the calling agent populate a task description for the next agent:
```python
from typing import Annotated
from langchain_core.tools import tool, InjectedToolCallId
from langgraph.prebuilt import InjectedState
from langgraph.graph import StateGraph, START, MessagesState
# highlight-next-line
from langgraph.types import Command, Send
def create_task_description_handoff_tool(
*, agent_name: str, description: str | None = None
):
name = f"transfer_to_{agent_name}"
description = description or f"Ask {agent_name} for help."
@tool(name, description=description)
def handoff_tool(
# this is populated by the calling agent
task_description: Annotated[
str,
"Description of what the next agent should do, including all of the relevant context.",
],
# these parameters are ignored by the LLM
state: Annotated[MessagesState, InjectedState],
) -> Command:
task_description_message = {"role": "user", "content": task_description}
agent_input = {**state, "messages": [task_description_message]}
return Command(
# highlight-next-line
goto=[Send(agent_name, agent_input)],
graph=Command.PARENT,
)
return handoff_tool
```
See the multi-agent [supervisor](../tutorials/multi_agent/agent_supervisor.md#4-create-delegation-tasks) example for a full example of using [`Send()`][langgraph.types.Send] in handoffs.
## Build a multi-agent system
You can use handoffs in any agents built with LangGraph. We recommend using the prebuilt [agent](../agents/overview.md) or [`ToolNode`](./tool-calling.md#toolnode), as they natively support handoffs tools returning `Command`. Below is an example of how you can implement a multi-agent system for booking travel using handoffs:
```python
from langgraph.prebuilt import create_react_agent
from langgraph.graph import StateGraph, START, MessagesState
def create_handoff_tool(*, agent_name: str, description: str | None = None):
# same implementation as above
...
return Command(...)
# Handoffs
transfer_to_hotel_assistant = create_handoff_tool(agent_name="hotel_assistant")
transfer_to_flight_assistant = create_handoff_tool(agent_name="flight_assistant")
# Define agents
flight_assistant = create_react_agent(
model="anthropic:claude-3-5-sonnet-latest",
# highlight-next-line
tools=[..., transfer_to_hotel_assistant],
# highlight-next-line
name="flight_assistant"
)
hotel_assistant = create_react_agent(
model="anthropic:claude-3-5-sonnet-latest",
# highlight-next-line
tools=[..., transfer_to_flight_assistant],
# highlight-next-line
name="hotel_assistant"
)
# Define multi-agent graph
multi_agent_graph = (
StateGraph(MessagesState)
# highlight-next-line
.add_node(flight_assistant)
# highlight-next-line
.add_node(hotel_assistant)
.add_edge(START, "flight_assistant")
.compile()
)
```
??? example "Full example: Multi-agent system for booking travel"
```python
from typing import Annotated
from langchain_core.messages import convert_to_messages
from langchain_core.tools import tool, InjectedToolCallId
from langgraph.prebuilt import create_react_agent, InjectedState
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.types import Command
# We'll use `pretty_print_messages` helper to render the streamed agent outputs nicely
def pretty_print_message(message, indent=False):
pretty_message = message.pretty_repr(html=True)
if not indent:
print(pretty_message)
return
indented = "\n".join("\t" + c for c in pretty_message.split("\n"))
print(indented)
def pretty_print_messages(update, last_message=False):
is_subgraph = False
if isinstance(update, tuple):
ns, update = update
# skip parent graph updates in the printouts
if len(ns) == 0:
return
graph_id = ns[-1].split(":")[0]
print(f"Update from subgraph {graph_id}:")
print("\n")
is_subgraph = True
for node_name, node_update in update.items():
update_label = f"Update from node {node_name}:"
if is_subgraph:
update_label = "\t" + update_label
print(update_label)
print("\n")
messages = convert_to_messages(node_update["messages"])
if last_message:
messages = messages[-1:]
for m in messages:
pretty_print_message(m, indent=is_subgraph)
print("\n")
def create_handoff_tool(*, agent_name: str, description: str | None = None):
name = f"transfer_to_{agent_name}"
description = description or f"Transfer to {agent_name}"
@tool(name, description=description)
def handoff_tool(
# highlight-next-line
state: Annotated[MessagesState, InjectedState], # (1)!
# highlight-next-line
tool_call_id: Annotated[str, InjectedToolCallId],
) -> Command:
tool_message = {
"role": "tool",
"content": f"Successfully transferred to {agent_name}",
"name": name,
"tool_call_id": tool_call_id,
}
return Command( # (2)!
# highlight-next-line
goto=agent_name, # (3)!
# highlight-next-line
update={"messages": state["messages"] + [tool_message]}, # (4)!
# highlight-next-line
graph=Command.PARENT, # (5)!
)
return handoff_tool
# Handoffs
transfer_to_hotel_assistant = create_handoff_tool(
agent_name="hotel_assistant",
description="Transfer user to the hotel-booking assistant.",
)
transfer_to_flight_assistant = create_handoff_tool(
agent_name="flight_assistant",
description="Transfer user to the flight-booking assistant.",
)
# Simple agent tools
def book_hotel(hotel_name: str):
"""Book a hotel"""
return f"Successfully booked a stay at {hotel_name}."
def book_flight(from_airport: str, to_airport: str):
"""Book a flight"""
return f"Successfully booked a flight from {from_airport} to {to_airport}."
# Define agents
flight_assistant = create_react_agent(
model="anthropic:claude-3-5-sonnet-latest",
# highlight-next-line
tools=[book_flight, transfer_to_hotel_assistant],
prompt="You are a flight booking assistant",
# highlight-next-line
name="flight_assistant"
)
hotel_assistant = create_react_agent(
model="anthropic:claude-3-5-sonnet-latest",
# highlight-next-line
tools=[book_hotel, transfer_to_flight_assistant],
prompt="You are a hotel booking assistant",
# highlight-next-line
name="hotel_assistant"
)
# Define multi-agent graph
multi_agent_graph = (
StateGraph(MessagesState)
.add_node(flight_assistant)
.add_node(hotel_assistant)
.add_edge(START, "flight_assistant")
.compile()
)
# Run the multi-agent graph
for chunk in multi_agent_graph.stream(
{
"messages": [
{
"role": "user",
"content": "book a flight from BOS to JFK and a stay at McKittrick Hotel"
}
]
},
# highlight-next-line
subgraphs=True
):
pretty_print_messages(chunk)
```
1. Access agent's state
2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.
3. Name of the agent or node to hand off to.
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
## Multi-turn conversation
Users might want to engage in a *multi-turn conversation* with one or more agents. To build a system that can handle this, you can create a node that uses an [`interrupt`][langgraph.types.interrupt] to collect user input and routes back to the **active** agent.
The agents can then be implemented as nodes in a graph that executes agent steps and determines the next action:
1. **Wait for user input** to continue the conversation, or
2. **Route to another agent** (or back to itself, such as in a loop) via a [handoff](#handoffs)
```python
def human(state) -> Command[Literal["agent", "another_agent"]]:
"""A node for collecting user input."""
user_input = interrupt(value="Ready for user input.")
# Determine the active agent.
active_agent = ...
...
return Command(
update={
"messages": [{
"role": "human",
"content": user_input,
}]
},
goto=active_agent
)
def agent(state) -> Command[Literal["agent", "another_agent", "human"]]:
# The condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.
goto = get_next_agent(...) # 'agent' / 'another_agent'
if goto:
return Command(goto=goto, update={"my_state_key": "my_state_value"})
else:
return Command(goto="human") # Go to human node
```
??? example "Full example: multi-agent system for travel recommendations"
In this example, we will build a team of travel assistant agents that can communicate with each other via handoffs.
We will create 2 agents:
* travel_advisor: can help with travel destination recommendations. Can ask hotel_advisor for help.
* hotel_advisor: can help with hotel recommendations. Can ask travel_advisor for help.
```python
from langchain_anthropic import ChatAnthropic
from langgraph.graph import MessagesState, StateGraph, START
from langgraph.prebuilt import create_react_agent, InjectedState
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import MemorySaver
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
class MultiAgentState(MessagesState):
last_active_agent: str
# Define travel advisor tools and ReAct agent
travel_advisor_tools = [
get_travel_recommendations,
make_handoff_tool(agent_name="hotel_advisor"),
]
travel_advisor = create_react_agent(
model,
travel_advisor_tools,
prompt=(
"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). "
"If you need hotel recommendations, ask 'hotel_advisor' for help. "
"You MUST include human-readable response before transferring to another agent."
),
)
def call_travel_advisor(
state: MultiAgentState,
) -> Command[Literal["hotel_advisor", "human"]]:
# You can also add additional logic like changing the input to the agent / output from the agent, etc.
# NOTE: we're invoking the ReAct agent with the full history of messages in the state
response = travel_advisor.invoke(state)
update = {**response, "last_active_agent": "travel_advisor"}
return Command(update=update, goto="human")
# Define hotel advisor tools and ReAct agent
hotel_advisor_tools = [
get_hotel_recommendations,
make_handoff_tool(agent_name="travel_advisor"),
]
hotel_advisor = create_react_agent(
model,
hotel_advisor_tools,
prompt=(
"You are a hotel expert that can provide hotel recommendations for a given destination. "
"If you need help picking travel destinations, ask 'travel_advisor' for help."
"You MUST include human-readable response before transferring to another agent."
),
)
def call_hotel_advisor(
state: MultiAgentState,
) -> Command[Literal["travel_advisor", "human"]]:
response = hotel_advisor.invoke(state)
update = {**response, "last_active_agent": "hotel_advisor"}
return Command(update=update, goto="human")
def human_node(
state: MultiAgentState, config
) -> Command[Literal["hotel_advisor", "travel_advisor", "human"]]:
"""A node for collecting user input."""
user_input = interrupt(value="Ready for user input.")
active_agent = state["last_active_agent"]
return Command(
update={
"messages": [
{
"role": "human",
"content": user_input,
}
]
},
goto=active_agent,
)
builder = StateGraph(MultiAgentState)
builder.add_node("travel_advisor", call_travel_advisor)
builder.add_node("hotel_advisor", call_hotel_advisor)
# This adds a node to collect human input, which will route
# back to the active agent.
builder.add_node("human", human_node)
# We'll always start with a general travel advisor.
builder.add_edge(START, "travel_advisor")
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
```
Let's test a multi turn conversation with this application.
```python
import uuid
thread_config = {"configurable": {"thread_id": str(uuid.uuid4())}}
inputs = [
# 1st round of conversation,
{
"messages": [
{"role": "user", "content": "i wanna go somewhere warm in the caribbean"}
]
},
# Since we're using `interrupt`, we'll need to resume using the Command primitive.
# 2nd round of conversation,
Command(
resume="could you recommend a nice hotel in one of the areas and tell me which area it is."
),
# 3rd round of conversation,
Command(
resume="i like the first one. could you recommend something to do near the hotel?"
),
]
for idx, user_input in enumerate(inputs):
print()
print(f"--- Conversation Turn {idx + 1} ---")
print()
print(f"User: {user_input}")
print()
for update in graph.stream(
user_input,
config=thread_config,
stream_mode="updates",
):
for node_id, value in update.items():
if isinstance(value, dict) and value.get("messages", []):
last_message = value["messages"][-1]
if isinstance(last_message, dict) or last_message.type != "ai":
continue
print(f"{node_id}: {last_message.content}")
```
```
--- Conversation Turn 1 ---
User: {'messages': [{'role': 'user', 'content': 'i wanna go somewhere warm in the caribbean'}]}
travel_advisor: Based on the recommendations, Aruba would be an excellent choice for your Caribbean getaway! Aruba is known as "One Happy Island" and offers:
- Year-round warm weather with consistent temperatures around 82°F (28°C)
- Beautiful white sand beaches like Eagle Beach and Palm Beach
- Clear turquoise waters perfect for swimming and snorkeling
- Minimal rainfall and location outside the hurricane belt
- A blend of Caribbean and Dutch culture
- Great dining options and nightlife
- Various water sports and activities
Would you like me to get some specific hotel recommendations in Aruba for your stay? I can transfer you to our hotel advisor who can help with accommodations.
--- Conversation Turn 2 ---
User: Command(resume='could you recommend a nice hotel in one of the areas and tell me which area it is.')
hotel_advisor: Based on the recommendations, I can suggest two excellent options:
1. The Ritz-Carlton, Aruba - Located in Palm Beach
- This luxury resort is situated in the vibrant Palm Beach area
- Known for its exceptional service and amenities
- Perfect if you want to be close to dining, shopping, and entertainment
- Features multiple restaurants, a casino, and a world-class spa
- Located on a pristine stretch of Palm Beach
2. Bucuti & Tara Beach Resort - Located in Eagle Beach
- An adults-only boutique resort on Eagle Beach
- Known for being more intimate and peaceful
- Award-winning for its sustainability practices
- Perfect for a romantic getaway or peaceful vacation
- Located on one of the most beautiful beaches in the Caribbean
Would you like more specific information about either of these properties or their locations?
--- Conversation Turn 3 ---
User: Command(resume='i like the first one. could you recommend something to do near the hotel?')
travel_advisor: Near the Ritz-Carlton in Palm Beach, here are some highly recommended activities:
1. Visit the Palm Beach Plaza Mall - Just a short walk from the hotel, featuring shopping, dining, and entertainment
2. Try your luck at the Stellaris Casino - It's right in the Ritz-Carlton
3. Take a sunset sailing cruise - Many depart from the nearby pier
4. Visit the California Lighthouse - A scenic landmark just north of Palm Beach
5. Enjoy water sports at Palm Beach:
- Jet skiing
- Parasailing
- Snorkeling
- Stand-up paddleboarding
Would you like more specific information about any of these activities or would you like to know about other options in the area?
```
## Prebuilt implementations
LangGraph comes with prebuilt implementations of two of the most popular multi-agent architectures:
- [supervisor](../agents/multi-agent.md#supervisor) — individual agents are coordinated by a central supervisor agent. The supervisor controls all communication flow and task delegation, making decisions about which agent to invoke based on the current context and task requirements. You can use [`langgraph-supervisor`](https://github.com/langchain-ai/langgraph-supervisor-py) library to create a supervisor multi-agent systems.
- [swarm](../agents/multi-agent.md#supervisor) — agents dynamically hand off control to one another based on their specializations. The system remembers which agent was last active, ensuring that on subsequent interactions, the conversation resumes with that agent. You can use [`langgraph-swarm`](https://github.com/langchain-ai/langgraph-swarm-py) library to create a swarm multi-agent systems.
File diff suppressed because one or more lines are too long
+155
View File
@@ -0,0 +1,155 @@
# How to pass custom run ID or set tags and metadata for graph runs in LangSmith
!!! tip "Prerequisites"
This guide assumes familiarity with the following:
- [LangSmith Documentation](https://docs.smith.langchain.com)
- [LangSmith Platform](https://smith.langchain.com)
- [RunnableConfig](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.config.RunnableConfig.html#langchain_core.runnables.config.RunnableConfig)
- [Add metadata and tags to traces](https://docs.smith.langchain.com/how_to_guides/tracing/trace_with_langchain#add-metadata-and-tags-to-traces)
- [Customize run name](https://docs.smith.langchain.com/how_to_guides/tracing/trace_with_langchain#customize-run-name)
Debugging graph runs can sometimes be difficult to do in an IDE or terminal. [LangSmith](https://docs.smith.langchain.com) lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read the [LangSmith documentation](https://docs.smith.langchain.com) for more information on how to get started.
To make it easier to identify and analyzed traces generated during graph invocation, you can set additional configuration at run time (see [RunnableConfig](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.config.RunnableConfig.html#langchain_core.runnables.config.RunnableConfig)):
| **Field** | **Type** | **Description** |
|-------------|---------------------|--------------------------------------------------------------------------------------------------------------------|
| run_name | `str` | Name for the tracer run for this call. Defaults to the name of the class. |
| run_id | `UUID` | Unique identifier for the tracer run for this call. If not provided, a new UUID will be generated. |
| tags | `List[str]` | Tags for this call and any sub-calls (e.g., a Chain calling an LLM). You can use these to filter calls. |
| metadata | `Dict[str, Any]` | Metadata for this call and any sub-calls (e.g., a Chain calling an LLM). Keys should be strings, values should be JSON-serializable. |
LangGraph graphs implement the [LangChain Runnable Interface](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.base.Runnable.html) and accept a second argument (`RunnableConfig`) in methods like `invoke`, `ainvoke`, `stream` etc.
The LangSmith platform will allow you to search and filter traces based on `run_name`, `run_id`, `tags` and `metadata`.
## TLDR
```python
import uuid
# Generate a random UUID -- it must be a UUID
config = {"run_id": uuid.uuid4()}, "tags": ["my_tag1"], "metadata": {"a": 5}}
# Works with all standard Runnable methods
# like invoke, batch, ainvoke, astream_events etc
graph.stream(inputs, config, stream_mode="values")
```
The rest of the how to guide will show a full agent.
## Setup
First, let's install the required packages and set our API keys
```python
%%capture --no-stderr
%pip install --quiet -U langgraph langchain_openai
```
```python
import getpass
import os
def _set_env(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
_set_env("OPENAI_API_KEY")
_set_env("LANGSMITH_API_KEY")
```
!!! tip
Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. [LangSmith](https://docs.smith.langchain.com) lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com).
## Define the graph
For this example we will use the [prebuilt ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent/).
```python
from langchain_openai import ChatOpenAI
from typing import Literal
from langgraph.prebuilt import create_react_agent
from langchain_core.tools import tool
# First we initialize the model we want to use.
model = ChatOpenAI(model="gpt-4o", temperature=0)
# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)
@tool
def get_weather(city: Literal["nyc", "sf"]):
"""Use this to get weather information."""
if city == "nyc":
return "It might be cloudy in nyc"
elif city == "sf":
return "It's always sunny in sf"
else:
raise AssertionError("Unknown city")
tools = [get_weather]
# Define the graph
graph = create_react_agent(model, tools=tools)
```
## Run your graph
Now that we've defined our graph let's run it once and view the trace in LangSmith. In order for our trace to be easily accessible in LangSmith, we will pass in a custom `run_id` in the config.
This assumes that you have set your `LANGSMITH_API_KEY` environment variable.
Note that you can also configure what project to trace to by setting the `LANGCHAIN_PROJECT` environment variable, by default runs will be traced to the `default` project.
```python
import uuid
def print_stream(stream):
for s in stream:
message = s["messages"][-1]
if isinstance(message, tuple):
print(message)
else:
message.pretty_print()
inputs = {"messages": [("user", "what is the weather in sf")]}
config = {"run_name": "agent_007", "tags": ["cats are awesome"]}
print_stream(graph.stream(inputs, config, stream_mode="values"))
```
**Output:**
```
================================ Human Message ==================================
what is the weather in sf
================================== Ai Message ===================================
Tool Calls:
get_weather (call_9ZudXyMAdlUjptq9oMGtQo8o)
Call ID: call_9ZudXyMAdlUjptq9oMGtQo8o
Args:
city: sf
================================= Tool Message ==================================
Name: get_weather
It's always sunny in sf
================================== Ai Message ===================================
The weather in San Francisco is currently sunny.
```
## View the trace in LangSmith
Now that we've ran our graph, let's head over to LangSmith and view our trace. First click into the project that you traced to (in our case the default project). You should see a run with the custom run name "agent_007".
![LangSmith Trace View](assets/d38d1f2b-0f4c-4707-b531-a3c749de987f.png)
In addition, you will be able to filter traces after the fact using the tags or metadata provided. For example,
![LangSmith Filter View](assets/410e0089-2ab8-46bb-a61a-827187fd46b3.png)
-559
View File
@@ -1,559 +0,0 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"# Use subgraphs\n",
"\n",
"This guide explains the mechanics of using [subgraphs](../../concepts/subgraphs). A common application of subgraphs is to build [multi-agent](../../concepts/multi_agent) systems.\n",
"\n",
"When adding subgraphs, you need to define how the parent graph and the subgraph communicate:\n",
"\n",
"* [Shared state schemas](#shared-state-schemas) — parent and subgraph have **shared state keys** in their state [schemas](../../concepts/low_level#state)\n",
"* [Different state schemas](#different-state-schemas) — **no shared state keys** in parent and subgraph [schemas](../../concepts/low_level#state)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setup"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%%capture --no-stderr\n",
"%pip install -U langgraph"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for LangGraph development</p>\n",
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div>"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Shared state schemas\n",
"\n",
"A common case is for the parent graph and subgraph to communicate over a shared state key (channel) in the [schema](../../concepts/low_level#state). For example, in [multi-agent](../../concepts/multi_agent) systems, the agents often communicate over a shared [messages](https://langchain-ai.github.io/langgraph/concepts/low_level/#why-use-messages) key.\n",
"\n",
"If your subgraph shares state keys with the parent graph, you can follow these steps to add it to your graph:\n",
"\n",
"1. Define the subgraph workflow (`subgraph_builder` in the example below) and compile it\n",
"2. Pass compiled subgraph to the `.add_node` method when defining the parent graph workflow\n",
"\n",
"```python\n",
"from typing_extensions import TypedDict\n",
"from langgraph.graph.state import StateGraph, START\n",
"\n",
"class State(TypedDict):\n",
" foo: str\n",
"\n",
"# Subgraph\n",
"\n",
"def subgraph_node_1(state: State):\n",
" return {\"foo\": \"hi! \" + state[\"foo\"]}\n",
"\n",
"subgraph_builder = StateGraph(State)\n",
"subgraph_builder.add_node(subgraph_node_1)\n",
"subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
"# highlight-next-line\n",
"subgraph = subgraph_builder.compile()\n",
"\n",
"# Parent graph\n",
"\n",
"builder = StateGraph(State)\n",
"# highlight-next-line\n",
"builder.add_node(\"node_1\", subgraph)\n",
"builder.add_edge(START, \"node_1\")\n",
"graph = builder.compile()\n",
"```\n",
"\n",
"??? example \"Full example: shared state schemas\"\n",
"\n",
" ```python\n",
" from typing_extensions import TypedDict\n",
" from langgraph.graph.state import StateGraph, START\n",
"\n",
" # Define subgraph\n",
" class SubgraphState(TypedDict):\n",
" foo: str # (1)! \n",
" bar: str # (2)!\n",
" \n",
" def subgraph_node_1(state: SubgraphState):\n",
" return {\"bar\": \"bar\"}\n",
" \n",
" def subgraph_node_2(state: SubgraphState):\n",
" # note that this node is using a state key ('bar') that is only available in the subgraph\n",
" # and is sending update on the shared state key ('foo')\n",
" return {\"foo\": state[\"foo\"] + state[\"bar\"]}\n",
" \n",
" subgraph_builder = StateGraph(SubgraphState)\n",
" subgraph_builder.add_node(subgraph_node_1)\n",
" subgraph_builder.add_node(subgraph_node_2)\n",
" subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
" subgraph_builder.add_edge(\"subgraph_node_1\", \"subgraph_node_2\")\n",
" subgraph = subgraph_builder.compile()\n",
" \n",
" # Define parent graph\n",
" class ParentState(TypedDict):\n",
" foo: str\n",
" \n",
" def node_1(state: ParentState):\n",
" return {\"foo\": \"hi! \" + state[\"foo\"]}\n",
" \n",
" builder = StateGraph(ParentState)\n",
" builder.add_node(\"node_1\", node_1)\n",
" # highlight-next-line\n",
" builder.add_node(\"node_2\", subgraph)\n",
" builder.add_edge(START, \"node_1\")\n",
" builder.add_edge(\"node_1\", \"node_2\")\n",
" graph = builder.compile()\n",
" \n",
" for chunk in graph.stream({\"foo\": \"foo\"}):\n",
" print(chunk)\n",
" ```\n",
"\n",
" 1. This key is shared with the parent graph state\n",
" 2. This key is private to the `SubgraphState` and is not visible to the parent graph\n",
" \n",
" ```\n",
" {'node_1': {'foo': 'hi! foo'}}\n",
" {'node_2': {'foo': 'hi! foobar'}}\n",
" ```\n",
"\n",
" ```"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Different state schemas\n",
"\n",
"For more complex systems you might want to define subgraphs that have a **completely different schema** from the parent graph (no shared keys). For example, you might want to keep a private message history for each of the agents in a [multi-agent](../concepts/multi_agent.md) system.\n",
"\n",
"If that's the case for your application, you need to define a node **function that invokes the subgraph**. This function needs to transform the input (parent) state to the subgraph state before invoking the subgraph, and transform the results back to the parent state before returning the state update from the node.\n",
"\n",
"```python\n",
"from typing_extensions import TypedDict\n",
"from langgraph.graph.state import StateGraph, START\n",
"\n",
"class SubgraphState(TypedDict):\n",
" bar: str\n",
"\n",
"# Subgraph\n",
"\n",
"def subgraph_node_1(state: SubgraphState):\n",
" return {\"bar\": \"hi! \" + state[\"bar\"]}\n",
"\n",
"subgraph_builder = StateGraph(SubgraphState)\n",
"subgraph_builder.add_node(subgraph_node_1)\n",
"subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
"# highlight-next-line\n",
"subgraph = subgraph_builder.compile()\n",
"\n",
"# Parent graph\n",
"\n",
"class State(TypedDict):\n",
" foo: str\n",
"\n",
"def call_subgraph(state: State):\n",
" # highlight-next-line\n",
" subgraph_output = subgraph.invoke({\"bar\": state[\"foo\"]}) # (1)!\n",
" # highlight-next-line\n",
" return {\"foo\": subgraph_output[\"bar\"]} # (2)!\n",
"\n",
"builder = StateGraph(State)\n",
"# highlight-next-line\n",
"builder.add_node(\"node_1\", call_subgraph)\n",
"builder.add_edge(START, \"node_1\")\n",
"graph = builder.compile()\n",
"```\n",
"\n",
"1. Transform the state to the subgraph state\n",
"2. Transform response back to the parent state\n",
"\n",
"??? example \"Full example: different state schemas\"\n",
"\n",
" ```python\n",
" from typing_extensions import TypedDict\n",
" from langgraph.graph.state import StateGraph, START\n",
"\n",
" # Define subgraph\n",
" class SubgraphState(TypedDict):\n",
" # note that none of these keys are shared with the parent graph state\n",
" bar: str\n",
" baz: str\n",
" \n",
" def subgraph_node_1(state: SubgraphState):\n",
" return {\"baz\": \"baz\"}\n",
" \n",
" def subgraph_node_2(state: SubgraphState):\n",
" return {\"bar\": state[\"bar\"] + state[\"baz\"]}\n",
" \n",
" subgraph_builder = StateGraph(SubgraphState)\n",
" subgraph_builder.add_node(subgraph_node_1)\n",
" subgraph_builder.add_node(subgraph_node_2)\n",
" subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
" subgraph_builder.add_edge(\"subgraph_node_1\", \"subgraph_node_2\")\n",
" subgraph = subgraph_builder.compile()\n",
" \n",
" # Define parent graph\n",
" class ParentState(TypedDict):\n",
" foo: str\n",
" \n",
" def node_1(state: ParentState):\n",
" return {\"foo\": \"hi! \" + state[\"foo\"]}\n",
" \n",
" def node_2(state: ParentState):\n",
" # highlight-next-line\n",
" response = subgraph.invoke({\"bar\": state[\"foo\"]}) # (1)!\n",
" # highlight-next-line\n",
" return {\"foo\": response[\"bar\"]} # (2)!\n",
" \n",
" \n",
" builder = StateGraph(ParentState)\n",
" builder.add_node(\"node_1\", node_1)\n",
" # highlight-next-line\n",
" builder.add_node(\"node_2\", node_2)\n",
" builder.add_edge(START, \"node_1\")\n",
" builder.add_edge(\"node_1\", \"node_2\")\n",
" graph = builder.compile()\n",
" \n",
" for chunk in graph.stream({\"foo\": \"foo\"}, subgraphs=True):\n",
" print(chunk)\n",
" ```\n",
"\n",
" 1. Transform the state to the subgraph state\n",
" 2. Transform response back to the parent state\n",
"\n",
" ```\n",
" ((), {'node_1': {'foo': 'hi! foo'}})\n",
" (('node_2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7',), {'subgraph_node_1': {'baz': 'baz'}})\n",
" (('node_2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7',), {'subgraph_node_2': {'bar': 'hi! foobaz'}})\n",
" ((), {'node_2': {'foo': 'hi! foobaz'}})\n",
" ```\n",
"\n",
"??? example \"Full example: different state schemas (two levels of subgraphs)\"\n",
"\n",
" This is an example with two levels of subgraphs: parent -> child -> grandchild.\n",
"\n",
" ```python\n",
" # Grandchild graph\n",
" from typing_extensions import TypedDict\n",
" from langgraph.graph.state import StateGraph, START, END\n",
" \n",
" class GrandChildState(TypedDict):\n",
" my_grandchild_key: str\n",
" \n",
" def grandchild_1(state: GrandChildState) -> GrandChildState:\n",
" # NOTE: child or parent keys will not be accessible here\n",
" return {\"my_grandchild_key\": state[\"my_grandchild_key\"] + \", how are you\"}\n",
" \n",
" \n",
" grandchild = StateGraph(GrandChildState)\n",
" grandchild.add_node(\"grandchild_1\", grandchild_1)\n",
" \n",
" grandchild.add_edge(START, \"grandchild_1\")\n",
" grandchild.add_edge(\"grandchild_1\", END)\n",
" \n",
" grandchild_graph = grandchild.compile()\n",
" \n",
" # Child graph\n",
" class ChildState(TypedDict):\n",
" my_child_key: str\n",
" \n",
" def call_grandchild_graph(state: ChildState) -> ChildState:\n",
" # NOTE: parent or grandchild keys won't be accessible here\n",
" grandchild_graph_input = {\"my_grandchild_key\": state[\"my_child_key\"]} # (1)!\n",
" # highlight-next-line\n",
" grandchild_graph_output = grandchild_graph.invoke(grandchild_graph_input)\n",
" return {\"my_child_key\": grandchild_graph_output[\"my_grandchild_key\"] + \" today?\"} # (2)!\n",
" \n",
" child = StateGraph(ChildState)\n",
" # highlight-next-line\n",
" child.add_node(\"child_1\", call_grandchild_graph) # (3)!\n",
" child.add_edge(START, \"child_1\")\n",
" child.add_edge(\"child_1\", END)\n",
" child_graph = child.compile()\n",
" \n",
" # Parent graph\n",
" class ParentState(TypedDict):\n",
" my_key: str\n",
" \n",
" def parent_1(state: ParentState) -> ParentState:\n",
" # NOTE: child or grandchild keys won't be accessible here\n",
" return {\"my_key\": \"hi \" + state[\"my_key\"]}\n",
" \n",
" def parent_2(state: ParentState) -> ParentState:\n",
" return {\"my_key\": state[\"my_key\"] + \" bye!\"}\n",
" \n",
" def call_child_graph(state: ParentState) -> ParentState:\n",
" child_graph_input = {\"my_child_key\": state[\"my_key\"]} # (4)!\n",
" # highlight-next-line\n",
" child_graph_output = child_graph.invoke(child_graph_input)\n",
" return {\"my_key\": child_graph_output[\"my_child_key\"]} # (5)!\n",
" \n",
" parent = StateGraph(ParentState)\n",
" parent.add_node(\"parent_1\", parent_1)\n",
" # highlight-next-line\n",
" parent.add_node(\"child\", call_child_graph) # (6)!\n",
" parent.add_node(\"parent_2\", parent_2)\n",
" \n",
" parent.add_edge(START, \"parent_1\")\n",
" parent.add_edge(\"parent_1\", \"child\")\n",
" parent.add_edge(\"child\", \"parent_2\")\n",
" parent.add_edge(\"parent_2\", END)\n",
" \n",
" parent_graph = parent.compile()\n",
" \n",
" for chunk in parent_graph.stream({\"my_key\": \"Bob\"}, subgraphs=True):\n",
" print(chunk)\n",
" ```\n",
"\n",
" 1. We're transforming the state from the child state channels (`my_child_key`) to the child state channels (`my_grandchild_key`)\n",
" 2. We're transforming the state from the grandchild state channels (`my_grandchild_key`) back to the child state channels (`my_child_key`)\n",
" 3. We're passing a function here instead of just compiled graph (`grandchild_graph`)\n",
" 4. We're transforming the state from the parent state channels (`my_key`) to the child state channels (`my_child_key`)\n",
" 5. We're transforming the state from the child state channels (`my_child_key`) back to the parent state channels (`my_key`)\n",
" 6. We're passing a function here instead of just a compiled graph (`child_graph`)\n",
"\n",
" ```\n",
" ((), {'parent_1': {'my_key': 'hi Bob'}})\n",
" (('child:2e26e9ce-602f-862c-aa66-1ea5a4655e3b', 'child_1:781bb3b1-3971-84ce-810b-acf819a03f9c'), {'grandchild_1': {'my_grandchild_key': 'hi Bob, how are you'}})\n",
" (('child:2e26e9ce-602f-862c-aa66-1ea5a4655e3b',), {'child_1': {'my_child_key': 'hi Bob, how are you today?'}})\n",
" ((), {'child': {'my_key': 'hi Bob, how are you today?'}})\n",
" ((), {'parent_2': {'my_key': 'hi Bob, how are you today? bye!'}})\n",
" ```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Add persistence \n",
"\n",
"You only need to **provide the checkpointer when compiling the parent graph**. LangGraph will automatically propagate the checkpointer to the child subgraphs.\n",
"\n",
"```python\n",
"from langgraph.graph import START, StateGraph\n",
"from langgraph.checkpoint.memory import InMemorySaver\n",
"from typing_extensions import TypedDict\n",
"\n",
"class State(TypedDict):\n",
" foo: str\n",
"\n",
"# Subgraph\n",
"\n",
"def subgraph_node_1(state: State):\n",
" return {\"foo\": state[\"foo\"] + \"bar\"}\n",
"\n",
"subgraph_builder = StateGraph(State)\n",
"subgraph_builder.add_node(subgraph_node_1)\n",
"subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
"# highlight-next-line\n",
"subgraph = subgraph_builder.compile()\n",
"\n",
"# Parent graph\n",
"\n",
"builder = StateGraph(State)\n",
"# highlight-next-line\n",
"builder.add_node(\"node_1\", subgraph)\n",
"builder.add_edge(START, \"node_1\")\n",
"\n",
"checkpointer = InMemorySaver()\n",
"# highlight-next-line\n",
"graph = builder.compile(checkpointer=checkpointer)\n",
"``` \n",
"\n",
"If you want the subgraph to **have its own memory**, you can compile it `with checkpointer=True`. This is useful in [multi-agent](../../concepts/multi_agent) systems, if you want agents to keep track of their internal message histories:\n",
"\n",
"```python\n",
"subgraph_builder = StateGraph(...)\n",
"# highlight-next-line\n",
"subgraph = subgraph_builder.compile(checkpointer=True)\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## View subgraph state\n",
"\n",
"When you enable [persistence](../persistence), you can [inspect the graph state](../persistence#manage-checkpoints) (checkpoint) via `graph.get_state(config)`. To view the subgraph state, you can use `graph.get_state(config, subgraphs=True)`.\n",
"\n",
"!!! important \"Available **only** when interrupted\"\n",
"\n",
" Subgraph state can only be viewed **when the subgraph is interrupted**. Once you resume the graph, you won't be able to access the subgraph state.\n",
"\n",
"??? example \"View interrupted subgraph state\"\n",
"\n",
" ```python\n",
" from langgraph.graph import START, StateGraph\n",
" from langgraph.checkpoint.memory import InMemorySaver\n",
" from langgraph.types import interrupt, Command\n",
" from typing_extensions import TypedDict\n",
" \n",
" class State(TypedDict):\n",
" foo: str\n",
" \n",
" # Subgraph\n",
" \n",
" def subgraph_node_1(state: State):\n",
" # highlight-next-line\n",
" value = interrupt(\"Provide value:\")\n",
" return {\"foo\": state[\"foo\"] + value}\n",
" \n",
" subgraph_builder = StateGraph(State)\n",
" subgraph_builder.add_node(subgraph_node_1)\n",
" subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
" \n",
" subgraph = subgraph_builder.compile()\n",
" \n",
" # Parent graph\n",
" \n",
" builder = StateGraph(State)\n",
" # highlight-next-line\n",
" builder.add_node(\"node_1\", subgraph)\n",
" builder.add_edge(START, \"node_1\")\n",
" \n",
" checkpointer = InMemorySaver()\n",
" # highlight-next-line\n",
" graph = builder.compile(checkpointer=checkpointer)\n",
" \n",
" config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
" \n",
" graph.invoke({\"foo\": \"\"}, config)\n",
" parent_state = graph.get_state(config)\n",
" # highlight-next-line\n",
" subgraph_state = graph.get_state(config, subgraphs=True).tasks[0].state # (1)!\n",
" \n",
" # resume the subgraph\n",
" graph.invoke(Command(resume=\"bar\"), config)\n",
" ```\n",
" \n",
" 1. This will be available only when the subgraph is interrupted. Once you resume the graph, you won't be able to access the subgraph state."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Stream subgraph outputs\n",
"\n",
"To include outputs from [subgraphs](../concepts/low_level.md#subgraphs) in the streamed outputs, you can set `subgraphs=True` in the `.stream()` method of the parent graph. This will stream outputs from both the parent graph and any subgraphs.\n",
"\n",
"```python\n",
"for chunk in graph.stream(\n",
" {\"foo\": \"foo\"},\n",
" # highlight-next-line\n",
" subgraphs=True, # (1)!\n",
" stream_mode=\"updates\",\n",
"):\n",
" print(chunk)\n",
"```\n",
"\n",
"1. Set `subgraphs=True` to stream outputs from subgraphs.\n",
"\n",
"??? example \"Stream from subgraphs\"\n",
"\n",
" ```python\n",
" from typing_extensions import TypedDict\n",
" from langgraph.graph.state import StateGraph, START\n",
"\n",
" # Define subgraph\n",
" class SubgraphState(TypedDict):\n",
" foo: str\n",
" bar: str\n",
" \n",
" def subgraph_node_1(state: SubgraphState):\n",
" return {\"bar\": \"bar\"}\n",
" \n",
" def subgraph_node_2(state: SubgraphState):\n",
" # note that this node is using a state key ('bar') that is only available in the subgraph\n",
" # and is sending update on the shared state key ('foo')\n",
" return {\"foo\": state[\"foo\"] + state[\"bar\"]}\n",
" \n",
" subgraph_builder = StateGraph(SubgraphState)\n",
" subgraph_builder.add_node(subgraph_node_1)\n",
" subgraph_builder.add_node(subgraph_node_2)\n",
" subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
" subgraph_builder.add_edge(\"subgraph_node_1\", \"subgraph_node_2\")\n",
" subgraph = subgraph_builder.compile()\n",
" \n",
" # Define parent graph\n",
" class ParentState(TypedDict):\n",
" foo: str\n",
" \n",
" def node_1(state: ParentState):\n",
" return {\"foo\": \"hi! \" + state[\"foo\"]}\n",
" \n",
" builder = StateGraph(ParentState)\n",
" builder.add_node(\"node_1\", node_1)\n",
" # highlight-next-line\n",
" builder.add_node(\"node_2\", subgraph)\n",
" builder.add_edge(START, \"node_1\")\n",
" builder.add_edge(\"node_1\", \"node_2\")\n",
" graph = builder.compile()\n",
"\n",
" for chunk in graph.stream(\n",
" {\"foo\": \"foo\"},\n",
" stream_mode=\"updates\",\n",
" # highlight-next-line\n",
" subgraphs=True, # (1)!\n",
" ):\n",
" print(chunk)\n",
" ```\n",
" \n",
" 1. Set `subgraphs=True` to stream outputs from subgraphs.\n",
"\n",
" ```\n",
" ((), {'node_1': {'foo': 'hi! foo'}})\n",
" (('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_1': {'bar': 'bar'}})\n",
" (('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_2': {'foo': 'hi! foobar'}})\n",
" ((), {'node_2': {'foo': 'hi! foobar'}})\n",
" ```"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+453
View File
@@ -0,0 +1,453 @@
# Use subgraphs
This guide explains the mechanics of using [subgraphs](../concepts/subgraphs.md). A common application of subgraphs is to build [multi-agent](../concepts/multi_agent.md) systems.
When adding subgraphs, you need to define how the parent graph and the subgraph communicate:
* [Shared state schemas](#shared-state-schemas) — parent and subgraph have **shared state keys** in their state [schemas](../concepts/low_level.md#state)
* [Different state schemas](#different-state-schemas) — **no shared state keys** in parent and subgraph [schemas](../concepts/low_level.md#state)
## Setup
```bash
pip install -U langgraph
```
!!! tip "Set up LangSmith for LangGraph development"
Sign up for [LangSmith](https://smith.langchain.com) to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com).
## Shared state schemas
A common case is for the parent graph and subgraph to communicate over a shared state key (channel) in the [schema](../concepts/low_level.md#state). For example, in [multi-agent](../concepts/multi_agent.md) systems, the agents often communicate over a shared [messages](https://langchain-ai.github.io/langgraph/concepts/low_level.md#why-use-messages) key.
If your subgraph shares state keys with the parent graph, you can follow these steps to add it to your graph:
1. Define the subgraph workflow (`subgraph_builder` in the example below) and compile it
2. Pass compiled subgraph to the `.add_node` method when defining the parent graph workflow
```python
from typing_extensions import TypedDict
from langgraph.graph.state import StateGraph, START
class State(TypedDict):
foo: str
# Subgraph
def subgraph_node_1(state: State):
return {"foo": "hi! " + state["foo"]}
subgraph_builder = StateGraph(State)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph = subgraph_builder.compile()
# Parent graph
builder = StateGraph(State)
builder.add_node("node_1", subgraph)
builder.add_edge(START, "node_1")
graph = builder.compile()
```
??? example "Full example: shared state schemas"
```python
from typing_extensions import TypedDict
from langgraph.graph.state import StateGraph, START
# Define subgraph
class SubgraphState(TypedDict):
foo: str # (1)!
bar: str # (2)!
def subgraph_node_1(state: SubgraphState):
return {"bar": "bar"}
def subgraph_node_2(state: SubgraphState):
# note that this node is using a state key ('bar') that is only available in the subgraph
# and is sending update on the shared state key ('foo')
return {"foo": state["foo"] + state["bar"]}
subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_node(subgraph_node_2)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph_builder.add_edge("subgraph_node_1", "subgraph_node_2")
subgraph = subgraph_builder.compile()
# Define parent graph
class ParentState(TypedDict):
foo: str
def node_1(state: ParentState):
return {"foo": "hi! " + state["foo"]}
builder = StateGraph(ParentState)
builder.add_node("node_1", node_1)
builder.add_node("node_2", subgraph)
builder.add_edge(START, "node_1")
builder.add_edge("node_1", "node_2")
graph = builder.compile()
for chunk in graph.stream({"foo": "foo"}):
print(chunk)
```
1. This key is shared with the parent graph state
2. This key is private to the `SubgraphState` and is not visible to the parent graph
```
{'node_1': {'foo': 'hi! foo'}}
{'node_2': {'foo': 'hi! foobar'}}
```
## Different state schemas
For more complex systems you might want to define subgraphs that have a **completely different schema** from the parent graph (no shared keys). For example, you might want to keep a private message history for each of the agents in a [multi-agent](../concepts/multi_agent.md) system.
If that's the case for your application, you need to define a node **function that invokes the subgraph**. This function needs to transform the input (parent) state to the subgraph state before invoking the subgraph, and transform the results back to the parent state before returning the state update from the node.
```python
from typing_extensions import TypedDict
from langgraph.graph.state import StateGraph, START
class SubgraphState(TypedDict):
bar: str
# Subgraph
def subgraph_node_1(state: SubgraphState):
return {"bar": "hi! " + state["bar"]}
subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph = subgraph_builder.compile()
# Parent graph
class State(TypedDict):
foo: str
def call_subgraph(state: State):
subgraph_output = subgraph.invoke({"bar": state["foo"]}) # (1)!
return {"foo": subgraph_output["bar"]} # (2)!
builder = StateGraph(State)
builder.add_node("node_1", call_subgraph)
builder.add_edge(START, "node_1")
graph = builder.compile()
```
1. Transform the state to the subgraph state
2. Transform response back to the parent state
??? example "Full example: different state schemas"
```python
from typing_extensions import TypedDict
from langgraph.graph.state import StateGraph, START
# Define subgraph
class SubgraphState(TypedDict):
# note that none of these keys are shared with the parent graph state
bar: str
baz: str
def subgraph_node_1(state: SubgraphState):
return {"baz": "baz"}
def subgraph_node_2(state: SubgraphState):
return {"bar": state["bar"] + state["baz"]}
subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_node(subgraph_node_2)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph_builder.add_edge("subgraph_node_1", "subgraph_node_2")
subgraph = subgraph_builder.compile()
# Define parent graph
class ParentState(TypedDict):
foo: str
def node_1(state: ParentState):
return {"foo": "hi! " + state["foo"]}
def node_2(state: ParentState):
response = subgraph.invoke({"bar": state["foo"]}) # (1)!
return {"foo": response["bar"]} # (2)!
builder = StateGraph(ParentState)
builder.add_node("node_1", node_1)
builder.add_node("node_2", node_2)
builder.add_edge(START, "node_1")
builder.add_edge("node_1", "node_2")
graph = builder.compile()
for chunk in graph.stream({"foo": "foo"}, subgraphs=True):
print(chunk)
```
1. Transform the state to the subgraph state
2. Transform response back to the parent state
```
((), {'node_1': {'foo': 'hi! foo'}})
(('node_2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7',), {'grandchild_1': {'my_grandchild_key': 'hi Bob, how are you'}})
(('node_2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7',), {'grandchild_2': {'bar': 'hi! foobaz'}})
((), {'node_2': {'foo': 'hi! foobaz'}})
```
??? example "Full example: different state schemas (two levels of subgraphs)"
This is an example with two levels of subgraphs: parent -> child -> grandchild.
```python
# Grandchild graph
from typing_extensions import TypedDict
from langgraph.graph.state import StateGraph, START, END
class GrandChildState(TypedDict):
my_grandchild_key: str
def grandchild_1(state: GrandChildState) -> GrandChildState:
# NOTE: child or parent keys will not be accessible here
return {"my_grandchild_key": state["my_grandchild_key"] + ", how are you"}
grandchild = StateGraph(GrandChildState)
grandchild.add_node("grandchild_1", grandchild_1)
grandchild.add_edge(START, "grandchild_1")
grandchild.add_edge("grandchild_1", END)
grandchild_graph = grandchild.compile()
# Child graph
class ChildState(TypedDict):
my_child_key: str
def call_grandchild_graph(state: ChildState) -> ChildState:
# NOTE: parent or grandchild keys won't be accessible here
grandchild_graph_input = {"my_grandchild_key": state["my_child_key"]} # (1)!
grandchild_graph_output = grandchild_graph.invoke(grandchild_graph_input)
return {"my_child_key": grandchild_graph_output["my_grandchild_key"] + " today?"} # (2)!
child = StateGraph(ChildState)
child.add_node("child_1", call_grandchild_graph) # (3)!
child.add_edge(START, "child_1")
child.add_edge("child_1", END)
child_graph = child.compile()
# Parent graph
class ParentState(TypedDict):
my_key: str
def parent_1(state: ParentState) -> ParentState:
# NOTE: child or grandchild keys won't be accessible here
return {"my_key": "hi " + state["my_key"]}
def parent_2(state: ParentState) -> ParentState:
return {"my_key": state["my_key"] + " bye!"}
def call_child_graph(state: ParentState) -> ParentState:
child_graph_input = {"my_child_key": state["my_key"]} # (4)!
child_graph_output = child_graph.invoke(child_graph_input)
return {"my_key": child_graph_output["my_child_key"]} # (5)!
parent = StateGraph(ParentState)
parent.add_node("parent_1", parent_1)
parent.add_node("child", call_child_graph) # (6)!
parent.add_node("parent_2", parent_2)
parent.add_edge(START, "parent_1")
parent.add_edge("parent_1", "child")
parent.add_edge("child", "parent_2")
parent.add_edge("parent_2", END)
parent_graph = parent.compile()
for chunk in parent_graph.stream({"my_key": "Bob"}, subgraphs=True):
print(chunk)
```
1. We're transforming the state from the child state channels (`my_child_key`) to the child state channels (`my_grandchild_key`)
2. We're transforming the state from the grandchild state channels (`my_grandchild_key`) back to the child state channels (`my_child_key`)
3. We're passing a function here instead of just compiled graph (`grandchild_graph`)
4. We're transforming the state from the parent state channels (`my_key`) to the child state channels (`my_child_key`)
5. We're transforming the state from the child state channels (`my_child_key`) back to the parent state channels (`my_key`)
6. We're passing a function here instead of just a compiled graph (`child_graph`)
```
((), {'parent_1': {'my_key': 'hi Bob'}})
(('child:2e26e9ce-602f-862c-aa66-1ea5a4655e3b', 'child_1:781bb3b1-3971-84ce-810b-acf819a03f9c'), {'grandchild_1': {'my_grandchild_key': 'hi Bob, how are you'}})
(('child:2e26e9ce-602f-862c-aa66-1ea5a4655e3b',), {'child_1': {'my_child_key': 'hi Bob, how are you today?'}})
((), {'child': {'my_key': 'hi Bob, how are you today?'}})
((), {'parent_2': {'my_key': 'hi Bob, how are you today? bye!'}})
```
## Add persistence
You only need to **provide the checkpointer when compiling the parent graph**. LangGraph will automatically propagate the checkpointer to the child subgraphs.
```python
from langgraph.graph import START, StateGraph
from langgraph.checkpoint.memory import InMemorySaver
from typing_extensions import TypedDict
class State(TypedDict):
foo: str
# Subgraph
def subgraph_node_1(state: State):
return {"foo": state["foo"] + "bar"}
subgraph_builder = StateGraph(State)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph = subgraph_builder.compile()
# Parent graph
builder = StateGraph(State)
builder.add_node("node_1", subgraph)
builder.add_edge(START, "node_1")
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)
```
If you want the subgraph to **have its own memory**, you can compile it `with checkpointer=True`. This is useful in [multi-agent](../concepts/multi_agent.md) systems, if you want agents to keep track of their internal message histories:
```python
subgraph_builder = StateGraph(...)
subgraph = subgraph_builder.compile(checkpointer=True)
```
## View subgraph state
When you enable [persistence](../concepts/persistence.md), you can [inspect the graph state](../concepts/persistence.md#checkpoints) (checkpoint) via `graph.get_state(config)`. To view the subgraph state, you can use `graph.get_state(config, subgraphs=True)`.
!!! important "Available **only** when interrupted"
Subgraph state can only be viewed **when the subgraph is interrupted**. Once you resume the graph, you won't be able to access the subgraph state.
??? example "View interrupted subgraph state"
```python
from langgraph.graph import START, StateGraph
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt, Command
from typing_extensions import TypedDict
class State(TypedDict):
foo: str
# Subgraph
def subgraph_node_1(state: State):
value = interrupt("Provide value:")
return {"foo": state["foo"] + value}
subgraph_builder = StateGraph(State)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph = subgraph_builder.compile()
# Parent graph
builder = StateGraph(State)
builder.add_node("node_1", subgraph)
builder.add_edge(START, "node_1")
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "1"}}
graph.invoke({"foo": ""}, config)
parent_state = graph.get_state(config)
subgraph_state = graph.get_state(config, subgraphs=True).tasks[0].state # (1)!
# resume the subgraph
graph.invoke(Command(resume="bar"), config)
```
1. This will be available only when the subgraph is interrupted. Once you resume the graph, you won't be able to access the subgraph state.
## Stream subgraph outputs
To include outputs from subgraphs in the streamed outputs, you can set `subgraphs=True` in the `.stream()` method of the parent graph. This will stream outputs from both the parent graph and any subgraphs.
```python
for chunk in graph.stream(
{"foo": "foo"},
subgraphs=True, # (1)!
stream_mode="updates",
):
print(chunk)
```
1. Set `subgraphs=True` to stream outputs from subgraphs.
??? example "Stream from subgraphs"
```python
from typing_extensions import TypedDict
from langgraph.graph.state import StateGraph, START
# Define subgraph
class SubgraphState(TypedDict):
foo: str
bar: str
def subgraph_node_1(state: SubgraphState):
return {"bar": "bar"}
def subgraph_node_2(state: SubgraphState):
# note that this node is using a state key ('bar') that is only available in the subgraph
# and is sending update on the shared state key ('foo')
return {"foo": state["foo"] + state["bar"]}
subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_node(subgraph_node_2)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph_builder.add_edge("subgraph_node_1", "subgraph_node_2")
subgraph = subgraph_builder.compile()
# Define parent graph
class ParentState(TypedDict):
foo: str
def node_1(state: ParentState):
return {"foo": "hi! " + state["foo"]}
builder = StateGraph(ParentState)
builder.add_node("node_1", node_1)
builder.add_node("node_2", subgraph)
builder.add_edge(START, "node_1")
builder.add_edge("node_1", "node_2")
graph = builder.compile()
for chunk in graph.stream(
{"foo": "foo"},
stream_mode="updates",
subgraphs=True, # (1)!
):
print(chunk)
```
1. Set `subgraphs=True` to stream outputs from subgraphs.
```
((), {'node_1': {'foo': 'hi! foo'}})
(('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_1': {'bar': 'bar'}})
(('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_2': {'foo': 'hi! foobar'}})
((), {'node_2': {'foo': 'hi! foobar'}})
+2
View File
@@ -344,6 +344,8 @@ tool_node.invoke({"messages": [...]})
## Tool customization
For more control over tool behavior, use the `@tool` decorator.
### Parameter descriptions
Auto-generate descriptions from docstrings:
@@ -1,29 +0,0 @@
/*
* This file is used to override the navigation title for the LangGraph documentation.
* It is used to change the title of the first and second items in the navigation menu.
* The first item is the Guides page, and the second item is the Reference page.
*/
.md-nav--primary > .md-nav__list > .md-nav__item:nth-child(1) > .md-nav__link .md-ellipsis {
visibility: hidden !important;
position: relative;
}
.md-nav--primary > .md-nav__list > .md-nav__item:nth-child(1) > .md-nav__link .md-ellipsis::after {
content: "Home";
visibility: visible;
position: absolute;
left: 0;
}
.md-nav--primary > .md-nav__list > .md-nav__item:nth-child(2) > .md-nav__link .md-ellipsis {
visibility: hidden !important;
position: relative;
}
.md-nav--primary > .md-nav__list > .md-nav__item:nth-child(2) > .md-nav__link .md-ellipsis::after {
content: "Home";
visibility: visible;
position: absolute;
left: 0;
}
+2 -2
View File
@@ -17,7 +17,7 @@ Safari blocks plain-HTTP traffic on localhost. When running Studio with `langgra
```shell
# Requires @langchain/langgraph-cli>=0.0.26
npx @langchain/langgraph-cli dev
npx @langchain/langgraph-cli dev --tunnel
```
The command outputs a URL in this format:
@@ -55,7 +55,7 @@ Disable Brave Shields for LangSmith using the Brave icon in the URL bar.
```shell
# Requires @langchain/langgraph-cli>=0.0.26
npx @langchain/langgraph-cli dev
npx @langchain/langgraph-cli dev --tunnel
```
The command outputs a URL in this format:
@@ -112,7 +112,7 @@ This tells the graph to terminate after running the chatbot node.
## 6. Compile the graph
Before running the graph, we'll need to compile it. We can do so by calling `compile()`
on the graph builder. This creates a `CompiledGraph` we can invoke on our state.
on the graph builder. This creates a `CompiledStateGraph` we can invoke on our state.
```python
graph = graph_builder.compile()
@@ -23,12 +23,16 @@ pip install -U langchain-tavily
Configure your environment with your search engine API key:
```bash
```python
def _set_env(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
_set_env("TAVILY_API_KEY")
```
```
TAVILY_API_KEY: ········
os.environ["TAVILY_API_KEY"]: "········"
```
## 3. Define the tool
File diff suppressed because one or more lines are too long
@@ -0,0 +1,812 @@
# Multi-agent supervisor
[**Supervisor**](../../concepts/multi_agent.md#supervisor) is a multi-agent architecture where **specialized** agents are coordinated by a central **supervisor agent**. The supervisor agent controls all communication flow and task delegation, making decisions about which agent to invoke based on the current context and task requirements.
In this tutorial, you will build a supervisor system with two agents — a research and a math expert. By the end of the tutorial you will:
1. Build specialized research and math agents
2. Build a supervisor for orchestrating them with the prebuilt [`langgraph-supervisor`](https://langchain-ai.github.io/langgraph/agents/multi-agent/#supervisor)
3. Build a supervisor from scratch
4. Implement advanced task delegation
![diagram](assets/diagram.png)
## Setup
First, let's install required packages and set our API keys
```python
%%capture --no-stderr
%pip install -U langgraph langgraph-supervisor langchain-tavily "langchain[openai]"
```
```python
import getpass
import os
def _set_if_undefined(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"Please provide your {var}")
_set_if_undefined("OPENAI_API_KEY")
_set_if_undefined("TAVILY_API_KEY")
```
!!! tip
Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. [LangSmith](https://docs.smith.langchain.com) lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph.
## 1. Create worker agents
First, let's create our specialized worker agents — research agent and math agent:
* Research agent will have access to a web search tool using [Tavily API](https://tavily.com/)
* Math agent will have access to simple math tools (`add`, `multiply`, `divide`)
### Research agent
For web search, we will use `TavilySearch` tool from `langchain-tavily`:
```python
from langchain_tavily import TavilySearch
web_search = TavilySearch(max_results=3)
web_search_results = web_search.invoke("who is the mayor of NYC?")
print(web_search_results["results"][0]["content"])
```
**Output:**
```
Find events, attractions, deals, and more at nyctourism.com Skip Main Navigation Menu The Official Website of the City of New York Text Size Powered by Translate SearchSearch Primary Navigation The official website of NYC Home NYC Resources NYC311 Office of the Mayor Events Connect Jobs Search Office of the Mayor | Mayor's Bio | City of New York Secondary Navigation MayorBiographyNewsOfficials Eric L. Adams 110th Mayor of New York City Mayor Eric Adams has served the people of New York City as an NYPD officer, State Senator, Brooklyn Borough President, and now as the 110th Mayor of the City of New York. Mayor Eric Adams has served the people of New York City as an NYPD officer, State Senator, Brooklyn Borough President, and now as the 110th Mayor of the City of New York. He gave voice to a diverse coalition of working families in all five boroughs and is leading the fight to bring back New York City's economy, reduce inequality, improve public safety, and build a stronger, healthier city that delivers for all New Yorkers. As the representative of one of the nation's largest counties, Eric fought tirelessly to grow the local economy, invest in schools, reduce inequality, improve public safety, and advocate for smart policies and better government that delivers for all New Yorkers.
```
To create individual worker agents, we will use LangGraph's prebuilt [agent](../../agents/agents.md).
```python
from langgraph.prebuilt import create_react_agent
research_agent = create_react_agent(
model="openai:gpt-4.1",
tools=[web_search],
prompt=(
"You are a research agent.\n\n"
"INSTRUCTIONS:\n"
"- Assist ONLY with research-related tasks, DO NOT do any math\n"
"- After you're done with your tasks, respond to the supervisor directly\n"
"- Respond ONLY with the results of your work, do NOT include ANY other text."
),
name="research_agent",
)
```
Let's [run the agent](../../agents/run_agents.md) to verify that it behaves as expected.
!!! note "We'll use `pretty_print_messages` helper to render the streamed agent outputs nicely"
```python
from langchain_core.messages import convert_to_messages
def pretty_print_message(message, indent=False):
pretty_message = message.pretty_repr(html=True)
if not indent:
print(pretty_message)
return
indented = "\n".join("\t" + c for c in pretty_message.split("\n"))
print(indented)
def pretty_print_messages(update, last_message=False):
is_subgraph = False
if isinstance(update, tuple):
ns, update = update
# skip parent graph updates in the printouts
if len(ns) == 0:
return
graph_id = ns[-1].split(":")[0]
print(f"Update from subgraph {graph_id}:")
print("\n")
is_subgraph = True
for node_name, node_update in update.items():
update_label = f"Update from node {node_name}:"
if is_subgraph:
update_label = "\t" + update_label
print(update_label)
print("\n")
messages = convert_to_messages(node_update["messages"])
if last_message:
messages = messages[-1:]
for m in messages:
pretty_print_message(m, indent=is_subgraph)
print("\n")
```
```python
from langchain_core.messages import convert_to_messages
def pretty_print_message(message, indent=False):
pretty_message = message.pretty_repr(html=True)
if not indent:
print(pretty_message)
return
indented = "\n".join("\t" + c for c in pretty_message.split("\n"))
print(indented)
def pretty_print_messages(update, last_message=False):
is_subgraph = False
if isinstance(update, tuple):
ns, update = update
# skip parent graph updates in the printouts
if len(ns) == 0:
return
graph_id = ns[-1].split(":")[0]
print(f"Update from subgraph {graph_id}:")
print("\n")
is_subgraph = True
for node_name, node_update in update.items():
update_label = f"Update from node {node_name}:"
if is_subgraph:
update_label = "\t" + update_label
print(update_label)
print("\n")
messages = convert_to_messages(node_update["messages"])
if last_message:
messages = messages[-1:]
for m in messages:
pretty_print_message(m, indent=is_subgraph)
print("\n")
```
```python
for chunk in research_agent.stream(
{"messages": [{"role": "user", "content": "who is the mayor of NYC?"}]}
):
pretty_print_messages(chunk)
```
**Output:**
```
Update from node agent:
================================== Ai Message ==================================
Name: research_agent
Tool Calls:
tavily_search (call_U748rQhQXT36sjhbkYLSXQtJ)
Call ID: call_U748rQhQXT36sjhbkYLSXQtJ
Args:
query: current mayor of New York City
search_depth: basic
Update from node tools:
================================= Tool Message ==================================
Name: tavily_search
{"query": "current mayor of New York City", "follow_up_questions": null, "answer": null, "images": [], "results": [{"title": "List of mayors of New York City - Wikipedia", "url": "https://en.wikipedia.org/wiki/List_of_mayors_of_New_York_City", "content": "The mayor of New York City is the chief executive of the Government of New York City, as stipulated by New York City's charter.The current officeholder, the 110th in the sequence of regular mayors, is Eric Adams, a member of the Democratic Party.. During the Dutch colonial period from 1624 to 1664, New Amsterdam was governed by the Director of Netherland.", "score": 0.9039154, "raw_content": null}, {"title": "Office of the Mayor | Mayor's Bio | City of New York - NYC.gov", "url": "https://www.nyc.gov/office-of-the-mayor/bio.page", "content": "Mayor Eric Adams has served the people of New York City as an NYPD officer, State Senator, Brooklyn Borough President, and now as the 110th Mayor of the City of New York. He gave voice to a diverse coalition of working families in all five boroughs and is leading the fight to bring back New York City's economy, reduce inequality, improve", "score": 0.8405867, "raw_content": null}, {"title": "Eric Adams - Wikipedia", "url": "https://en.wikipedia.org/wiki/Eric_Adams", "content": "Eric Leroy Adams (born September 1, 1960) is an American politician and former police officer who has served as the 110th mayor of New York City since 2022. Adams was an officer in the New York City Transit Police and then the New York City Police Department (```
```
### Math agent
For math agent tools we will use [vanilla Python functions](../../how-tos/tool-calling.md#define-a-tool):
```python
def add(a: float, b: float):
"""Add two numbers."""
return a + b
def multiply(a: float, b: float):
"""Multiply two numbers."""
return a * b
def divide(a: float, b: float):
"""Divide two numbers."""
return a / b
math_agent = create_react_agent(
model="openai:gpt-4.1",
tools=[add, multiply, divide],
prompt=(
"You are a math agent.\n\n"
"INSTRUCTIONS:\n"
"- Assist ONLY with math-related tasks\n"
"- After you're done with your tasks, respond to the supervisor directly\n"
"- Respond ONLY with the results of your work, do NOT include ANY other text."
),
name="math_agent",
)
```
Let's run the math agent:
```python
for chunk in math_agent.stream(
{"messages": [{"role": "user", "content": "what's (3 + 5) x 7"}]}
):
pretty_print_messages(chunk)
```
**Output:**
```
Update from node agent:
================================== Ai Message ==================================
Name: math_agent
Tool Calls:
add (call_p6OVLDHB4LyCNCxPOZzWR15v)
Call ID: call_p6OVLDHB4LyCNCxPOZzWR15v
Args:
a: 3
b: 5
Update from node tools:
================================= Tool Message ==================================
Name: add
8.0
Update from node agent:
================================== Ai Message ==================================
Name: math_agent
Tool Calls:
multiply (call_EoaWHMLFZAX4AkajQCtZvbli)
Call ID: call_EoaWHMLFZAX4AkajQCtZvbli
Args:
a: 8
b: 7
Update from node tools:
================================= Tool Message ==================================
Name: multiply
56.0
Update from node agent:
================================== Ai Message ==================================
Name: math_agent
56
```
## 2. Create supervisor with `langgraph-supervisor`
To implement out multi-agent system, we will use [`create_supervisor`][langgraph_supervisor.supervisor.create_supervisor] from the prebuilt `langgraph-supervisor` library:
```python
from langgraph_supervisor import create_supervisor
from langchain.chat_models import init_chat_model
supervisor = create_supervisor(
model=init_chat_model("openai:gpt-4.1"),
agents=[research_agent, math_agent],
prompt=(
"You are a supervisor managing two agents:\n"
"- a research agent. Assign research-related tasks to this agent\n"
"- a math agent. Assign math-related tasks to this agent\n"
"Assign work to one agent at a time, do not call agents in parallel.\n"
"Do not do any work yourself."
),
add_handoff_back_messages=True,
output_mode="full_history",
).compile()
```
```python
from IPython.display import display, Image
display(Image(supervisor.get_graph().draw_mermaid_png()))
```
![Graph](assets/output.png)
**Note:** When you run this code, it will generate and display a visual representation of the supervisor graph showing the flow between the supervisor and worker agents.
Let's now run it with a query that requires both agents:
* research agent will look up the necessary GDP information
* math agent will perform division to find the percentage of NY state GDP, as requested
```python
for chunk in supervisor.stream(
{
"messages": [
{
"role": "user",
"content": "find US and New York state GDP in 2024. what % of US GDP was New York state?",
}
]
},
):
pretty_print_messages(chunk, last_message=True)
final_message_history = chunk["supervisor"]["messages"]
```
**Output:**
```
Update from node supervisor:
================================= Tool Message ==================================
Name: transfer_to_research_agent
Successfully transferred to research_agent
Update from node research_agent:
================================= Tool Message ==================================
Name: transfer_back_to_supervisor
Successfully transferred back to supervisor
Update from node supervisor:
================================= Tool Message ==================================
Name: transfer_to_math_agent
Successfully transferred to math_agent
Update from node math_agent:
================================= Tool Message ==================================
Name: transfer_back_to_supervisor
Successfully transferred back to supervisor
Update from node supervisor:
================================== Ai Message ==================================
Name: supervisor
In 2024, the US GDP was $29.18 trillion and New York State's GDP was $2.297 trillion. New York State accounted for approximately 7.87% of the total US GDP in 2024.
```
## 3. Create supervisor from scratch
Let's now implement this same multi-agent system from scratch. We will need to:
1. [Set up how the supervisor communicates](#set-up-agent-communication) with individual agents
2. [Create the supervisor agent](#create-supervisor-agent)
3. Combine supervisor and worker agents into a [single multi-agent graph](#create-multi-agent-graph).
### Set up agent communication
We will need to define a way for the supervisor agent to communicate with the worker agents. A common way to implement this in multi-agent architectures is using **handoffs**, where one agent *hands off* control to another. Handoffs allow you to specify:
- **destination**: target agent to transfer to
- **payload**: information to pass to that agent
We will implement handoffs via **handoff tools** and give these tools to the supervisor agent: when the supervisor calls these tools, it will hand off control to a worker agent, passing the full message history to that agent.
```python
from typing import Annotated
from langchain_core.tools import tool, InjectedToolCallId
from langgraph.prebuilt import InjectedState
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.types import Command
def create_handoff_tool(*, agent_name: str, description: str | None = None):
name = f"transfer_to_{agent_name}"
description = description or f"Ask {agent_name} for help."
@tool(name, description=description)
def handoff_tool(
state: Annotated[MessagesState, InjectedState],
tool_call_id: Annotated[str, InjectedToolCallId],
) -> Command:
tool_message = {
"role": "tool",
"content": f"Successfully transferred to {agent_name}",
"name": name,
"tool_call_id": tool_call_id,
}
# highlight-next-line
return Command(
# highlight-next-line
goto=agent_name, # (1)!
# highlight-next-line
update={**state, "messages": state["messages"] + [tool_message]}, # (2)!
# highlight-next-line
graph=Command.PARENT, # (3)!
)
return handoff_tool
# Handoffs
assign_to_research_agent = create_handoff_tool(
agent_name="research_agent",
description="Assign task to a researcher agent.",
)
assign_to_math_agent = create_handoff_tool(
agent_name="math_agent",
description="Assign task to a math agent.",
)
```
1. Name of the agent or node to hand off to.
2. Take the agent's messages and add them to the parent's state as part of the handoff. The next agent will see the parent state.
3. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
### Create supervisor agent
Then, let's create the supervisor agent with the handoff tools we just defined. We will use the prebuilt [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]:
```python
supervisor_agent = create_react_agent(
model="openai:gpt-4.1",
tools=[assign_to_research_agent, assign_to_math_agent],
prompt=(
"You are a supervisor managing two agents:\n"
"- a research agent. Assign research-related tasks to this agent\n"
"- a math agent. Assign math-related tasks to this agent\n"
"Assign work to one agent at a time, do not call agents in parallel.\n"
"Do not do any work yourself."
),
name="supervisor",
)
```
### Create multi-agent graph
Putting this all together, let's create a graph for our overall multi-agent system. We will add the supervisor and the individual agents as subgraph [nodes](../../concepts/low_level.md#nodes).
```python
from langgraph.graph import END
# Define the multi-agent supervisor graph
supervisor = (
StateGraph(MessagesState)
# NOTE: `destinations` is only needed for visualization and doesn't affect runtime behavior
.add_node(supervisor_agent, destinations=("research_agent", "math_agent", END))
.add_node(research_agent)
.add_node(math_agent)
.add_edge(START, "supervisor")
# always return back to the supervisor
.add_edge("research_agent", "supervisor")
.add_edge("math_agent", "supervisor")
.compile()
)
```
Notice that we've added explicit [edges](../../concepts/low_level.md#edges) from worker agents back to the supervisor — this means that they are guaranteed to return control back to the supervisor. If you want the agents to respond directly to the user (i.e., turn the system into a router, you can remove these edges).
```python
from IPython.display import display, Image
display(Image(supervisor.get_graph().draw_mermaid_png()))
```
![Graph](assets/multi-output.png)
**Note:** When you run this code, it will generate and display a visual representation of the multi-agent supervisor graph showing the flow between the supervisor and worker agents.
With the multi-agent graph created, let's now run it!
```python
for chunk in supervisor.stream(
{
"messages": [
{
"role": "user",
"content": "find US and New York state GDP in 2024. what % of US GDP was New York state?",
}
]
},
):
pretty_print_messages(chunk, last_message=True)
final_message_history = chunk["supervisor"]["messages"]
```
**Output:**
```
Update from node supervisor:
================================= Tool Message ==================================
Name: transfer_to_research_agent
Successfully transferred to research_agent
Update from node research_agent:
================================== Ai Message ==================================
Name: research_agent
- US GDP in 2024 is projected to be about $28.18 trillion USD (Statista; CBO projection).
- New York State's nominal GDP for 2024 is estimated at approximately $2.16 trillion USD (various economic reports).
- New York State's share of US GDP in 2024 is roughly 7.7%.
Sources:
- https://www.statista.com/statistics/216985/forecast-of-us-gross-domestic-product/
- https://nyassembly.gov/Reports/WAM/2025economic_revenue/2025_report.pdf?v=1740533306
Update from node supervisor:
================================= Tool Message ==================================
Name: transfer_to_math_agent
Successfully transferred to math_agent
Update from node math_agent:
================================== Ai Message ==================================
Name: math_agent
US GDP in 2024: $28.18 trillion
New York State GDP in 2024: $2.16 trillion
Percentage of US GDP from New York State: 7.67%
Update from node supervisor:
================================== Ai Message ==================================
Name: supervisor
Here are your results:
- 2024 US GDP (projected): $28.18 trillion USD
- 2024 New York State GDP (estimated): $2.16 trillion USD
- New York State's share of US GDP: approximately 7.7%
If you need the calculation steps or sources, let me know!
```
Let's examine the full resulting message history:
```python
for message in final_message_history:
message.pretty_print()
```
**Output:**
```
================================ Human Message ==================================
find US and New York state GDP in 2024. what % of US GDP was New York state?
================================== Ai Message ===================================
Name: supervisor
Tool Calls:
transfer_to_research_agent (call_KlGgvF5ahlAbjX8d2kHFjsC3)
Call ID: call_KlGgvF5ahlAbjX8d2kHFjsC3
Args:
================================= Tool Message ==================================
Name: transfer_to_research_agent
Successfully transferred to research_agent
================================== Ai Message ===================================
Name: research_agent
Tool Calls:
tavily_search (call_ZOaTVUA6DKrOjWQldLhtrsO2)
Call ID: call_ZOaTVUA6DKrOjWQldLhtrsO2
Args:
query: US GDP 2024 estimate or actual
search_depth: advanced
tavily_search (call_QsRAasxW9K03lTlqjuhNLFbZ)
Call ID: call_QsRAasxW9K03lTlqjuhNLFbZ
Args:
query: New York state GDP 2024 estimate or actual
search_depth: advanced
================================= Tool Message ==================================
Name: tavily_search
{"query": "US GDP 2024 estimate or actual", "follow_up_questions": null, "answer": null, "images": [], "results": [{"url": "https://www.advisorperspectives.com/dshort/updates/2025/05/29/gdp-gross-domestic-product-q1-2025-second-estimate", "title": "Q1 GDP Second Estimate: Real GDP at -0.2%, Higher Than Expected", "content": "> Real gross domestic product (GDP) decreased at an annual rate of 0.2 percent in the first quarter of 2025 (January, February, and March), according to the second estimate released by the U.S. Bureau of Economic Analysis. In the fourth quarter of 2024, real GDP increased 2.4 percent. The decrease in real GDP in the first quarter primarily reflected an increase in imports, which are a subtraction in the calculation of GDP, and a decrease in government spending. These movements were partly [...] by [Harry Mamaysky](https://www.advisor```
```
!!! important
You can see that the supervisor system appends **all** of the individual agent messages (i.e., their internal tool-calling loop) to the full message history. This means that on every supervisor turn, supervisor agent sees this full history. If you want more control over:
* **how inputs are passed to agents**: you can use LangGraph [`Send()`][langgraph.types.Send] primitive to directly send data to the worker agents during the handoff. See the [task delegation](#4-create-delegation-tasks) example below
* **how agent outputs are added**: you can control how much of the agent's internal message history is added to the overall supervisor message history by wrapping the agent in a separate node function:
```python
def call_research_agent(state):
# return agent's final response,
# excluding inner monologue
response = research_agent.invoke(state)
# highlight-next-line
return {"messages": response["messages"][-1]}
```
## 4. Create delegation tasks
So far the individual agents relied on **interpreting full message history** to determine their tasks. An alternative approach is to ask the supervisor to **formulate a task explicitly**. We can do so by adding a `task_description` parameter to the `handoff_tool` function.
```python
from langgraph.types import Send
def create_task_description_handoff_tool(
*, agent_name: str, description: str | None = None
):
name = f"transfer_to_{agent_name}"
description = description or f"Ask {agent_name} for help."
@tool(name, description=description)
def handoff_tool(
# this is populated by the supervisor LLM
task_description: Annotated[
str,
"Description of what the next agent should do, including all of the relevant context.",
],
# these parameters are ignored by the LLM
state: Annotated[MessagesState, InjectedState],
) -> Command:
task_description_message = {"role": "user", "content": task_description}
agent_input = {**state, "messages": [task_description_message]}
return Command(
# highlight-next-line
goto=[Send(agent_name, agent_input)],
graph=Command.PARENT,
)
return handoff_tool
assign_to_research_agent_with_description = create_task_description_handoff_tool(
agent_name="research_agent",
description="Assign task to a researcher agent.",
)
assign_to_math_agent_with_description = create_task_description_handoff_tool(
agent_name="math_agent",
description="Assign task to a math agent.",
)
supervisor_agent_with_description = create_react_agent(
model="openai:gpt-4.1",
tools=[
assign_to_research_agent_with_description,
assign_to_math_agent_with_description,
],
prompt=(
"You are a supervisor managing two agents:\n"
"- a research agent. Assign research-related tasks to this assistant\n"
"- a math agent. Assign math-related tasks to this assistant\n"
"Assign work to one agent at a time, do not call agents in parallel.\n"
"Do not do any work yourself."
),
name="supervisor",
)
supervisor_with_description = (
StateGraph(MessagesState)
.add_node(
supervisor_agent_with_description, destinations=("research_agent", "math_agent")
)
.add_node(research_agent)
.add_node(math_agent)
.add_edge(START, "supervisor")
.add_edge("research_agent", "supervisor")
.add_edge("math_agent", "supervisor")
.compile()
)
```
!!! note
We're using [`Send()`][langgraph.types.Send] primitive in the `handoff_tool`. This means that instead of receiving the full `supervisor` graph state as input, each worker agent only sees the contents of the `Send` payload. In this example, we're sending the task description as a single "human" message.
Let's now running it with the same input query:
```python
for chunk in supervisor_with_description.stream(
{
"messages": [
{
"role": "user",
"content": "find US and New York state GDP in 2024. what % of US GDP was New York state?",
}
]
},
subgraphs=True,
):
pretty_print_messages(chunk, last_message=True)
```
**Output:**
```
Update from subgraph supervisor:
Update from node agent:
================================== Ai Message ==================================
Name: supervisor
Tool Calls:
transfer_to_research_agent (call_tk8q8py8qK6MQz6Kj6mijKua)
Call ID: call_tk8q8py8qK6MQz6Kj6mijKua
Args:
task_description: Find the 2024 GDP (Gross Domestic Product) for both the United States and New York state, using the most up-to-date and reputable sources available. Provide both GDP values and cite the data sources.
Update from subgraph research_agent:
Update from node agent:
================================== Ai Message ==================================
Name: research_agent
Tool Calls:
tavily_search (call_KqvhSvOIhAvXNsT6BOwbPlRB)
Call ID: call_KqvhSvOIhAvXNsT6BOwbPlRB
Args:
query: 2024 United States GDP value from a reputable source
search_depth: advanced
tavily_search (call_kbbAWBc9KwCWKHmM5v04H88t)
Call ID: call_kbbAWBc9KwCWKHmM5v04H88t
Args:
query: 2024 New York state GDP value from a reputable source
search_depth: advanced
Update from subgraph research_agent:
Update from node tools:
================================= Tool Message ==================================
Name: tavily_search
{"query": "2024 United States GDP value from a reputable source", "follow_up_questions": null, "answer": null, "images": [], "results": [{"url": "https://www.focus-economics.com/countries/united-states/", "title": "United States Economy Overview - Focus Economics", "content": "The United States' Macroeconomic Analysis:\n------------------------------------------\n\n**Nominal GDP of USD 29,185 billion in 2024.**\n\n**Nominal GDP of USD 29,179 billion in 2024.**\n\n**GDP per capita of USD 86,635 compared to the global average of USD 10,589.**\n\n**GDP per capita of USD 86,652 compared to the global average of USD 10,589.**\n\n**Average real GDP growth of 2.5% over the last decade.**\n\n**Average real GDP growth of ```
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -49,7 +49,7 @@
"outputs": [],
"source": [
"%%capture --no-stderr\n",
"%pip install -U langgraph langchain_community langchain_anthropic langchain_experimental"
"%pip install -U langgraph langchain_community langchain_anthropic langchain-tavily langchain_experimental"
]
},
{
@@ -121,10 +121,10 @@
"from typing import Annotated, List\n",
"\n",
"from langchain_community.document_loaders import WebBaseLoader\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"from langchain_tavily import TavilySearch\n",
"from langchain_core.tools import tool\n",
"\n",
"tavily_tool = TavilySearchResults(max_results=5)\n",
"tavily_tool = TavilySearch(max_results=5)\n",
"\n",
"\n",
"@tool\n",
@@ -37,7 +37,7 @@
"outputs": [],
"source": [
"%%capture --no-stderr\n",
"%pip install -U langchain_community langchain_anthropic langchain_experimental matplotlib langgraph"
"%pip install -U langchain_community langchain_anthropic langchain-tavily langchain_experimental matplotlib langgraph"
]
},
{
@@ -92,11 +92,11 @@
"source": [
"from typing import Annotated\n",
"\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"from langchain_tavily import TavilySearch\n",
"from langchain_core.tools import tool\n",
"from langchain_experimental.utilities import PythonREPL\n",
"\n",
"tavily_tool = TavilySearchResults(max_results=5)\n",
"tavily_tool = TavilySearch(max_results=5)\n",
"\n",
"# Warning: This executes code locally, which can be unsafe when not sandboxed\n",
"\n",

Some files were not shown because too many files have changed in this diff Show More