Compare commits

..
Author SHA1 Message Date
William Fu-Hinthorn 1af6b812c4 Cycles 2025-03-28 14:29:07 -07:00
Andrew NguonlyandGitHub 575de221fe docs: Update Cloud SaaS and CLI documentation pages (#4077)
### Summary
1. Update API spec.
2. Clarify how to specify `requirements.txt` in `dependencies` list.
3. Clarify deletion policy for database.
4. Clarify resource allocation for `Production` type deployments.
5. Update supported Python versions.
2025-03-28 14:21:24 -07:00
David DuongandGitHub d2fc5f0a0f feat(sdk-js): run optimistic values mutator before any network request (#4070) 2025-03-28 21:57:33 +01:00
Tat Dat Duong 8e4b8b11ff Add docs about optimistic updates 2025-03-28 21:52:37 +01:00
Tat Dat Duong 2808a7859a Bump to 0.0.62 2025-03-28 21:39:44 +01:00
Tat Dat Duong fb3c61ea4f feat(sdk-js): run optimistic values mutator before any network request 2025-03-28 21:39:35 +01:00
David DuongandGitHub 69cecd872c release(sdk-js): 0.0.61 (#4069) 2025-03-28 15:24:38 +01:00
Tat Dat Duong 9abc1c8174 release(sdk-js): 0.0.61 2025-03-28 15:23:25 +01:00
David DuongandGitHub de90ced29d fix(sdk-js): do not await for client.runs.stream, as it is already async generator (#4068) 2025-03-28 15:22:20 +01:00
Tat Dat Duong b8b973fc0c fix(sdk-js): do not await for client.runs.stream, as it is already async generator 2025-03-28 15:15:54 +01:00
David DuongandGitHub 89e3709a2a feat(docs): cloning traces locally (#4057) 2025-03-28 14:41:00 +01:00
Arjun Natarajan a7f012a19c fix link 2025-03-28 09:37:05 -04:00
Arjun Natarajan 9b05ab6453 mkdocs yaml 2025-03-28 09:27:30 -04:00
David DuongandGitHub 5401d2ea81 feat(sdk-js): add option to manually provide implementation for shared modules (#4042) 2025-03-28 14:04:09 +01:00
Nuno CamposandGitHub 4401612aa6 Reduce the number of channels created for each node by 50% (#4064)
- Used to be 2 channels per node, it is now one per node, which is the
minimum
- Now both hard edges, conditional edges, entrypoint and conditional
entrypoint all use the same channel to trigger a node
2025-03-27 18:05:04 -07:00
Nuno Campos 49bb08a3f9 Update prebuilt test 2025-03-27 17:58:29 -07:00
Nuno Campos 4f2e9b838f Reduce the number of channels created for each node by 50%
- Used to be 2 channels per node, it is now one per node, which is the minimum
- Now both hard edges, conditional edges, entrypoint and conditional entrypoint all use the same channel to trigger a node
2025-03-27 17:47:34 -07:00
Nuno Campos d30da72f6e Reduce the number of channels created for each node by 50%
- Used to be 2 channels per node, it is now one per node, which is the minimum
- Now both hard edges, conditional edges, entrypoint and conditional entrypoint all use the same channel to trigger a node
2025-03-27 17:47:11 -07:00
William FHandGitHub 7f079adfee Update auth user type (#4062) 2025-03-27 16:09:54 -07:00
William Fu-Hinthorn 4c74af606f Update auth user type 2025-03-27 16:03:11 -07:00
Eugene YurtsevandGitHub 900824089b docs: disable link checking on push and workflow dispatch (#4061) 2025-03-27 17:33:36 -04:00
Nuno CamposandGitHub 522caa643f In Python 3.12 or above, use asyncio eager task factory (#4055)
- This is a performance improvement when calling async functions that do
not use await, as they are run immediately and never scheduled in the
loop
2025-03-27 13:36:04 -07:00
Nuno Campos ef71656f05 Fix 2025-03-27 13:14:39 -07:00
Tat Dat Duong 3ba7c7fbed Do not throw error if window is undefined due to Next 2025-03-27 21:03:54 +01:00
Tat Dat Duong 5cdab86d48 Fix object assignment 2025-03-27 20:58:39 +01:00
Nuno CamposandGitHub f5fe7e5195 Remove internal frames from stack traces (#4054)
- For exceptions raised in user code (ie. nodes or edges) remove
internal frames from the stack trace
2025-03-27 12:41:54 -07:00
Arjun Natarajan 2b728410e9 spell check 2025-03-27 14:47:27 -04:00
Arjun Natarajan 30221da4a8 docs for cloning traces locally 2025-03-27 14:38:20 -04:00
Nuno Campos 1c5a354a7d In Python 3.12 or above, use asyncio eager task factory
- This is a performance improvement when calling async functions that do not use await, as they are run immediately and never scheduled in the loop
2025-03-27 11:00:37 -07:00
Nuno Campos bfd271e00e Remove internal frames from stack traces
- For exceptions raised in user code (ie. nodes or edges) remove internal frames from the stack trace
2025-03-27 10:58:13 -07:00
Vadym BardaandGitHub 9647b1e55f langgraph: use correct type for node destination annotations (#4053)
Fixes https://github.com/langchain-ai/langgraph/issues/4051
2025-03-27 16:57:39 +00:00
Eugene YurtsevandGitHub e4aa204110 docs: add langchain llms.txt to the overview (#4047) 2025-03-27 12:33:42 -04:00
Vadym BardaandGitHub 2c29edadec langgraph: release 0.3.21 (#4050) 2025-03-27 11:38:37 -04:00
Vadym BardaandGitHub 96847e644b langgraph: add tests for remote graph interrupts (#4048) 2025-03-27 15:17:29 +00:00
Tat Dat Duong 4b102638c2 feat(sdk-js): add option to manually provide implementation for shared modules 2025-03-26 22:46:59 +01:00
7021ce3742 patch: fix return type of Topic.update (#4029)
This PR fixes the return type annotation of the `update` method from
`None` to `bool`, as the method returns a boolean value indicating
whether self.values has changed

Co-authored-by: kakaogames <kakaogames@Justin-MacBook-Pro.local>
2025-03-26 16:57:35 -04:00
Nuno CamposandGitHub d0c0aa9697 benchmark: remove some benchmarks (#4039)
Remove some benchmarks temporarily so we can fit more stuff into the
annotation
2025-03-26 13:50:53 -07:00
Vadym BardaandGitHub 520de30350 langgraph: fix interrupt deserialization in RemoteGraph (#4040) 2025-03-26 16:42:21 -04:00
Eugene Yurtsev 81c0d47363 x 2025-03-26 15:16:44 -04:00
87603d8a00 docs: add version admonitions for Interrupt and RetryPolicy (#3988)
This pull request includes changes to add version admonitions to the
documentation and update the styling for these admonitions. The most
important changes include the addition of version information to the
documentation, updates to the CSS for version admonitions, and
modifications to the `mkdocs.yml` configuration file to include the new
stylesheets.
this should solve this #3991

---------

Co-authored-by: Eugene Yurtsev <eugene@langchain.dev>
2025-03-26 13:48:54 -04:00
Vadym BardaandGitHub 7ad7329c7d docs: add codeact prebuilt (#4036) 2025-03-26 17:25:17 +00:00
Vadym BardaandGitHub e981d27f84 prebuilt: release 0.1.7 (#4034) 2025-03-26 09:22:09 -04:00
Vadym BardaandGitHub 71db4f2ad5 prebuilt: ignore updates when combining parent commands with Send (#4033) 2025-03-26 09:20:37 -04:00
Nuno CamposandGitHub 4ced277e2d Update adopters.md (#4025) 2025-03-25 18:40:02 -07:00
jessicaouandGitHub 34738fa566 Update adopters.md 2025-03-25 17:27:53 -07:00
Nuno CamposandGitHub 0286c38784 fix(sdk-js): mark schema as nullable to match python (#3928) 2025-03-25 16:11:35 -07:00
David DuongandGitHub fb5a1c4028 feat(cli): add packageManager and devEngines detection (#4024) 2025-03-25 23:43:36 +01:00
Tat Dat Duong 39d85466f8 Bump to 0.1.80 2025-03-25 23:36:33 +01:00
Tat Dat Duong 3c797529bb Avoid frozen lockfile 2025-03-25 23:32:40 +01:00
Tat Dat Duong 0d185d43ed feat(cli): add packageManager and devEngines detection 2025-03-25 23:26:04 +01:00
Eugene YurtsevandGitHub 70b8391a89 ci: use fast benchmark (#4017) 2025-03-25 17:59:18 -04:00
Eugene YurtsevandGitHub 1a37f2d5a2 sdk-py: release 0.1.59 (#4018) 2025-03-25 17:58:51 -04:00
Really HimandGitHub 949af8abe5 docs(pregel): One-line markdown formatting quick-fix (#4023)
## Description

I noticed a very minor issue in the formatting of the Concepts > Pregel
doc:
(https://langchain-ai.github.io/langgraph/concepts/pregel/#high-level-api)
(https://github.com/langchain-ai/langgraph/blob/main/docs/docs/concepts/pregel.md)

You can see in the image below that there is a python codeblock, then a
pycon block, and inside that block there is an extra
triple-backtick/code fence, and then text at the bottom, which it
appears like it is supposed to be a separate python block, like the one
above it. I.e., clearly:

```
```python
print(graph.channels)
```

is intended to be:

```python
print(graph.channels)
```

I'm pretty sure this is due to an extra whitespace character before the preceding closing code fence, which is throwing off the formatting.


![image](https://github.com/user-attachments/assets/616d72d6-652b-4599-be3e-55c47766861d)

My VS-Code/extensions can't really render the Markdown the way it appears on the Website, I think because of the tabs (Graph API vs. Functional API), but I noticed that if I remove the extra whitespace, the highlighting on the python codeblock is corrected:

BEFORE:
<img width="605" alt="Screenshot 2025-03-25 at 5 02 41 PM" src="https://github.com/user-attachments/assets/8474f103-c5ab-4c02-b22f-3d54b3363331" />

AFTER:
<img width="277" alt="Screenshot 2025-03-25 at 5 02 50 PM" src="https://github.com/user-attachments/assets/a311b87b-859c-4aba-a4d8-6b063a155d90" />


## Fix
* Remove one whitespace character that was throwing off markdown rendering
2025-03-25 17:58:37 -04:00
Eugene YurtsevandGitHub 094255c3fe docs: remove langmanus temporarily (#4022)
there's no pypi package
2025-03-25 16:46:07 -04:00
Nuno CamposandGitHub b6055ff3fe Warn when get_graph tries to draw edge that doesn't exist (#4021) 2025-03-25 12:57:31 -07:00
Nuno Campos e082ba4f85 Warn when get_graph tries to draw edge that doesn't exist 2025-03-25 12:50:47 -07:00
Vadym BardaandGitHub f36b7f61fb prebuilt: release 0.1.6 (#4020) 2025-03-25 15:49:02 -04:00
Vadym BardaandGitHub 4095f0a927 prebuilt: only combine Command.PARENT for Send gotos in ToolNode (#4019) 2025-03-25 15:47:46 -04:00
8af09714ff docs: remove unused imports from guide (#4014)
Not included in index page, and currently re-directs:
https://github.com/langchain-ai/langgraph/blob/01fed0fae27a8d71490a7f94a74942af5626da97/docs/_scripts/notebook_hooks.py#L24

---------

Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2025-03-25 15:35:54 -04:00
Eugene YurtsevandGitHub 5d7e818882 sdk: Add headers to sync client (#4012)
Add ability to pass run time headers to the sync client.
2025-03-25 14:58:33 -04:00
Eugene YurtsevandGitHub 37429ff73b docs: remove old code from workflow (#4016) 2025-03-25 14:20:04 -04:00
Tat Dat Duong dd733a3389 fix(sdk-js): mark schema as nullable to match python 2025-03-19 22:40:55 +01:00
50 changed files with 2096 additions and 807 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
run: |
{
echo 'OUTPUT<<EOF'
make -s benchmark
make -s benchmark-fast
echo EOF
} >> "$GITHUB_OUTPUT"
- name: Compare benchmarks
+1 -20
View File
@@ -63,35 +63,16 @@ jobs:
poetry-version: ${{ env.POETRY_VERSION }}
cache-key: docs
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "22"
cache: "yarn"
cache-dependency-path: docs/yarn.lock
- name: Install dependencies
run: |
yarn
poetry install --with test --with docs --no-root
poetry run pip install -U \
pytest \
pytest-check-links \
GitPython \
"git+https://github.com/benjamincburns/markdown-exec.git@cc0d39d737e5ffd4b83d23cd8729d7ea16e363c8"
# we run this installation only for internal PRs
# as GITHUB_TOKEN is not available for PRs from outside contributors
if [ -n "${GITHUB_TOKEN}" ]; then
poetry run pip install "git+https://${GITHUB_TOKEN}@github.com/langchain-ai/mkdocs-material-insiders.git"
fi
poetry run jupyter kernelspec list
poetry run python3 -m ipykernel install --user --name=python3
npm install -g tslab
poetry run tslab install --python=python3
poetry run jupyter kernelspec list
- name: Run unit tests
# Run unit tests on the docs build pipeline
run: make tests
@@ -118,7 +99,7 @@ jobs:
env:
LANGCHAIN_API_KEY: test
run: |
if [ "${{ github.event_name }}" == "schedule" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ] || ([ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == "refs/heads/main" ]); then
if [ "${{ github.event_name }}" == "schedule" ]; then
echo "Running link check on all HTML files matching notebooks in docs directory..."
poetry run pytest -v \
--check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \
+1 -1
View File
@@ -45,7 +45,7 @@ agent.invoke(
LangGraph is built for developers who want to build powerful, adaptable AI agents. Developers choose LangGraph for:
- **Reliability and controllability.** Steer agent actions with moderation checks and human-in-the-loop approvals. LangGraph persists context for long-running workflows, keeping your agents on course.
- **Low-level and extensible.** Build custom agents with fully descriptive, low-level primitives free from rigid abstractions that limit customization. Design scalable multi-agent systems, with each agent serving a specific role tailored to your use case.
- **Low-level and extensible.** Build custom agents with low-level primitives, avoiding both rigid high-level frameworks and limited DAG-only orchestrators. LangGraph supports cyclic workflows and enables multi-agent systems, with each agent tailored to your use case.
- **First-class streaming support.** With token-by-token streaming and streaming of intermediate steps, LangGraph gives users clear visibility into agent reasoning and actions as they unfold in real time.
LangGraph is trusted in production and powering agents for companies like:
+3 -3
View File
@@ -36,6 +36,6 @@ packages:
- name: "langgraph-reflection"
repo: "langchain-ai/langgraph-reflection"
description: "LangGraph agent that runs a reflection step."
- name: "langmanus"
repo: "langmanus/langmanus"
description: "A community-driven AI automation framework that builds upon the incredible work of the open source community. Our goal is to combine language models with specialized tools for tasks like web search, crawling, and Python code execution, while giving back to the community that made this possible."
- name: "langgraph-codeact"
repo: "langchain-ai/langgraph-codeact"
description: "LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling."
+4
View File
@@ -10,14 +10,17 @@ This list of companies using LangGraph and their success stories is compiled fro
| [Athena Intelligence](https://www.athenaintel.com/) | Software & Technology (GenAI Native) | Research & summarization | [Case study, 2024](https://blog.langchain.dev/customers-athena-intelligence/) |
| [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) |
| [C.H. Robinson](https://www.chrobinson.com/en-us/) | Logistics | Automation | [Case study, 2025](https://blog.langchain.dev/customers-chrobinson/) |
| [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) |
| [GitLab](https://about.gitlab.com/) | Software & Technology | Code generation | [Duo workflow docs](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/duo_workflow/) |
| [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/) |
| [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) |
| [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/) |
| [OpenRecovery](https://www.openrecovery.com/) | Healthcare | Copilot for domain-specific task | [Case study, 2024](https://blog.langchain.dev/customers-openrecovery/) |
| [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) |
| [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/) |
@@ -25,3 +28,4 @@ This list of companies using LangGraph and their success stories is compiled fro
| [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/) |
| [Vodafone](https://www.vodafone.com/) | Telecommunications | Code generation; internal search | [Case study, 2025](https://blog.langchain.dev/customers-vodafone/) |
@@ -0,0 +1,31 @@
# Testing local agents with remote traces
## Overview
A common workflow when debugging production-deployed agents is to test the same thread against a local version of the same agent, which may have modifications.
To support this, LangGraph Studio, in combination with LangSmith, allows you to clone remote threads traced in LangSmith into your locally running agent. This cloned thread can then be used to re-run specific nodes within Studio.
## Requirements
!!! info "Prerequisites"
- langgraph>=0.3.18
- langgraph-api>=0.0.32
- A thread traced in LangSmith.
- A locally running agent. See [here](../../how-tos/local-studio.md) for setup instructions.
- Note that your local agent must be using the above specified `langgraph` and `langgraph-api` versions.
- The nodes present in the remote trace must exist in at least one of the graphs in your local agent.
## Cloning Thread
First navigate to the LangSmith trace. Here you should see a button to "Run in Studio".
![Run in Studio](../img/run_in_studio.png){width=1200}
This will prompt you to enter the url that your locally running agent is accessible at. Once provided, select "Clone thread locally". If you have multiple graphs in your agent, you will also be prompted to select a graph to clone this thread under.
Once selected, a will a new thread in your local agent will be created and the thread history will be reconstruced to reflect the original trace.
Alternatively, if your trace originates from an agent deployed on LangGraph Platform, you can "View original thread" to open Studio with the actual deployed thread.
Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

+47 -24
View File
@@ -1,8 +1,6 @@
# How to integrate LangGraph into your React application
!!! info "Prerequisites"
- [LangGraph Platform](../../concepts/langgraph_platform.md)
- [LangGraph Server](../../concepts/langgraph_server.md)
!!! info "Prerequisites" - [LangGraph Platform](../../concepts/langgraph_platform.md) - [LangGraph Server](../../concepts/langgraph_server.md)
The `useStream()` React hook provides a seamless way to integrate LangGraph into your React applications. It handles all the complexities of streaming, state management, and branching logic, letting you focus on building great chat experiences.
@@ -169,10 +167,7 @@ The `useStream()` hook exposes the `interrupt` property, which will be filled wi
Learn more about interrupts in the [How to handle interrupts](../../how-tos/human_in_the_loop/wait-user-input.ipynb) guide.
```tsx
const thread = useStream<
{ messages: Message[] },
{ InterruptType: string }
>({
const thread = useStream<{ messages: Message[] }, { InterruptType: string }>({
apiUrl: "http://localhost:2024",
assistantId: "agent",
messagesKey: "messages",
@@ -182,7 +177,6 @@ if (thread.interrupt) {
return (
<div>
Interrupted! {thread.interrupt.value}
<button
type="button"
onClick={() => {
@@ -313,7 +307,7 @@ export default function App() {
onEdit={(message) =>
thread.submit(
{ messages: [message] },
{ checkpoint: parentCheckpoint },
{ checkpoint: parentCheckpoint }
)
}
/>
@@ -370,6 +364,33 @@ export default function App() {
For advanced use cases you can use the `experimental_branchTree` property to get the tree representation of the thread, which can be used to render branching controls for non-message based graphs.
### Optimistic Updates
You can optimistically update the client state before performing a network request to the agent, allowing you to provide immediate feedback to the user, such as showing the user message immediately before the agent has seen the request.
```tsx
const stream = useStream({
apiUrl: "http://localhost:2024",
assistantId: "agent",
messagesKey: "messages",
});
const handleSubmit = (text: string) => {
const newMessage = { type: "human" as const, content: text };
stream.submit(
{ messages: [newMessage] },
{
optimisticValues(prev) {
const prevMessages = prev.messages ?? [];
const newMessages = [...prevMessages, newMessage];
return { ...prev, messages: newMessages };
},
}
);
};
```
### 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.
@@ -397,21 +418,23 @@ You can also optionally specify types for different scenarios, such as:
- `UpdateType`: Type for the submit function (default: `Partial<State>`)
```tsx
const thread = useStream<State, {
UpdateType: {
messages: Message[] | Message;
context?: Record<string, unknown>;
};
InterruptType: string;
CustomEventType: {
type: "progress" | "debug";
payload: unknown;
};
ConfigurableType: {
model: string;
};
}>({
const thread = useStream<
State,
{
UpdateType: {
messages: Message[] | Message;
context?: Record<string, unknown>;
};
InterruptType: string;
CustomEventType: {
type: "progress" | "debug";
payload: unknown;
};
ConfigurableType: {
model: string;
};
}
>({
apiUrl: "http://localhost:2024",
assistantId: "agent",
messagesKey: "messages",
+294 -11
View File
@@ -22,7 +22,7 @@
"description": "A run is an invocation of a graph / assistant, with no state or memory persistence."
},
{
"name": "Crons (Enterprise-only)",
"name": "Crons (Plus tier)",
"description": "A cron is a periodic run that recurs on a given schedule. The repeats can be isolated, or share state in a thread"
},
{
@@ -805,6 +805,58 @@
}
}
},
"/threads/state/bulk": {
"post": {
"tags": [
"Threads"
],
"summary": "Bulk Update Thread State",
"description": "Create a new thread from a batch of state updates.",
"operationId": "bulk_update_thread_state_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ThreadStateBulkUpdate"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Thread"
}
}
}
},
"409": {
"description": "Conflict",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/threads/{thread_id}/state": {
"get": {
"tags": [
@@ -1342,6 +1394,21 @@
},
"name": "offset",
"in": "query"
},
{
"required": false,
"schema": {
"type": "string",
"enum": [
"pending",
"error",
"success",
"timeout",
"interrupted"
]
},
"name": "status",
"in": "query"
}
],
"responses": {
@@ -1458,7 +1525,7 @@
"/threads/{thread_id}/runs/crons": {
"post": {
"tags": [
"Crons (Enterprise-only)"
"Crons (Plus tier)"
],
"summary": "Create Thread Cron",
"description": "Create a cron to schedule runs on a thread.",
@@ -1836,6 +1903,17 @@
},
"name": "run_id",
"in": "path"
},
{
"required": false,
"schema": {
"type": "boolean",
"title": "Cancel on Disconnect",
"description": "If true, the run will be cancelled if the client disconnects.",
"default": false
},
"name": "cancel_on_disconnect",
"in": "query"
}
],
"responses": {
@@ -2032,7 +2110,7 @@
"/runs/crons": {
"post": {
"tags": [
"Crons (Enterprise-only)"
"Crons (Plus tier)"
],
"summary": "Create Cron",
"description": "Create a cron to schedule runs on new threads.",
@@ -2084,7 +2162,7 @@
"/runs/crons/search": {
"post": {
"tags": [
"Crons (Enterprise-only)"
"Crons (Plus tier)"
],
"summary": "Search Crons",
"description": "Search all active crons",
@@ -2190,6 +2268,68 @@
}
}
},
"/runs/cancel": {
"post": {
"tags": [
"Thread Runs"
],
"summary": "Cancel Runs",
"description": "Cancel one or more runs. Can cancel runs by thread ID and run IDs, or by status filter.",
"operationId": "cancel_runs_post",
"parameters": [
{
"description": "Action to take when cancelling the run. Possible values are `interrupt` or `rollback`. `interrupt` will simply cancel the run. `rollback` will cancel the run and delete the run and associated checkpoints afterwards.",
"required": false,
"schema": {
"type": "string",
"enum": [
"interrupt",
"rollback"
],
"title": "Action",
"default": "interrupt"
},
"name": "action",
"in": "query"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunsCancel"
}
}
},
"required": true
},
"responses": {
"204": {
"description": "Success - Runs cancelled"
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/runs/wait": {
"post": {
"tags": [
@@ -2373,7 +2513,7 @@
"/runs/crons/{cron_id}": {
"delete": {
"tags": [
"Crons (Enterprise-only)"
"Crons (Plus tier)"
],
"summary": "Delete Cron",
"description": "Delete a cron by ID.",
@@ -2936,7 +3076,7 @@
"type": "string",
"maxLength": 65536,
"minLength": 1,
"format": "uri",
"format": "uri-reference",
"title": "Webhook",
"description": "Webhook to call after LangGraph API call is done."
},
@@ -3216,7 +3356,11 @@
"description": "The command to run.",
"properties": {
"update": {
"type": "object",
"type": [
"object",
"array",
"null"
],
"title": "Update",
"description": "An update to the state."
},
@@ -3226,12 +3370,13 @@
"array",
"number",
"string",
"boolean",
"null"
],
"title": "Resume",
"description": "A value to pass to an interrupted node."
},
"send": {
"goto": {
"anyOf": [
{
"$ref": "#/components/schemas/Send"
@@ -3242,10 +3387,21 @@
"$ref": "#/components/schemas/Send"
}
},
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "null"
}
]
],
"title": "Goto",
"description": "Name of the node(s) to navigate to next or node(s) to be executed with a provided input."
}
}
},
@@ -3276,6 +3432,18 @@
{
"type": "object"
},
{
"type": "array"
},
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
}
@@ -3326,7 +3494,7 @@
"type": "string",
"maxLength": 65536,
"minLength": 1,
"format": "uri",
"format": "uri-reference",
"title": "Webhook",
"description": "Webhook to call after LangGraph API call is done."
},
@@ -3491,6 +3659,18 @@
{
"type": "object"
},
{
"type": "array"
},
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
}
@@ -3541,7 +3721,7 @@
"type": "string",
"maxLength": 65536,
"minLength": 1,
"format": "uri",
"format": "uri-reference",
"title": "Webhook",
"description": "Webhook to call after LangGraph API call is done."
},
@@ -3840,6 +4020,36 @@
"title": "If Exists",
"description": "How to handle duplicate creation. Must be either 'raise' (raise error if duplicate), or 'do_nothing' (return existing thread).",
"default": "raise"
},
"ttl": {
"type": "object",
"title": "TTL",
"description": "The time-to-live for the thread.",
"properties": {
"strategy": {
"type": "string",
"enum": ["delete"],
"description": "The TTL strategy. 'delete' removes the entire thread.",
"default": "delete"
},
"ttl": {
"type": "number",
"description": "The time-to-live in minutes from now until thread should be swept."
}
}
},
"supersteps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"updates": {
"type": "array",
"items": { "$ref": "#/components/schemas/ThreadSuperstepUpdate" }
}
},
"required": ["updates"]
}
}
},
"type": "object",
@@ -4028,6 +4238,43 @@
"title": "ThreadStateUpdate",
"description": "Payload for updating the state of a thread."
},
"ThreadSuperstepUpdate": {
"properties": {
"values": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object"
}
},
{
"type": "object"
},
{
"type": "null"
}
]
},
"command": {
"anyOf": [
{
"$ref": "#/components/schemas/Command"
},
{
"type": "null"
}
],
"description": "The command associated with the update."
},
"as_node": {
"type": "string",
"description": "Update the state as if this node had just executed."
}
},
"required": ["as_node"],
"type": "object"
},
"ThreadStateUpdateResponse": {
"properties": {
"checkpoint": {
@@ -4230,6 +4477,42 @@
},
"description": "Represents a single document or data entry in the graph's Store. Items are used to store cross-thread memories."
},
"RunsCancel": {
"type": "object",
"title": "RunsCancel",
"description": "Payload for cancelling runs.",
"properties": {
"status": {
"type": "string",
"enum": ["pending", "running", "all"],
"title": "Status",
"description": "Filter runs by status to cancel. Must be one of 'pending', 'running', or 'all'."
},
"thread_id": {
"type": "string",
"format": "uuid",
"title": "Thread Id",
"description": "The ID of the thread containing runs to cancel."
},
"run_ids": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"title": "Run Ids",
"description": "List of run IDs to cancel."
}
},
"oneOf": [
{
"required": ["status"]
},
{
"required": ["thread_id", "run_ids"]
}
]
},
"SearchItemsResponse": {
"type": "object",
"required": [
+2 -2
View File
@@ -42,12 +42,12 @@ The LangGraph CLI requires a JSON configuration file with the following keys:
| Key | Description |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <span style="white-space: nowrap;">`dependencies`</span> | **Required**. Array of dependencies for LangGraph Cloud API server. Dependencies can be one of the following: (1) `"."`, which will look for local Python packages, (2) `pyproject.toml`, `setup.py` or `requirements.txt` in the app directory `"./local_package"`, or (3) a package name. |
| <span style="white-space: nowrap;">`dependencies`</span> | **Required**. Array of dependencies for LangGraph Cloud API server. Dependencies can be one of the following: <ul><li>A single period (`"."`), which will look for local Python packages.</li><li>The directory path where `pyproject.toml`, `setup.py` or `requirements.txt` is located.</br></br>For example, if `requirements.txt` is located in the root of the project directory, specify `"./"`. If it's located in a subdirectory called `local_package`, specify `"./local_package"`. Do not specify the string `"requirements.txt"` itself.</li><li>A Python package name.</li></ul> |
| <span style="white-space: nowrap;">`graphs`</span> | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example: <ul><li>`./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`</li><li>`./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and creates an instance of `langgraph.graph.state.StateGraph` / `langgraph.graph.state.CompiledStateGraph`.</li></ul> |
| <span style="white-space: nowrap;">`auth`</span> | _(Added in v0.0.11)_ Auth configuration containing the path to your authentication handler. Example: `./your_package/auth.py:auth`, where `auth` is an instance of `langgraph_sdk.Auth`. See [authentication guide](../../concepts/auth.md) for details. |
| <span style="white-space: nowrap;">`env`</span> | Path to `.env` file or a mapping from environment variable to its value. |
| <span style="white-space: nowrap;">`store`</span> | Configuration for adding semantic search to the BaseStore. Contains the following fields: <ul><li>`index`: Configuration for semantic search indexing with fields:<ul><li>`embed`: Embedding provider (e.g., "openai:text-embedding-3-small") or path to custom embedding function</li><li>`dims`: Dimension size of the embedding model. Used to initialize the vector table.</li><li>`fields` (optional): List of fields to index. Defaults to `["$"]`, which means to index entire documents. Can be specific fields like `["text", "summary", "some.value"]`</li></ul></li></ul> |
| <span style="white-space: nowrap;">`python_version`</span> | `3.11` or `3.12`. Defaults to `3.11`. |
| <span style="white-space: nowrap;">`python_version`</span> | `3.11`, `3.12`, or `3.13`. Defaults to `3.11`. |
| <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;">`dockerfile_lines`</span> | Array of additional lines to add to Dockerfile following the import from parent image. |
+7 -1
View File
@@ -19,6 +19,10 @@ Resource Allocation:
| Development | 1 CPU | 1 GB | Up to 1 container |
| Production | 2 CPU | 2 GB | Up to 10 containers |
CPU and memory resources are per container.
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.
See the [how-to guide](../cloud/deployment/cloud.md#create-new-deployment) for creating a new deployment.
## Revision
@@ -35,6 +39,8 @@ When defining a graph to be deployed to LangGraph Cloud SaaS, a [checkpointer](.
There is no direct access to the database. All access to the database occurs through the LangGraph Server APIs.
The database is never deleted until the deployment itself is deleted. See [Automatic Deletion](#automatic-deletion) for additional details.
## Autoscaling
`Production` type deployments automatically scale up to 10 containers. Scaling is based on the current request load for a single container. Specifically, the autoscaling implementation scales the deployment so that each container is processing about 10 concurrent requests. For example...
@@ -57,7 +63,7 @@ Infrastructure for [deployments](#deployment) and [revisions](#revision) are pro
## LangSmith Integration
A [LangSmith](https://docs.smith.langchain.com/) tracing project is automatically created for each deployemnt. The tracing project has the same name as the deployment. When creating a deployment, the `LANGCHAIN_TRACING_V2` and `LANGCHAIN_API_KEY` environment variables do not need to be specified; they are set internally, automatically. Traces are created for each run and are emitted to the tracing project automatically.
A [LangSmith](https://docs.smith.langchain.com/) tracing project is automatically created for each deployemnt. The tracing project has the same name as the deployment. When creating a deployment, the `LANGCHAIN_TRACING` and `LANGSMITH_API_KEY`/`LANGCHAIN_API_KEY` environment variables do not need to be specified; they are set internally, automatically. Traces are created for each run and are emitted to the tracing project automatically.
When a deployment is deleted, the traces and the tracing project are not deleted.
+2 -2
View File
@@ -284,7 +284,7 @@ LangGraph provides two high-level APIs for creating a Pregel application: the [S
{'__start__': <langgraph.pregel.read.PregelNode at 0x7d05e3ba1810>,
'write_essay': <langgraph.pregel.read.PregelNode at 0x7d05e3ba14d0>,
'score_essay': <langgraph.pregel.read.PregelNode at 0x7d05e3ba1710>}
```
```
```python
print(graph.channels)
@@ -344,4 +344,4 @@ LangGraph provides two high-level APIs for creating a Pregel application: the [S
{'write_essay': <langgraph.pregel.read.PregelNode object at 0x7d05e2f9aad0>}
Channels:
{'__start__': <langgraph.channels.ephemeral_value.EphemeralValue object at 0x7d05e2c906c0>, '__end__': <langgraph.channels.last_value.LastValue object at 0x7d05e2c90c40>, '__previous__': <langgraph.channels.last_value.LastValue object at 0x7d05e1007280>}
```
```
+1
View File
@@ -300,6 +300,7 @@ LangGraph Studio is a built-in UI for visualizing, testing, and debugging your a
- [How to interact with threads in LangGraph Studio](../cloud/how-tos/threads_studio.md)
- [How to add nodes as dataset examples in LangGraph Studio](../cloud/how-tos/datasets_studio.md)
- [How to engineer prompts in LangGraph Studio](../cloud/how-tos/iterate_graph_studio.md)
- [How to test your agent against remote traces](../cloud/how-tos/clone_traces_studio.md)
## Troubleshooting
-210
View File
@@ -1,210 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "3631f2b9-aa79-472e-a9d6-9125a90ee704",
"metadata": {},
"source": [
"# How to configure multiple streaming modes at the same time"
]
},
{
"cell_type": "markdown",
"id": "858c7499-0c92-40a9-bd95-e5a5a5817e92",
"metadata": {},
"source": [
"This guide covers how to configure multiple streaming modes at the same time."
]
},
{
"cell_type": "markdown",
"id": "7c2f84f1-0751-4779-97d4-5cbb286093b7",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"First, let's install the required packages and set our API keys"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "6b4285e4-7434-4971-bde0-aabceef8ee7e",
"metadata": {},
"outputs": [],
"source": [
"%%capture --no-stderr\n",
"%pip install -U langgraph langchain-openai langchain-community"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f7f9f24a-e3d0-422b-8924-47950b2facd6",
"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": "4e48aa9e",
"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>"
]
},
{
"cell_type": "markdown",
"id": "cc82c21f",
"metadata": {},
"source": [
"## Define the graph\n",
"\n",
"We'll be using a simple ReAct agent for this guide."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "85cf2e23-29f2-40cc-b302-5377b3b49da9",
"metadata": {},
"outputs": [],
"source": [
"from typing import Literal\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"from langchain_core.runnables import ConfigurableField\n",
"from langchain_core.tools import tool\n",
"from langchain_openai import ChatOpenAI\n",
"from langgraph.prebuilt import create_react_agent\n",
"\n",
"\n",
"@tool\n",
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
" \"\"\"Use this to get weather information.\"\"\"\n",
" if city == \"nyc\":\n",
" return \"It might be cloudy in nyc\"\n",
" elif city == \"sf\":\n",
" return \"It's always sunny in sf\"\n",
" else:\n",
" raise AssertionError(\"Unknown city\")\n",
"\n",
"\n",
"tools = [get_weather]\n",
"\n",
"model = ChatOpenAI(model_name=\"gpt-4o\", temperature=0)\n",
"graph = create_react_agent(model, tools)"
]
},
{
"cell_type": "markdown",
"id": "48a7751c-3f06-452b-89f4-70267e4dd305",
"metadata": {},
"source": [
"## Stream multiple"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "e9e9ffb0-2cd5-466f-b70b-b6ed51b852d1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Receiving new event of type: debug...\n",
"{'type': 'task', 'timestamp': '2024-06-25T16:12:29.144117+00:00', 'step': 1, 'payload': {'id': '8399d8fd-4b28-515a-b0e9-1679557c0953', 'name': 'agent', 'input': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='44ff9154-9485-49c9-b679-791314cc19e3')], 'is_last_step': False}, 'triggers': ['start:agent']}}\n",
"\n",
"\n",
"\n",
"Receiving new event of type: updates...\n",
"{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_gZEyPpcgwnzsnee1HH4geKmB', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-37ca191f-f68f-4a70-8924-a40f90c8c0ed-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_gZEyPpcgwnzsnee1HH4geKmB'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71})]}}\n",
"\n",
"\n",
"\n",
"Receiving new event of type: debug...\n",
"{'type': 'task_result', 'timestamp': '2024-06-25T16:12:29.802322+00:00', 'step': 1, 'payload': {'id': '8399d8fd-4b28-515a-b0e9-1679557c0953', 'name': 'agent', 'result': [('messages', [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_gZEyPpcgwnzsnee1HH4geKmB', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-37ca191f-f68f-4a70-8924-a40f90c8c0ed-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_gZEyPpcgwnzsnee1HH4geKmB'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71})])]}}\n",
"\n",
"\n",
"\n",
"Receiving new event of type: debug...\n",
"{'type': 'task', 'timestamp': '2024-06-25T16:12:29.802738+00:00', 'step': 2, 'payload': {'id': 'f22971bf-6eff-55a2-84ab-fb97f629b133', 'name': 'tools', 'input': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='44ff9154-9485-49c9-b679-791314cc19e3'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_gZEyPpcgwnzsnee1HH4geKmB', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-37ca191f-f68f-4a70-8924-a40f90c8c0ed-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_gZEyPpcgwnzsnee1HH4geKmB'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71})], 'is_last_step': False}, 'triggers': ['branch:agent:should_continue:tools']}}\n",
"\n",
"\n",
"\n",
"Receiving new event of type: updates...\n",
"{'tools': {'messages': [ToolMessage(content=\"It's always sunny in sf\", name='get_weather', tool_call_id='call_gZEyPpcgwnzsnee1HH4geKmB')]}}\n",
"\n",
"\n",
"\n",
"Receiving new event of type: debug...\n",
"{'type': 'task_result', 'timestamp': '2024-06-25T16:12:29.806676+00:00', 'step': 2, 'payload': {'id': 'f22971bf-6eff-55a2-84ab-fb97f629b133', 'name': 'tools', 'result': [('messages', [ToolMessage(content=\"It's always sunny in sf\", name='get_weather', tool_call_id='call_gZEyPpcgwnzsnee1HH4geKmB')])]}}\n",
"\n",
"\n",
"\n",
"Receiving new event of type: debug...\n",
"{'type': 'task', 'timestamp': '2024-06-25T16:12:29.807014+00:00', 'step': 3, 'payload': {'id': '3e1a91b9-b94c-56a7-ace5-6fd8ee73fe8d', 'name': 'agent', 'input': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='44ff9154-9485-49c9-b679-791314cc19e3'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_gZEyPpcgwnzsnee1HH4geKmB', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-37ca191f-f68f-4a70-8924-a40f90c8c0ed-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_gZEyPpcgwnzsnee1HH4geKmB'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}), ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='afc3ceaa-6663-4f7a-b874-e77e5515b175', tool_call_id='call_gZEyPpcgwnzsnee1HH4geKmB')], 'is_last_step': False}, 'triggers': ['tools']}}\n",
"\n",
"\n",
"\n",
"Receiving new event of type: updates...\n",
"{'agent': {'messages': [AIMessage(content='The weather in San Francisco is currently sunny.', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'stop', 'logprobs': None}, id='run-575efeca-fdeb-4b4f-80f8-08ff177c34a5-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})]}}\n",
"\n",
"\n",
"\n",
"Receiving new event of type: debug...\n",
"{'type': 'task_result', 'timestamp': '2024-06-25T16:12:30.355658+00:00', 'step': 3, 'payload': {'id': '3e1a91b9-b94c-56a7-ace5-6fd8ee73fe8d', 'name': 'agent', 'result': [('messages', [AIMessage(content='The weather in San Francisco is currently sunny.', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3e7d703517', 'finish_reason': 'stop', 'logprobs': None}, id='run-575efeca-fdeb-4b4f-80f8-08ff177c34a5-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})])]}}\n",
"\n",
"\n",
"\n"
]
}
],
"source": [
"inputs = {\"messages\": [(\"human\", \"what's the weather in sf\")]}\n",
"async for event, chunk in graph.astream(inputs, stream_mode=[\"updates\", \"debug\"]):\n",
" print(f\"Receiving new event of type: {event}...\")\n",
" print(chunk)\n",
" print(\"\\n\\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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+19 -4
View File
@@ -1,13 +1,20 @@
# LLMs-txt for LangGraph
# LLMs-txt Overview
## Overview
LangGraph provides documentation files in the [`llms.txt`](https://llmstxt.org/) format, specifically `llms.txt` and `llms-full.txt`. These files allow large language models (LLMs) and agents to access programming documentation and APIs, particularly useful within integrated development environments (IDEs).
Below you can find a list of documentation files in the [`llms.txt`](https://llmstxt.org/) format, specifically `llms.txt` and `llms-full.txt`. These files allow large language models (LLMs) and agents to access programming documentation and APIs, particularly useful within integrated development environments (IDEs).
| Language Version | llms.txt | llms-full.txt |
|------------------|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------|
| LangGraph Python | [https://langchain-ai.github.io/langgraph/llms.txt](https://langchain-ai.github.io/langgraph/llms.txt) | [https://langchain-ai.github.io/langgraph/llms-full.txt](https://langchain-ai.github.io/langgraph/llms-full.txt) |
| LangGraph JS | [https://langchain-ai.github.io/langgraphjs/llms.txt](https://langchain-ai.github.io/langgraphjs/llms.txt) | [https://langchain-ai.github.io/langgraphjs/llms-full.txt](https://langchain-ai.github.io/langgraphjs/llms-full.txt) |
| LangChain Python | [https://python.langchain.com/llms.txt](https://python.langchain.com/llms.txt) | N/A |
| LangChain JS | [https://js.langchain.com/llms.txt](https://js.langchain.com/llms.txt) | N/A |
!!! info "Review the output"
Even with access to up-to-date documentation, current state-of-the-art models may not always generate correct code. Treat the generated code as a starting point, and always review it before shipping
code to production.
## Differences Between `llms.txt` and `llms-full.txt`
@@ -19,9 +26,17 @@ A key consideration when using `llms-full.txt` is its size. For extensive docume
## Using `llms.txt` via an MCP Server
As of March 9, 2025, IDEs [do not yet have robust native support for `llms.txt`](https://x.com/jeremyphoward/status/1902109312216129905?t=1eHFv2vdNdAckajnug0_Vw&s=19). However, you can utilize `llms.txt` effectively through an MCP server.
As of March 9, 2025, IDEs [do not yet have robust native support for `llms.txt`](https://x.com/jeremyphoward/status/1902109312216129905?t=1eHFv2vdNdAckajnug0_Vw&s=19). However, you can still use `llms.txt` effectively through an MCP server.
We provide an MCP server specifically designed to serve documentation, called [`mcpdoc`](https://github.com/langchain-ai/mcpdoc). This setup is compatible with IDEs and platforms such as Cursor, Windsurf, Claude, and Claude Code. Instructions for using `mcpdoc` with these tools are available in the repository.
### 🚀 Use the `mcpdoc` Server
We provide an **MCP server** that was designed to serve documentation for LLMs and IDEs:
👉 **[langchain-ai/mcpdoc GitHub Repository](https://github.com/langchain-ai/mcpdoc)**
This MCP server allows integrating `llms.txt` into tools like **Cursor**, **Windsurf**, **Claude**, and **Claude Code**.
📘 **Setup instructions and usage examples** are available in the repository.
## Using `llms-full.txt`
@@ -0,0 +1,38 @@
:root {
--md-admonition-icon--version-added: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h4l3 3 3-3h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2m0 16h-4.2l-.8.8-2 2-2-2-.8-.8H5V4h14z"/><path d="M11 15h2v2h-2v-2m0-10h2v8h-2V5"/></svg>');
--md-admonition-icon--version-changed: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h4l3 3 3-3h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2m0 16h-4.2l-.8.8-2 2-2-2-.8-.8H5V4h14z"/><path d="M15 11h-2V9h-2v2H9v2h2v2h2v-2h2v-2Z"/></svg>');
}
.md-typeset .admonition.version-added,
.md-typeset details.version-added {
border-color: rgb(0, 191, 165);
}
.md-typeset .version-added > .admonition-title,
.md-typeset .version-added > summary {
background-color: rgba(0, 191, 165, 0.1);
}
.md-typeset .version-added > .admonition-title::before,
.md-typeset .version-added > summary::before {
background-color: rgb(0, 191, 165);
-webkit-mask-image: var(--md-admonition-icon--version-added);
mask-image: var(--md-admonition-icon--version-added);
}
.md-typeset .admonition.version-changed,
.md-typeset details.version-changed {
border-color: rgb(100, 221, 23);
}
.md-typeset .version-changed > .admonition-title,
.md-typeset .version-changed > summary {
background-color: rgba(100, 221, 23, 0.1);
}
.md-typeset .version-changed > .admonition-title::before,
.md-typeset .version-changed > summary::before {
background-color: rgb(100, 221, 23);
-webkit-mask-image: var(--md-admonition-icon--version-changed);
mask-image: var(--md-admonition-icon--version-changed);
}
+4
View File
@@ -256,6 +256,8 @@ nav:
- cloud/how-tos/invoke_studio.md
- cloud/how-tos/threads_studio.md
- cloud/how-tos/datasets_studio.md
- cloud/how-tos/iterate_graph_studio.md
- cloud/how-tos/clone_traces_studio.md
- Concepts:
- concepts/index.md
- LangGraph:
@@ -503,3 +505,5 @@ validation:
not_found: info
copyright: >
Copyright &copy; 2025 LangChain, Inc | <a href="#__consent">Consent Preferences</a>
extra_css:
- stylesheets/version_admonitions.css
+825 -63
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -64,6 +64,8 @@ grandalf = "^0.8"
pyppeteer = "^2.0.0"
networkx = "^3.3"
autogen = { version = "^0.3.0", python = "<3.13,>=3.8" }
pytest = "^8.3.5"
pytest-check-links = "^0.10.1"
[tool.poetry.group.test]
optional = true
+32 -1
View File
@@ -1022,6 +1022,28 @@ def node_config_to_docker(
except OSError:
return False
# inspired by `package-manager-detector`
def get_pkg_manager_name():
try:
with open(config_path.parent / "package.json") as f:
pkg = json.load(f)
if (pkg_manager_name := pkg.get("packageManager")) and isinstance(
pkg_manager_name, str
):
return pkg_manager_name.lstrip("^").split("@")[0]
if (
dev_engine_name := (
(pkg.get("devEngines") or {}).get("packageManager") or {}
).get("name")
) and isinstance(dev_engine_name, str):
return dev_engine_name
return None
except Exception:
return None
npm, yarn, pnpm, bun = [
test_file("package-lock.json"),
test_file("yarn.lock"),
@@ -1038,7 +1060,16 @@ def node_config_to_docker(
elif bun:
install_cmd = "bun i"
else:
install_cmd = "npm i"
pkg_manager_name = get_pkg_manager_name()
if pkg_manager_name == "yarn":
install_cmd = "yarn install"
elif pkg_manager_name == "pnpm":
install_cmd = "pnpm i"
elif pkg_manager_name == "bun":
install_cmd = "bun i"
else:
install_cmd = "npm i"
store_config = config.get("store")
env_additional_config = (
""
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-cli"
version = "0.1.79"
version = "0.1.80"
description = "CLI for interacting with LangGraph API"
authors = []
license = "MIT"
+1 -1
View File
@@ -45,7 +45,7 @@ agent.invoke(
LangGraph is built for developers who want to build powerful, adaptable AI agents. Developers choose LangGraph for:
- **Reliability and controllability.** Steer agent actions with moderation checks and human-in-the-loop approvals. LangGraph persists context for long-running workflows, keeping your agents on course.
- **Low-level and extensible.** Build custom agents with fully descriptive, low-level primitives free from rigid abstractions that limit customization. Design scalable multi-agent systems, with each agent serving a specific role tailored to your use case.
- **Low-level and extensible.** Build custom agents with low-level primitives, avoiding both rigid high-level frameworks and limited DAG-only orchestrators. LangGraph supports cyclic workflows and enables multi-agent systems with each agent tailored to your use case.
- **First-class streaming support.** With token-by-token streaming and streaming of intermediate steps, LangGraph gives users clear visibility into agent reasoning and actions as they unfold in real time.
LangGraph is trusted in production and powering agents for companies like:
+15 -34
View File
@@ -252,27 +252,15 @@ benchmarks = (
},
),
(
"sequential_20",
create_sequential(20).compile(),
create_sequential(20).compile(),
"sequential_10",
create_sequential(10).compile(),
create_sequential(10).compile(),
{"messages": []}, # Empty list of messages
),
(
"sequential_50",
create_sequential(50).compile(),
create_sequential(50).compile(),
{"messages": []}, # Empty list of messages
),
(
"sequential_100",
create_sequential(100).compile(),
create_sequential(100).compile(),
{"messages": []}, # Empty list of messages
),
(
"sequential_200",
create_sequential(200).compile(),
create_sequential(200).compile(),
"sequential_1000",
create_sequential(1000).compile(),
create_sequential(1000).compile(),
{"messages": []}, # Empty list of messages
),
(
@@ -383,8 +371,17 @@ for name, agraph, graph, input in benchmarks:
r.bench_func(name + "_sync", run, graph, input)
# Pick a handful of graphs to measure the first event latency.
# At the moment, limiting just due to the size of the annotation on github.
GRAPHS_FOR_1st_EVENT_LATENCY = (
"sequential_1000",
"pydantic_state_25x300",
)
# First event latency
for name, agraph, graph, input in benchmarks:
if graph not in GRAPHS_FOR_1st_EVENT_LATENCY:
continue
r.bench_async_func(
name + "_first_event_latency",
arun_first_event_latency,
@@ -403,30 +400,14 @@ compilation_benchmarks = (
"sequential_1000",
create_sequential(1_000),
),
(
"sequential_10000",
create_sequential(10_000),
),
(
"pydantic_state_25x300",
pydantic_state(300),
),
(
"pydantic_state_15x600",
pydantic_state(600),
),
(
"pydantic_state_9x1200",
pydantic_state(1200),
),
(
"wide_state_15x600",
wide_state(600),
),
(
"wide_state_9x1200",
wide_state(1200),
),
)
for name, graph in compilation_benchmarks:
+1 -1
View File
@@ -62,7 +62,7 @@ class Topic(
empty.values = checkpoint
return empty
def update(self, values: Sequence[Union[Value, list[Value]]]) -> None:
def update(self, values: Sequence[Union[Value, list[Value]]]) -> bool:
current = list(self.values)
if not self.accumulate:
self.values = list[Value]()
+5
View File
@@ -499,6 +499,11 @@ class CompiledGraph(Pregel):
) -> None:
if end == END and END not in end_nodes:
end_nodes[END] = graph.add_node(self.get_output_schema(config), END)
if start not in start_nodes or end not in end_nodes:
logger.warning(
f"Could not add edge from '{start}' to '{end}' due to missing nodes"
)
return
return graph.add_edge(
start_nodes[start],
end_nodes[end],
+20 -36
View File
@@ -242,7 +242,7 @@ class StateGraph(Graph):
metadata: Optional[dict[str, Any]] = None,
input: Optional[Type[Any]] = None,
retry: Optional[RetryPolicy] = None,
destinations: Optional[Union[dict[str, str], tuple[str]]] = None,
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
) -> Self:
"""Adds a new node to the state graph.
Will take the name of the function/runnable as the node name.
@@ -267,7 +267,7 @@ class StateGraph(Graph):
metadata: Optional[dict[str, Any]] = None,
input: Optional[Type[Any]] = None,
retry: Optional[RetryPolicy] = None,
destinations: Optional[Union[dict[str, str], tuple[str]]] = None,
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
) -> Self:
"""Adds a new node to the state graph.
@@ -291,7 +291,7 @@ class StateGraph(Graph):
metadata: Optional[dict[str, Any]] = None,
input: Optional[Type[Any]] = None,
retry: Optional[RetryPolicy] = None,
destinations: Optional[Union[dict[str, str], tuple[str]]] = None,
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
) -> Self:
"""Adds a new node to the state graph.
@@ -303,7 +303,7 @@ class StateGraph(Graph):
metadata (Optional[dict[str, Any]]): The metadata associated with the node. (default: None)
input (Optional[Type[Any]]): The input schema for the node. (default: the graph's input schema)
retry (Optional[RetryPolicy]): The policy for retrying the node. (default: None)
destinations (Optional[Union[dict[str, str], tuple[str]]]): Destinations that indicate where a node can route to.
destinations (Optional[Union[dict[str, str], tuple[str, ...]]]): Destinations that indicate where a node can route to.
This is useful for edgeless graphs with nodes that return `Command` objects.
If a dict is provided, the keys will be used as the target node names and the values will be used as the labels for the edges.
If a tuple is provided, the values will be used as the target node names.
@@ -799,11 +799,11 @@ class CompiledStateGraph(CompiledGraph):
raise InvalidUpdateError(msg)
# state updaters
write_entries: list[Union[ChannelWriteEntry, ChannelWriteTupleEntry]] = [
write_entries: tuple[Union[ChannelWriteEntry, ChannelWriteTupleEntry], ...] = (
ChannelWriteTupleEntry(
mapper=_get_root if output_keys == ["__root__"] else _get_updates
)
]
),
)
# add node and output channel
if key == START:
@@ -811,20 +811,14 @@ class CompiledStateGraph(CompiledGraph):
tags=[TAG_HIDDEN],
triggers=[START],
channels=[START],
writers=[
ChannelWrite(
write_entries,
tags=[TAG_HIDDEN],
),
],
writers=[ChannelWrite(write_entries, tags=[TAG_HIDDEN])],
)
elif node is not None:
input_schema = node.input if node else self.builder.schema
input_values = {k: k for k in self.builder.schemas[input_schema]}
is_single_input = len(input_values) == 1 and "__root__" in input_values
branch_channel = f"branch:to:{key}"
self.channels[key] = EphemeralValue(Any, guard=False)
branch_channel = CHANNEL_BRANCH_TO.format(key)
self.channels[branch_channel] = EphemeralValue(Any, guard=False)
self.nodes[key] = PregelNode(
triggers=[branch_channel],
@@ -836,13 +830,8 @@ class CompiledStateGraph(CompiledGraph):
input_schema,
self.builder.type_hints[input_schema],
),
writers=[
# publish to this channel and state keys
ChannelWrite(
write_entries + [ChannelWriteEntry(key, key)],
tags=[TAG_HIDDEN],
),
],
# publish to state keys
writers=[ChannelWrite(write_entries, tags=[TAG_HIDDEN])],
metadata=node.metadata,
retry_policy=node.retry_policy,
bound=node.runnable,
@@ -852,21 +841,13 @@ class CompiledStateGraph(CompiledGraph):
def attach_edge(self, starts: Union[str, Sequence[str]], end: str) -> None:
if isinstance(starts, str):
if starts == START:
channel_name = f"start:{end}"
# register channel
self.channels[channel_name] = EphemeralValue(Any)
# subscribe to channel
self.nodes[end].triggers.append(channel_name)
# publish to channel
self.nodes[START].writers.append(
# subscribe to start channel
if end != END:
self.nodes[starts].writers.append(
ChannelWrite(
[ChannelWriteEntry(channel_name, START)], tags=[TAG_HIDDEN]
(ChannelWriteEntry(CHANNEL_BRANCH_TO.format(end), starts),)
)
)
elif end != END:
# subscribe to start channel
self.nodes[end].triggers.append(starts)
elif end != END:
channel_name = f"join:{'+'.join(starts)}:{end}"
# register channel
@@ -877,7 +858,7 @@ class CompiledStateGraph(CompiledGraph):
for start in starts:
self.nodes[start].writers.append(
ChannelWrite(
[ChannelWriteEntry(channel_name, start)], tags=[TAG_HIDDEN]
(ChannelWriteEntry(channel_name, start),), tags=[TAG_HIDDEN]
)
)
@@ -890,7 +871,7 @@ class CompiledStateGraph(CompiledGraph):
if filtered := [p for p in packets if p != END]:
writes = [
(
ChannelWriteEntry(f"branch:to:{p}", start)
ChannelWriteEntry(CHANNEL_BRANCH_TO.format(p), start)
if not isinstance(p, Send)
else p
)
@@ -1166,3 +1147,6 @@ def _get_schema(
if k in channels and isinstance(channels[k], BaseChannel)
},
)
CHANNEL_BRANCH_TO = "branch:to:{}"
+8 -2
View File
@@ -156,10 +156,16 @@ class AsyncBackgroundExecutor(AsyncContextManager):
if self.semaphore:
coro = gated(self.semaphore, coro)
if CONTEXT_NOT_SUPPORTED:
task = run_coroutine_threadsafe(coro, self.loop, name=__name__)
task = run_coroutine_threadsafe(
coro, self.loop, name=__name__, lazy=__next_tick__
)
else:
task = run_coroutine_threadsafe(
coro, self.loop, name=__name__, context=copy_context()
coro,
self.loop,
name=__name__,
context=copy_context(),
lazy=__next_tick__,
)
self.tasks[task] = (__cancel_on_exit__, __reraise_on_exit__)
task.add_done_callback(self.done)
+6 -2
View File
@@ -654,7 +654,9 @@ class RemoteGraph(PregelProtocol):
# raise interrupt or errors
if chunk.event.startswith("updates"):
if isinstance(chunk.data, dict) and INTERRUPT in chunk.data:
raise GraphInterrupt(chunk.data[INTERRUPT])
raise GraphInterrupt(
[Interrupt(**i) for i in chunk.data[INTERRUPT]]
)
elif chunk.event.startswith("error"):
raise RemoteException(chunk.data)
# filter for what was actually requested
@@ -746,7 +748,9 @@ class RemoteGraph(PregelProtocol):
# raise interrupt or errors
if chunk.event.startswith("updates"):
if isinstance(chunk.data, dict) and INTERRUPT in chunk.data:
raise GraphInterrupt(chunk.data[INTERRUPT])
raise GraphInterrupt(
[Interrupt(**i) for i in chunk.data[INTERRUPT]]
)
elif chunk.event.startswith("error"):
raise RemoteException(chunk.data)
# filter for what was actually requested
+55 -9
View File
@@ -44,6 +44,18 @@ from langgraph.utils.future import chain_future
F = TypeVar("F", concurrent.futures.Future, asyncio.Future)
E = TypeVar("E", threading.Event, asyncio.Event)
# List of filenames to exclude from exception traceback
# Note: Frames will be removed if they are the last frame in traceback, recursively
EXCLUDED_FRAME_FNAMES = (
"langgraph/pregel/retry.py",
"langgraph/pregel/runner.py",
"langgraph/pregel/executor.py",
"langgraph/utils/runnable.py",
"langchain_core/runnables/config.py",
"concurrent/futures/thread.py",
"concurrent/futures/_base.py",
)
class FuturesDict(Generic[F, E], dict[F, Optional[PregelExecutableTask]]):
event: E
@@ -167,6 +179,13 @@ class PregelRunner:
fut.set_exception(exc)
futures.done.add(fut)
elif reraise:
if tb := exc.__traceback__:
while tb.tb_next is not None and any(
tb.tb_frame.f_code.co_filename.endswith(name)
for name in EXCLUDED_FRAME_FNAMES
):
tb = tb.tb_next
exc.__traceback__ = tb
raise
if not futures: # maybe `t` schuduled another task
return
@@ -229,10 +248,20 @@ class PregelRunner:
# give control back to the caller
yield
# panic on failure or timeout
_panic_or_proceed(
futures.done.union(f for f, t in futures.items() if t is not None),
panic=reraise,
)
try:
_panic_or_proceed(
futures.done.union(f for f, t in futures.items() if t is not None),
panic=reraise,
)
except Exception as exc:
if tb := exc.__traceback__:
while tb.tb_next is not None and any(
tb.tb_frame.f_code.co_filename.endswith(name)
for name in EXCLUDED_FRAME_FNAMES
):
tb = tb.tb_next
exc.__traceback__ = tb
raise
async def atick(
self,
@@ -283,6 +312,13 @@ class PregelRunner:
fut.set_exception(exc)
futures.done.add(fut)
elif reraise:
if tb := exc.__traceback__:
while tb.tb_next is not None and any(
tb.tb_frame.f_code.co_filename.endswith(name)
for name in EXCLUDED_FRAME_FNAMES
):
tb = tb.tb_next
exc.__traceback__ = tb
raise
if not futures: # maybe `t` schuduled another task
return
@@ -357,11 +393,21 @@ class PregelRunner:
for fut in futures:
fut.cancel()
# panic on failure or timeout
_panic_or_proceed(
futures.done.union(f for f, t in futures.items() if t is not None),
timeout_exc_cls=asyncio.TimeoutError,
panic=reraise,
)
try:
_panic_or_proceed(
futures.done.union(f for f, t in futures.items() if t is not None),
timeout_exc_cls=asyncio.TimeoutError,
panic=reraise,
)
except Exception as exc:
if tb := exc.__traceback__:
while tb.tb_next is not None and any(
tb.tb_frame.f_code.co_filename.endswith(name)
for name in EXCLUDED_FRAME_FNAMES
):
tb = tb.tb_next
exc.__traceback__ = tb
raise
def commit(
self,
+14 -2
View File
@@ -101,7 +101,10 @@ def default_retry_on(exc: Exception) -> bool:
class RetryPolicy(NamedTuple):
"""Configuration for retrying nodes."""
"""Configuration for retrying nodes.
!!! version-added "Added in version 0.2.24."
"""
initial_interval: float = 0.5
"""Amount of time that must elapse before the first retry occurs. In seconds."""
@@ -120,13 +123,20 @@ class RetryPolicy(NamedTuple):
class CachePolicy(NamedTuple):
"""Configuration for caching nodes."""
"""Configuration for caching nodes.
!!! version-added "Added in version 0.2.24."
"""
pass
@dataclasses.dataclass(**_DC_KWARGS)
class Interrupt:
"""
!!! version-added "Added in version 0.2.24."
"""
value: Any
resumable: bool = False
ns: Optional[Sequence[str]] = None
@@ -268,6 +278,8 @@ N = TypeVar("N", bound=Hashable)
class Command(Generic[N], ToolOutputMixin):
"""One or more commands to update the graph's state and send messages to nodes.
!!! version-added "Added in version 0.2.24."
Args:
graph: graph to send the command to. Supported values are:
+27 -14
View File
@@ -10,6 +10,7 @@ T = TypeVar("T")
AnyFuture = Union[asyncio.Future, concurrent.futures.Future]
CONTEXT_NOT_SUPPORTED = sys.version_info < (3, 11)
EAGER_NOT_SUPPORTED = sys.version_info < (3, 12)
def _get_loop(fut: asyncio.Future) -> asyncio.AbstractEventLoop:
@@ -142,6 +143,7 @@ def _ensure_future(
loop: asyncio.AbstractEventLoop,
name: Optional[str] = None,
context: Optional[contextvars.Context] = None,
lazy: bool = True,
) -> asyncio.Task[T]:
called_wrap_awaitable = False
if not asyncio.iscoroutine(coro_or_future):
@@ -159,8 +161,12 @@ def _ensure_future(
try:
if CONTEXT_NOT_SUPPORTED:
return loop.create_task(coro_or_future, name=name)
else:
elif EAGER_NOT_SUPPORTED or lazy:
return loop.create_task(coro_or_future, name=name, context=context)
else:
return asyncio.eager_task_factory(
loop, coro_or_future, name=name, context=context
)
except RuntimeError:
if not called_wrap_awaitable:
coro_or_future.close()
@@ -180,6 +186,8 @@ def _wrap_awaitable(awaitable: Awaitable[T]) -> Generator[None, None, T]:
def run_coroutine_threadsafe(
coro: Coroutine[None, None, T],
loop: asyncio.AbstractEventLoop,
*,
lazy: bool,
name: Optional[str] = None,
context: Optional[contextvars.Context] = None,
) -> asyncio.Future[T]:
@@ -187,18 +195,23 @@ def run_coroutine_threadsafe(
Return a asyncio.Future to access the result.
"""
future: asyncio.Future[T] = asyncio.Future(loop=loop)
def callback() -> None:
try:
chain_future(
_ensure_future(coro, loop=loop, name=name, context=context), future
)
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
future.set_exception(exc)
raise
if asyncio._get_running_loop() is loop:
return _ensure_future(coro, loop=loop, name=name, context=context, lazy=lazy)
else:
future: asyncio.Future[T] = asyncio.Future(loop=loop)
loop.call_soon_threadsafe(callback, context=context)
return future
def callback() -> None:
try:
chain_future(
_ensure_future(coro, loop=loop, name=name, context=context),
future,
)
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
future.set_exception(exc)
raise
loop.call_soon_threadsafe(callback, context=context)
return future
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph"
version = "0.3.20"
version = "0.3.21"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
license = "MIT"
+25 -35
View File
@@ -2483,7 +2483,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
{
"langgraph_step": 1,
"langgraph_node": "agent",
"langgraph_triggers": ("branch:to:agent", "start:agent", "tools"),
"langgraph_triggers": ("branch:to:agent",),
"langgraph_path": (PULL, "agent"),
"langgraph_checkpoint_ns": AnyStr("agent:"),
"checkpoint_ns": AnyStr("agent:"),
@@ -2542,7 +2542,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
{
"langgraph_step": 3,
"langgraph_node": "agent",
"langgraph_triggers": ("branch:to:agent", "start:agent", "tools"),
"langgraph_triggers": ("branch:to:agent",),
"langgraph_path": (PULL, "agent"),
"langgraph_checkpoint_ns": AnyStr("agent:"),
"checkpoint_ns": AnyStr("agent:"),
@@ -2585,7 +2585,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
{
"langgraph_step": 5,
"langgraph_node": "agent",
"langgraph_triggers": ("branch:to:agent", "start:agent", "tools"),
"langgraph_triggers": ("branch:to:agent",),
"langgraph_path": (PULL, "agent"),
"langgraph_checkpoint_ns": AnyStr("agent:"),
"checkpoint_ns": AnyStr("agent:"),
@@ -4660,7 +4660,7 @@ def test_root_graph(
content="result for query",
name="search_api",
tool_call_id="tool_call123",
id="00000000-0000-4000-8000-000000000037",
id="00000000-0000-4000-8000-000000000040",
)
]
},
@@ -4683,7 +4683,7 @@ def test_root_graph(
content="result for another",
name="search_api",
tool_call_id="tool_call456",
id="00000000-0000-4000-8000-000000000045",
id="00000000-0000-4000-8000-000000000049",
)
]
},
@@ -5387,7 +5387,7 @@ def test_root_graph(
"__root__": [
HumanMessage(
content="what is weather in sf",
id="00000000-0000-4000-8000-000000000078",
id="00000000-0000-4000-8000-000000000083",
),
AIMessage(
content="",
@@ -5407,7 +5407,7 @@ def test_root_graph(
),
AIMessage(content="answer", id="ai2"),
AIMessage(
content="an extra message", id="00000000-0000-4000-8000-000000000100"
content="an extra message", id="00000000-0000-4000-8000-000000000107"
),
HumanMessage(content="what is weather in la"),
],
@@ -5501,10 +5501,7 @@ def test_in_one_fan_out_out_one_graph_state() -> None:
"id": AnyStr(),
"name": "rewrite_query",
"input": {"query": "what is weather in sf", "docs": []},
"triggers": (
"branch:to:rewrite_query",
"start:rewrite_query",
),
"triggers": ("branch:to:rewrite_query",),
},
},
),
@@ -5535,10 +5532,7 @@ def test_in_one_fan_out_out_one_graph_state() -> None:
"id": AnyStr(),
"name": "retriever_one",
"input": {"query": "query: what is weather in sf", "docs": []},
"triggers": (
"branch:to:retriever_one",
"rewrite_query",
),
"triggers": ("branch:to:retriever_one",),
},
},
),
@@ -5552,10 +5546,7 @@ def test_in_one_fan_out_out_one_graph_state() -> None:
"id": AnyStr(),
"name": "retriever_two",
"input": {"query": "query: what is weather in sf", "docs": []},
"triggers": (
"branch:to:retriever_two",
"rewrite_query",
),
"triggers": ("branch:to:retriever_two",),
},
},
),
@@ -5617,7 +5608,7 @@ def test_in_one_fan_out_out_one_graph_state() -> None:
"query": "query: what is weather in sf",
"docs": ["doc1", "doc2", "doc3", "doc4"],
},
"triggers": ("branch:to:qa", "retriever_one", "retriever_two"),
"triggers": ("branch:to:qa",),
},
},
),
@@ -6643,7 +6634,7 @@ def test_branch_then(
"id": AnyStr(),
"name": "prepare",
"input": {"my_key": "value", "market": "DE"},
"triggers": ("branch:to:prepare", "start:prepare"),
"triggers": ("branch:to:prepare",),
},
},
{
@@ -7795,7 +7786,7 @@ def test_nested_graph_state(
"langgraph_node": "inner",
"langgraph_path": [PULL, "inner"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:inner", "outer_1"],
"langgraph_triggers": ["branch:to:inner"],
"langgraph_checkpoint_ns": AnyStr("inner:"),
},
created_at=AnyStr(),
@@ -7990,7 +7981,7 @@ def test_nested_graph_state(
"langgraph_node": "inner",
"langgraph_path": [PULL, "inner"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:inner", "outer_1"],
"langgraph_triggers": ["branch:to:inner"],
"langgraph_checkpoint_ns": AnyStr("inner:"),
},
created_at=AnyStr(),
@@ -8033,7 +8024,7 @@ def test_nested_graph_state(
"langgraph_node": "inner",
"langgraph_path": [PULL, "inner"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:inner", "outer_1"],
"langgraph_triggers": ["branch:to:inner"],
"langgraph_checkpoint_ns": AnyStr("inner:"),
},
created_at=AnyStr(),
@@ -8082,7 +8073,7 @@ def test_nested_graph_state(
"langgraph_node": "inner",
"langgraph_path": [PULL, "inner"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:inner", "outer_1"],
"langgraph_triggers": ["branch:to:inner"],
"langgraph_checkpoint_ns": AnyStr("inner:"),
},
created_at=AnyStr(),
@@ -8516,7 +8507,7 @@ def test_doubly_nested_graph_state(
"langgraph_node": "child_1",
"langgraph_path": [PULL, AnyStr("child_1")],
"langgraph_step": 1,
"langgraph_triggers": ["branch:to:child_1", AnyStr("start:child_1")],
"langgraph_triggers": ["branch:to:child_1"],
},
created_at=AnyStr(),
parent_config=(
@@ -8602,7 +8593,6 @@ def test_doubly_nested_graph_state(
"langgraph_step": 1,
"langgraph_triggers": [
"branch:to:child_1",
AnyStr("start:child_1"),
],
},
created_at=AnyStr(),
@@ -8650,7 +8640,7 @@ def test_doubly_nested_graph_state(
"langgraph_node": "child",
"langgraph_path": [PULL, AnyStr("child")],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:child", AnyStr("parent_1")],
"langgraph_triggers": ["branch:to:child"],
"langgraph_checkpoint_ns": AnyStr("child:"),
},
created_at=AnyStr(),
@@ -8946,7 +8936,7 @@ def test_doubly_nested_graph_state(
"langgraph_node": "child",
"langgraph_path": [PULL, AnyStr("child")],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:child", AnyStr("parent_1")],
"langgraph_triggers": ["branch:to:child"],
"langgraph_checkpoint_ns": AnyStr("child:"),
},
created_at=AnyStr(),
@@ -8985,7 +8975,7 @@ def test_doubly_nested_graph_state(
"langgraph_node": "child",
"langgraph_path": [PULL, AnyStr("child")],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:child", AnyStr("parent_1")],
"langgraph_triggers": ["branch:to:child"],
"langgraph_checkpoint_ns": AnyStr("child:"),
},
created_at=AnyStr(),
@@ -9037,7 +9027,7 @@ def test_doubly_nested_graph_state(
"langgraph_node": "child",
"langgraph_path": [PULL, AnyStr("child")],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:child", AnyStr("parent_1")],
"langgraph_triggers": ["branch:to:child"],
"langgraph_checkpoint_ns": AnyStr("child:"),
},
created_at=AnyStr(),
@@ -9091,7 +9081,7 @@ def test_doubly_nested_graph_state(
AnyStr("child_1"),
],
"langgraph_step": 1,
"langgraph_triggers": ["branch:to:child_1", AnyStr("start:child_1")],
"langgraph_triggers": ["branch:to:child_1"],
},
created_at=AnyStr(),
parent_config={
@@ -9146,7 +9136,7 @@ def test_doubly_nested_graph_state(
AnyStr("child_1"),
],
"langgraph_step": 1,
"langgraph_triggers": ["branch:to:child_1", AnyStr("start:child_1")],
"langgraph_triggers": ["branch:to:child_1"],
},
created_at=AnyStr(),
parent_config={
@@ -9208,7 +9198,7 @@ def test_doubly_nested_graph_state(
AnyStr("child_1"),
],
"langgraph_step": 1,
"langgraph_triggers": ["branch:to:child_1", AnyStr("start:child_1")],
"langgraph_triggers": ["branch:to:child_1"],
},
created_at=AnyStr(),
parent_config={
@@ -9270,7 +9260,7 @@ def test_doubly_nested_graph_state(
AnyStr("child_1"),
],
"langgraph_step": 1,
"langgraph_triggers": ["branch:to:child_1", AnyStr("start:child_1")],
"langgraph_triggers": ["branch:to:child_1"],
},
created_at=AnyStr(),
parent_config=None,
+19 -50
View File
@@ -2300,11 +2300,7 @@ async def test_prebuilt_tool_chat() -> None:
{
"langgraph_step": 1,
"langgraph_node": "agent",
"langgraph_triggers": (
"branch:to:agent",
"start:agent",
"tools",
),
"langgraph_triggers": ("branch:to:agent",),
"langgraph_path": ("__pregel_pull", "agent"),
"langgraph_checkpoint_ns": AnyStr("agent:"),
"checkpoint_ns": AnyStr("agent:"),
@@ -2363,11 +2359,7 @@ async def test_prebuilt_tool_chat() -> None:
{
"langgraph_step": 3,
"langgraph_node": "agent",
"langgraph_triggers": (
"branch:to:agent",
"start:agent",
"tools",
),
"langgraph_triggers": ("branch:to:agent",),
"langgraph_path": ("__pregel_pull", "agent"),
"langgraph_checkpoint_ns": AnyStr("agent:"),
"checkpoint_ns": AnyStr("agent:"),
@@ -2410,11 +2402,7 @@ async def test_prebuilt_tool_chat() -> None:
{
"langgraph_step": 5,
"langgraph_node": "agent",
"langgraph_triggers": (
"branch:to:agent",
"start:agent",
"tools",
),
"langgraph_triggers": ("branch:to:agent",),
"langgraph_path": ("__pregel_pull", "agent"),
"langgraph_checkpoint_ns": AnyStr("agent:"),
"checkpoint_ns": AnyStr("agent:"),
@@ -3895,10 +3883,7 @@ async def test_in_one_fan_out_out_one_graph_state() -> None:
"id": AnyStr(),
"name": "rewrite_query",
"input": {"query": "what is weather in sf", "docs": []},
"triggers": (
"branch:to:rewrite_query",
"start:rewrite_query",
),
"triggers": ("branch:to:rewrite_query",),
},
},
),
@@ -3929,10 +3914,7 @@ async def test_in_one_fan_out_out_one_graph_state() -> None:
"id": AnyStr(),
"name": "retriever_one",
"input": {"query": "query: what is weather in sf", "docs": []},
"triggers": (
"branch:to:retriever_one",
"rewrite_query",
),
"triggers": ("branch:to:retriever_one",),
},
},
),
@@ -3946,10 +3928,7 @@ async def test_in_one_fan_out_out_one_graph_state() -> None:
"id": AnyStr(),
"name": "retriever_two",
"input": {"query": "query: what is weather in sf", "docs": []},
"triggers": (
"branch:to:retriever_two",
"rewrite_query",
),
"triggers": ("branch:to:retriever_two",),
},
},
),
@@ -4011,7 +3990,7 @@ async def test_in_one_fan_out_out_one_graph_state() -> None:
"query": "query: what is weather in sf",
"docs": ["doc1", "doc2", "doc3", "doc4"],
},
"triggers": ("branch:to:qa", "retriever_one", "retriever_two"),
"triggers": ("branch:to:qa",),
},
},
),
@@ -4486,10 +4465,7 @@ async def test_branch_then(checkpointer_name: str) -> None:
"id": AnyStr(),
"name": "prepare",
"input": {"my_key": "value", "market": "DE"},
"triggers": (
"branch:to:prepare",
"start:prepare",
),
"triggers": ("branch:to:prepare",),
},
},
{
@@ -4805,10 +4781,7 @@ async def test_branch_then(checkpointer_name: str) -> None:
"id": AnyStr(),
"name": "prepare",
"input": {"my_key": "value", "market": "DE"},
"triggers": (
"branch:to:prepare",
"start:prepare",
),
"triggers": ("branch:to:prepare",),
},
},
{
@@ -5363,7 +5336,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_node": "inner",
"langgraph_path": [PULL, "inner"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:inner", "outer_1"],
"langgraph_triggers": ["branch:to:inner"],
"langgraph_checkpoint_ns": AnyStr("inner:"),
},
created_at=AnyStr(),
@@ -5560,7 +5533,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_node": "inner",
"langgraph_path": [PULL, "inner"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:inner", "outer_1"],
"langgraph_triggers": ["branch:to:inner"],
"langgraph_checkpoint_ns": AnyStr("inner:"),
},
created_at=AnyStr(),
@@ -5603,7 +5576,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_node": "inner",
"langgraph_path": [PULL, "inner"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:inner", "outer_1"],
"langgraph_triggers": ["branch:to:inner"],
"langgraph_checkpoint_ns": AnyStr("inner:"),
},
created_at=AnyStr(),
@@ -5652,7 +5625,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_node": "inner",
"langgraph_path": [PULL, "inner"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:inner", "outer_1"],
"langgraph_triggers": ["branch:to:inner"],
"langgraph_checkpoint_ns": AnyStr("inner:"),
},
created_at=AnyStr(),
@@ -6090,7 +6063,9 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_node": "child_1",
"langgraph_path": [PULL, AnyStr("child_1")],
"langgraph_step": 1,
"langgraph_triggers": ["branch:to:child_1", "start:child_1"],
"langgraph_triggers": [
"branch:to:child_1",
],
},
created_at=AnyStr(),
parent_config=(
@@ -6178,7 +6153,6 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_step": 1,
"langgraph_triggers": [
"branch:to:child_1",
"start:child_1",
],
},
created_at=AnyStr(),
@@ -6230,7 +6204,6 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_step": 2,
"langgraph_triggers": [
"branch:to:child",
AnyStr("parent_1"),
],
"langgraph_checkpoint_ns": AnyStr("child:"),
},
@@ -6529,7 +6502,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_node": "child",
"langgraph_path": [PULL, AnyStr("child")],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:child", AnyStr("parent_1")],
"langgraph_triggers": ["branch:to:child"],
"langgraph_checkpoint_ns": AnyStr("child:"),
},
created_at=AnyStr(),
@@ -6568,7 +6541,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_node": "child",
"langgraph_path": [PULL, AnyStr("child")],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:child", AnyStr("parent_1")],
"langgraph_triggers": ["branch:to:child"],
"langgraph_checkpoint_ns": AnyStr("child:"),
},
created_at=AnyStr(),
@@ -6620,7 +6593,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_node": "child",
"langgraph_path": [PULL, AnyStr("child")],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:child", AnyStr("parent_1")],
"langgraph_triggers": ["branch:to:child"],
"langgraph_checkpoint_ns": AnyStr("child:"),
},
created_at=AnyStr(),
@@ -6680,7 +6653,6 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_step": 1,
"langgraph_triggers": [
"branch:to:child_1",
AnyStr("start:child_1"),
],
},
created_at=AnyStr(),
@@ -6738,7 +6710,6 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_step": 1,
"langgraph_triggers": [
"branch:to:child_1",
AnyStr("start:child_1"),
],
},
created_at=AnyStr(),
@@ -6803,7 +6774,6 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_step": 1,
"langgraph_triggers": [
"branch:to:child_1",
AnyStr("start:child_1"),
],
},
created_at=AnyStr(),
@@ -6868,7 +6838,6 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None:
"langgraph_step": 1,
"langgraph_triggers": [
"branch:to:child_1",
AnyStr("start:child_1"),
],
},
created_at=AnyStr(),
+15 -24
View File
@@ -1187,15 +1187,13 @@ def test_pending_writes_resume(
assert checkpoint is not None
# should contain error from "two"
expected_writes = [
(AnyStr(), "one", "one"),
(AnyStr(), "value", 2),
(AnyStr(), ERROR, 'ConnectionError("I\'m not good")'),
]
assert len(checkpoint.pending_writes) == 3
assert len(checkpoint.pending_writes) == 2
assert all(w in expected_writes for w in checkpoint.pending_writes)
# both non-error pending writes come from same task
non_error_writes = [w for w in checkpoint.pending_writes if w[1] != ERROR]
assert non_error_writes[0][0] == non_error_writes[1][0]
# error write is from the other task
error_write = next(w for w in checkpoint.pending_writes if w[1] == ERROR)
assert error_write[0] != non_error_writes[0][0]
@@ -1242,10 +1240,10 @@ def test_pending_writes_resume(
"pending_sends": [],
"versions_seen": {
"one": {
"start:one": AnyVersion(),
"branch:to:one": AnyVersion(),
},
"two": {
"start:two": AnyVersion(),
"branch:to:two": AnyVersion(),
},
"__input__": {},
"__start__": {
@@ -1254,19 +1252,17 @@ def test_pending_writes_resume(
"__interrupt__": {
"value": AnyVersion(),
"__start__": AnyVersion(),
"start:one": AnyVersion(),
"start:two": AnyVersion(),
"branch:to:one": AnyVersion(),
"branch:to:two": AnyVersion(),
},
},
"channel_versions": {
"one": AnyVersion(),
"two": AnyVersion(),
"value": AnyVersion(),
"__start__": AnyVersion(),
"start:one": AnyVersion(),
"start:two": AnyVersion(),
"branch:to:one": AnyVersion(),
"branch:to:two": AnyVersion(),
},
"channel_values": {"one": "one", "two": "two", "value": 6},
"channel_values": {"value": 6},
},
metadata={
"parents": {},
@@ -1309,13 +1305,13 @@ def test_pending_writes_resume(
"channel_versions": {
"value": AnyVersion(),
"__start__": AnyVersion(),
"start:one": AnyVersion(),
"start:two": AnyVersion(),
"branch:to:one": AnyVersion(),
"branch:to:two": AnyVersion(),
},
"channel_values": {
"value": 1,
"start:one": "__start__",
"start:two": "__start__",
"branch:to:one": "__start__",
"branch:to:two": "__start__",
},
},
metadata={
@@ -1333,10 +1329,8 @@ def test_pending_writes_resume(
}
},
pending_writes=UnsortedSequence(
(AnyStr(), "one", "one"),
(AnyStr(), "value", 2),
(AnyStr(), "__error__", 'ConnectionError("I\'m not good")'),
(AnyStr(), "two", "two"),
(AnyStr(), "value", 3),
),
)
@@ -1369,8 +1363,8 @@ def test_pending_writes_resume(
parent_config=None,
pending_writes=UnsortedSequence(
(AnyStr(), "value", 1),
(AnyStr(), "start:one", "__start__"),
(AnyStr(), "start:two", "__start__"),
(AnyStr(), "branch:to:one", "__start__"),
(AnyStr(), "branch:to:two", "__start__"),
),
)
@@ -6876,10 +6870,7 @@ def test_tags_stream_mode_messages() -> None:
{
"langgraph_step": 1,
"langgraph_node": "call_model",
"langgraph_triggers": (
"branch:to:call_model",
"start:call_model",
),
"langgraph_triggers": ("branch:to:call_model",),
"langgraph_path": ("__pregel_pull", "call_model"),
"langgraph_checkpoint_ns": AnyStr("call_model:"),
"checkpoint_ns": AnyStr("call_model:"),
+15 -24
View File
@@ -2021,15 +2021,13 @@ async def test_pending_writes_resume(
assert checkpoint is not None
# should contain error from "two"
expected_writes = [
(AnyStr(), "one", "one"),
(AnyStr(), "value", 2),
(AnyStr(), ERROR, 'ConnectionError("I\'m not good")'),
]
assert len(checkpoint.pending_writes) == 3
assert len(checkpoint.pending_writes) == 2
assert all(w in expected_writes for w in checkpoint.pending_writes)
# both non-error pending writes come from same task
non_error_writes = [w for w in checkpoint.pending_writes if w[1] != ERROR]
assert non_error_writes[0][0] == non_error_writes[1][0]
# error write is from the other task
error_write = next(w for w in checkpoint.pending_writes if w[1] == ERROR)
assert error_write[0] != non_error_writes[0][0]
@@ -2076,10 +2074,10 @@ async def test_pending_writes_resume(
"pending_sends": [],
"versions_seen": {
"one": {
"start:one": AnyVersion(),
"branch:to:one": AnyVersion(),
},
"two": {
"start:two": AnyVersion(),
"branch:to:two": AnyVersion(),
},
"__input__": {},
"__start__": {
@@ -2088,19 +2086,17 @@ async def test_pending_writes_resume(
"__interrupt__": {
"value": AnyVersion(),
"__start__": AnyVersion(),
"start:one": AnyVersion(),
"start:two": AnyVersion(),
"branch:to:one": AnyVersion(),
"branch:to:two": AnyVersion(),
},
},
"channel_versions": {
"one": AnyVersion(),
"two": AnyVersion(),
"value": AnyVersion(),
"__start__": AnyVersion(),
"start:one": AnyVersion(),
"start:two": AnyVersion(),
"branch:to:one": AnyVersion(),
"branch:to:two": AnyVersion(),
},
"channel_values": {"one": "one", "two": "two", "value": 6},
"channel_values": {"value": 6},
},
metadata={
"parents": {},
@@ -2145,13 +2141,13 @@ async def test_pending_writes_resume(
"channel_versions": {
"value": AnyVersion(),
"__start__": AnyVersion(),
"start:one": AnyVersion(),
"start:two": AnyVersion(),
"branch:to:one": AnyVersion(),
"branch:to:two": AnyVersion(),
},
"channel_values": {
"value": 1,
"start:one": "__start__",
"start:two": "__start__",
"branch:to:one": "__start__",
"branch:to:two": "__start__",
},
},
metadata={
@@ -2171,10 +2167,8 @@ async def test_pending_writes_resume(
}
},
pending_writes=UnsortedSequence(
(AnyStr(), "one", "one"),
(AnyStr(), "value", 2),
(AnyStr(), "__error__", 'ConnectionError("I\'m not good")'),
(AnyStr(), "two", "two"),
(AnyStr(), "value", 3),
),
)
@@ -2207,8 +2201,8 @@ async def test_pending_writes_resume(
parent_config=None,
pending_writes=UnsortedSequence(
(AnyStr(), "value", 1),
(AnyStr(), "start:one", "__start__"),
(AnyStr(), "start:two", "__start__"),
(AnyStr(), "branch:to:one", "__start__"),
(AnyStr(), "branch:to:two", "__start__"),
),
)
@@ -7593,10 +7587,7 @@ async def test_tags_stream_mode_messages() -> None:
{
"langgraph_step": 1,
"langgraph_node": "call_model",
"langgraph_triggers": (
"branch:to:call_model",
"start:call_model",
),
"langgraph_triggers": ("branch:to:call_model",),
"langgraph_path": ("__pregel_pull", "call_model"),
"langgraph_checkpoint_ns": AnyStr("call_model:"),
"checkpoint_ns": AnyStr("call_model:"),
+47 -4
View File
@@ -12,6 +12,7 @@ from langgraph_sdk.schema import StreamPart
from langgraph.errors import GraphInterrupt
from langgraph.pregel.remote import RemoteGraph
from langgraph.pregel.types import StateSnapshot
from langgraph.types import Interrupt
def test_with_config():
@@ -415,7 +416,19 @@ def test_stream():
StreamPart(event="values", data={"chunk": "data2"}),
StreamPart(event="values", data={"chunk": "data3"}),
StreamPart(event="updates", data={"chunk": "data4"}),
StreamPart(event="updates", data={"__interrupt__": ()}),
StreamPart(
event="updates",
data={
"__interrupt__": [
{
"value": {"question": "Does this look good?"},
"resumable": True,
"ns": ["some_ns"],
"when": "during",
}
]
},
),
]
# call method / assertions
@@ -426,7 +439,7 @@ def test_stream():
# stream modes doesn't include 'updates'
stream_parts = []
with pytest.raises(GraphInterrupt):
with pytest.raises(GraphInterrupt) as exc:
for stream_part in remote_pregel.stream(
{"input": "data"},
config={"configurable": {"thread_id": "thread_1"}},
@@ -434,6 +447,15 @@ def test_stream():
):
stream_parts.append(stream_part)
assert exc.value.args[0] == [
Interrupt(
value={"question": "Does this look good?"},
resumable=True,
ns=["some_ns"],
when="during",
)
]
assert stream_parts == [
{"chunk": "data1"},
{"chunk": "data2"},
@@ -517,7 +539,19 @@ async def test_astream():
StreamPart(event="values", data={"chunk": "data2"}),
StreamPart(event="values", data={"chunk": "data3"}),
StreamPart(event="updates", data={"chunk": "data4"}),
StreamPart(event="updates", data={"__interrupt__": ()}),
StreamPart(
event="updates",
data={
"__interrupt__": [
{
"value": {"question": "Does this look good?"},
"resumable": True,
"ns": ["some_ns"],
"when": "during",
}
]
},
),
]
mock_async_client.runs.stream.return_value = async_iter
@@ -529,7 +563,7 @@ async def test_astream():
# stream modes doesn't include 'updates'
stream_parts = []
with pytest.raises(GraphInterrupt):
with pytest.raises(GraphInterrupt) as exc:
async for stream_part in remote_pregel.astream(
{"input": "data"},
config={"configurable": {"thread_id": "thread_1"}},
@@ -537,6 +571,15 @@ async def test_astream():
):
stream_parts.append(stream_part)
assert exc.value.args[0] == [
Interrupt(
value={"question": "Does this look good?"},
resumable=True,
ns=["some_ns"],
when="during",
)
]
assert stream_parts == [
{"chunk": "data1"},
{"chunk": "data2"},
@@ -281,15 +281,18 @@ class ToolNode(RunnableCallable):
parent_command: Optional[Command] = None
for output in outputs:
if isinstance(output, Command):
if output.graph is Command.PARENT and isinstance(output.goto, str):
parent_send = [Send(output.goto, output.update)]
if (
output.graph is Command.PARENT
and isinstance(output.goto, list)
and all(isinstance(send, Send) for send in output.goto)
):
if parent_command:
parent_command = replace(
parent_command,
goto=cast(list[Send], parent_command.goto) + parent_send,
goto=cast(list[Send], parent_command.goto) + output.goto,
)
else:
parent_command = Command(graph=Command.PARENT, goto=parent_send)
parent_command = Command(graph=Command.PARENT, goto=output.goto)
else:
combined_outputs.append(output)
else:
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-prebuilt"
version = "0.1.5"
version = "0.1.7"
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
authors = []
license = "MIT"
-2
View File
@@ -97,7 +97,6 @@ def test_no_prompt(
_AnyIdHumanMessage(content="hi?"),
AIMessage(content="hi?", id="0"),
],
"agent": "agent",
}
assert saved.metadata == {
"parents": {},
@@ -129,7 +128,6 @@ async def test_no_prompt_async(checkpointer_name: str) -> None:
_AnyIdHumanMessage(content="hi?"),
AIMessage(content="hi?", id="0"),
],
"agent": "agent",
}
assert saved.metadata == {
"parents": {},
+177 -120
View File
@@ -595,20 +595,16 @@ async def test_tool_node_command(input_type: str):
]
},
Command(
goto=[
Send(
"bob",
{
"messages": [
ToolMessage(
content="Transferred to Bob",
tool_call_id="2",
name="transfer_to_bob",
)
]
},
)
],
update={
"messages": [
ToolMessage(
content="Transferred to Bob",
tool_call_id="2",
name="transfer_to_bob",
)
]
},
goto="bob",
graph=Command.PARENT,
),
]
@@ -628,20 +624,16 @@ async def test_tool_node_command(input_type: str):
)
assert result == [
Command(
goto=[
Send(
"bob",
{
"messages": [
ToolMessage(
content="Transferred to Bob",
tool_call_id="1",
name=tool.name,
)
]
},
)
],
update={
"messages": [
ToolMessage(
content="Transferred to Bob",
tool_call_id="1",
name=tool.name,
)
]
},
goto="bob",
graph=Command.PARENT,
)
]
@@ -659,20 +651,16 @@ async def test_tool_node_command(input_type: str):
)
assert result == [
Command(
goto=[
Send(
"bob",
{
"messages": [
ToolMessage(
content="Transferred to Bob",
tool_call_id="1",
name=tool.name,
)
]
},
)
],
update={
"messages": [
ToolMessage(
content="Transferred to Bob",
tool_call_id="1",
name=tool.name,
)
]
},
goto="bob",
graph=Command.PARENT,
)
]
@@ -693,33 +681,30 @@ async def test_tool_node_command(input_type: str):
)
assert result == [
Command(
update={
"messages": [
ToolMessage(
content="Transferred to Bob",
tool_call_id="1",
name="transfer_to_bob",
)
]
},
goto="bob",
graph=Command.PARENT,
),
Command(
update={
"messages": [
ToolMessage(
content="Transferred to Bob",
tool_call_id="2",
name="custom_transfer_to_bob",
)
]
},
goto="bob",
graph=Command.PARENT,
goto=[
Send(
"bob",
{
"messages": [
ToolMessage(
content="Transferred to Bob",
tool_call_id="1",
name="transfer_to_bob",
)
]
},
),
Send(
"bob",
{
"messages": [
ToolMessage(
content="Transferred to Bob",
tool_call_id="2",
name="custom_transfer_to_bob",
)
]
},
),
],
),
]
@@ -906,19 +891,15 @@ async def test_tool_node_command_list_input():
)
],
Command(
graph=Command.PARENT,
goto=[
Send(
"bob",
[
ToolMessage(
content="Transferred to Bob",
tool_call_id="2",
name="transfer_to_bob",
)
],
update=[
ToolMessage(
content="Transferred to Bob",
tool_call_id="2",
name="transfer_to_bob",
)
],
goto="bob",
graph=Command.PARENT,
),
]
@@ -931,18 +912,14 @@ async def test_tool_node_command_list_input():
)
assert result == [
Command(
goto=[
Send(
"bob",
[
ToolMessage(
content="Transferred to Bob",
tool_call_id="1",
name=tool.name,
)
],
update=[
ToolMessage(
content="Transferred to Bob",
tool_call_id="1",
name=tool.name,
)
],
goto="bob",
graph=Command.PARENT,
)
]
@@ -954,18 +931,14 @@ async def test_tool_node_command_list_input():
)
assert result == [
Command(
goto=[
Send(
"bob",
[
ToolMessage(
content="Transferred to Bob",
tool_call_id="1",
name=tool.name,
)
],
update=[
ToolMessage(
content="Transferred to Bob",
tool_call_id="1",
name=tool.name,
)
],
goto="bob",
graph=Command.PARENT,
)
]
@@ -984,28 +957,25 @@ async def test_tool_node_command_list_input():
)
assert result == [
Command(
goto=[
Send(
"bob",
[
ToolMessage(
content="Transferred to Bob",
tool_call_id="1",
name="transfer_to_bob",
)
],
),
Send(
"bob",
[
ToolMessage(
content="Transferred to Bob",
tool_call_id="2",
name="custom_transfer_to_bob",
)
],
),
update=[
ToolMessage(
content="Transferred to Bob",
tool_call_id="1",
name="transfer_to_bob",
)
],
goto="bob",
graph=Command.PARENT,
),
Command(
update=[
ToolMessage(
content="Transferred to Bob",
tool_call_id="2",
name="custom_transfer_to_bob",
)
],
goto="bob",
graph=Command.PARENT,
),
]
@@ -1081,3 +1051,90 @@ async def test_tool_node_command_list_input():
)
]
) == [Command(update=[], graph=Command.PARENT)]
def test_tool_node_parent_command_with_send():
from langchain_core.tools.base import InjectedToolCallId
@dec_tool
def transfer_to_alice(tool_call_id: Annotated[str, InjectedToolCallId]):
"""Transfer to Alice"""
return Command(
goto=[
Send(
"alice",
{
"messages": [
ToolMessage(
content="Transferred to Alice",
name="transfer_to_alice",
tool_call_id=tool_call_id,
)
]
},
)
],
graph=Command.PARENT,
)
@dec_tool
def transfer_to_bob(tool_call_id: Annotated[str, InjectedToolCallId]):
"""Transfer to Bob"""
return Command(
goto=[
Send(
"bob",
{
"messages": [
ToolMessage(
content="Transferred to Bob",
name="transfer_to_bob",
tool_call_id=tool_call_id,
)
]
},
)
],
graph=Command.PARENT,
)
tool_calls = [
{"args": {}, "id": "1", "name": "transfer_to_alice", "type": "tool_call"},
{"args": {}, "id": "2", "name": "transfer_to_bob", "type": "tool_call"},
]
result = ToolNode([transfer_to_alice, transfer_to_bob]).invoke(
[AIMessage("", tool_calls=tool_calls)]
)
assert result == [
Command(
goto=[
Send(
"alice",
{
"messages": [
ToolMessage(
content="Transferred to Alice",
name="transfer_to_alice",
tool_call_id="1",
)
]
},
),
Send(
"bob",
{
"messages": [
ToolMessage(
content="Transferred to Bob",
name="transfer_to_bob",
tool_call_id="2",
)
]
},
),
],
graph=Command.PARENT,
)
]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@langchain/langgraph-sdk",
"version": "0.0.60",
"version": "0.0.62",
"description": "Client library for interacting with the LangGraph API",
"type": "module",
"packageManager": "yarn@1.22.19",
+17
View File
@@ -109,6 +109,7 @@ class ComponentStore {
const COMPONENT_STORE = new ComponentStore();
const EXT_STORE_SYMBOL = Symbol.for("LGUI_EXT_STORE");
const REQUIRE_SYMBOL = Symbol.for("LGUI_REQUIRE");
const REQUIRE_EXTRA_SYMBOL = Symbol.for("LGUI_REQUIRE_EXTRA");
interface LoadExternalComponentProps
extends Pick<React.HTMLAttributes<HTMLDivElement>, "style" | "className"> {
@@ -197,9 +198,17 @@ declare global {
interface Window {
[EXT_STORE_SYMBOL]: ComponentStore;
[REQUIRE_SYMBOL]: (name: string) => unknown;
[REQUIRE_EXTRA_SYMBOL]: Record<string, unknown>;
}
}
export function experimental_loadShare(name: string, module: unknown) {
if (typeof window === "undefined") return;
window[REQUIRE_EXTRA_SYMBOL] ??= {};
window[REQUIRE_EXTRA_SYMBOL][name] = module;
}
export function bootstrapUiContext() {
if (typeof window === "undefined") {
console.warn(
@@ -224,6 +233,14 @@ export function bootstrapUiContext() {
};
}
if (
window[REQUIRE_EXTRA_SYMBOL] != null &&
typeof window[REQUIRE_EXTRA_SYMBOL] === "object" &&
name in window[REQUIRE_EXTRA_SYMBOL]
) {
return window[REQUIRE_EXTRA_SYMBOL][name];
}
throw new Error(`Unknown module...: ${name}`);
};
}
+5 -1
View File
@@ -1,7 +1,11 @@
import { bootstrapUiContext } from "./client.js";
bootstrapUiContext();
export { useStreamContext, LoadExternalComponent } from "./client.js";
export {
useStreamContext,
LoadExternalComponent,
experimental_loadShare,
} from "./client.js";
export {
uiMessageReducer,
type UIMessage,
+36 -36
View File
@@ -782,42 +782,6 @@ export function useStream<
submittingRef.current = true;
abortRef.current = new AbortController();
let usableThreadId = threadId;
if (!usableThreadId) {
const thread = await client.threads.create();
onThreadId(thread.thread_id);
usableThreadId = thread.thread_id;
}
const streamMode = unique([
...(submitOptions?.streamMode ?? []),
...trackStreamModeRef.current,
...callbackStreamMode,
]);
const checkpoint =
submitOptions?.checkpoint ?? threadHead?.checkpoint ?? undefined;
// @ts-expect-error
if (checkpoint != null) delete checkpoint.thread_id;
const run = (await client.runs.stream(usableThreadId, assistantId, {
input: values as Record<string, unknown>,
config: submitOptions?.config,
command: submitOptions?.command,
interruptBefore: submitOptions?.interruptBefore,
interruptAfter: submitOptions?.interruptAfter,
metadata: submitOptions?.metadata,
multitaskStrategy: submitOptions?.multitaskStrategy,
onCompletion: submitOptions?.onCompletion,
onDisconnect: submitOptions?.onDisconnect ?? "cancel",
signal: abortRef.current.signal,
checkpoint,
streamMode,
})) as AsyncGenerator<EventStreamEvent>;
// Unbranch things
const newPath = submitOptions?.checkpoint?.checkpoint_id
? branchByCheckpoint[submitOptions?.checkpoint?.checkpoint_id]?.branch
@@ -842,6 +806,42 @@ export function useStream<
return values;
});
let usableThreadId = threadId;
if (!usableThreadId) {
const thread = await client.threads.create();
onThreadId(thread.thread_id);
usableThreadId = thread.thread_id;
}
const streamMode = unique([
...(submitOptions?.streamMode ?? []),
...trackStreamModeRef.current,
...callbackStreamMode,
]);
const checkpoint =
submitOptions?.checkpoint ?? threadHead?.checkpoint ?? undefined;
// @ts-expect-error
if (checkpoint != null) delete checkpoint.thread_id;
const run = client.runs.stream(usableThreadId, assistantId, {
input: values as Record<string, unknown>,
config: submitOptions?.config,
command: submitOptions?.command,
interruptBefore: submitOptions?.interruptBefore,
interruptAfter: submitOptions?.interruptAfter,
metadata: submitOptions?.metadata,
multitaskStrategy: submitOptions?.multitaskStrategy,
onCompletion: submitOptions?.onCompletion,
onDisconnect: submitOptions?.onDisconnect ?? "cancel",
signal: abortRef.current.signal,
checkpoint,
streamMode,
}) as AsyncGenerator<EventStreamEvent>;
let streamError: StreamError | undefined;
for await (const { event, data } of run) {
if (event === "error") {
+4 -4
View File
@@ -57,25 +57,25 @@ export interface GraphSchema {
* The schema for the input state.
* Missing if unable to generate JSON schema from graph.
*/
input_schema?: JSONSchema7;
input_schema?: JSONSchema7 | null | undefined;
/**
* The schema for the output state.
* Missing if unable to generate JSON schema from graph.
*/
output_schema?: JSONSchema7;
output_schema?: JSONSchema7 | null | undefined;
/**
* The schema for the graph state.
* Missing if unable to generate JSON schema from graph.
*/
state_schema?: JSONSchema7;
state_schema?: JSONSchema7 | null | undefined;
/**
* The schema for the graph config.
* Missing if unable to generate JSON schema from graph.
*/
config_schema?: JSONSchema7;
config_schema?: JSONSchema7 | null | undefined;
}
export type Subgraphs = Record<string, GraphSchema>;
+12
View File
@@ -192,6 +192,18 @@ class BaseUser(typing.Protocol):
"""The permissions associated with the user."""
...
def __getitem__(self, key):
"""Get a key from your minimal user dict."""
...
def __contains__(self, key):
"""Check if a property exists."""
...
def __iter__(self):
"""Iterate over the keys of the user."""
...
class StudioUser:
"""A user object that's populated from authenticated requests from the LangGraph studio.
+246 -54
View File
@@ -2689,10 +2689,15 @@ class SyncHttpClient:
raise e
return decode_json(r)
def put(self, path: str, *, json: dict) -> Any:
def put(
self, path: str, *, json: dict, headers: Optional[dict[str, str]] = None
) -> Any:
"""Send a PUT request."""
headers, content = encode_json(json)
r = self.client.put(path, headers=headers, content=content)
request_headers, content = encode_json(json)
if headers:
request_headers.update(headers)
r = self.client.put(path, headers=request_headers, content=content)
try:
r.raise_for_status()
except httpx.HTTPStatusError as e:
@@ -2816,11 +2821,17 @@ class SyncAssistantsClient:
def __init__(self, http: SyncHttpClient) -> None:
self.http = http
def get(self, assistant_id: str) -> Assistant:
def get(
self,
assistant_id: str,
*,
headers: Optional[dict[str, str]] = None,
) -> Assistant:
"""Get an assistant by ID.
Args:
assistant_id: The ID of the assistant to get.
headers: Optional custom headers to include with the request.
Returns:
Assistant: Assistant Object.
@@ -2844,16 +2855,21 @@ class SyncAssistantsClient:
}
""" # noqa: E501
return self.http.get(f"/assistants/{assistant_id}")
return self.http.get(f"/assistants/{assistant_id}", headers=headers)
def get_graph(
self, assistant_id: str, *, xray: Union[int, bool] = False
self,
assistant_id: str,
*,
xray: Union[int, bool] = False,
headers: Optional[dict[str, str]] = None,
) -> dict[str, list[dict[str, Any]]]:
"""Get the graph of an assistant by ID.
Args:
assistant_id: The ID of the assistant to get the graph of.
xray: Include graph representation of subgraphs. If an integer value is provided, only subgraphs with a depth less than or equal to the value will be included.
headers: Optional custom headers to include with the request.
Returns:
Graph: The graph information for the assistant in JSON format.
@@ -2883,13 +2899,21 @@ class SyncAssistantsClient:
""" # noqa: E501
return self.http.get(f"/assistants/{assistant_id}/graph", params={"xray": xray})
return self.http.get(
f"/assistants/{assistant_id}/graph", params={"xray": xray}, headers=headers
)
def get_schemas(self, assistant_id: str) -> GraphSchema:
def get_schemas(
self,
assistant_id: str,
*,
headers: Optional[dict[str, str]] = None,
) -> GraphSchema:
"""Get the schemas of an assistant by ID.
Args:
assistant_id: The ID of the assistant to get the schema of.
headers: Optional custom headers to include with the request.
Returns:
GraphSchema: The graph schema for the assistant.
@@ -2988,15 +3012,21 @@ class SyncAssistantsClient:
}
""" # noqa: E501
return self.http.get(f"/assistants/{assistant_id}/schemas")
return self.http.get(f"/assistants/{assistant_id}/schemas", headers=headers)
def get_subgraphs(
self, assistant_id: str, namespace: Optional[str] = None, recurse: bool = False
self,
assistant_id: str,
namespace: Optional[str] = None,
recurse: bool = False,
*,
headers: Optional[dict[str, str]] = None,
) -> Subgraphs:
"""Get the schemas of an assistant by ID.
Args:
assistant_id: The ID of the assistant to get the schema of.
headers: Optional custom headers to include with the request.
Returns:
Subgraphs: The graph schema for the assistant.
@@ -3006,11 +3036,13 @@ class SyncAssistantsClient:
return self.http.get(
f"/assistants/{assistant_id}/subgraphs/{namespace}",
params={"recurse": recurse},
headers=headers,
)
else:
return self.http.get(
f"/assistants/{assistant_id}/subgraphs",
params={"recurse": recurse},
headers=headers,
)
def create(
@@ -3022,6 +3054,7 @@ class SyncAssistantsClient:
assistant_id: Optional[str] = None,
if_exists: Optional[OnConflictBehavior] = None,
name: Optional[str] = None,
headers: Optional[dict[str, str]] = None,
) -> Assistant:
"""Create a new assistant.
@@ -3035,6 +3068,7 @@ class SyncAssistantsClient:
if_exists: How to handle duplicate creation. Defaults to 'raise' under the hood.
Must be either 'raise' (raise error if duplicate), or 'do_nothing' (return existing assistant).
name: The name of the assistant. Defaults to 'Untitled' under the hood.
headers: Optional custom headers to include with the request.
Returns:
Assistant: The created assistant.
@@ -3063,7 +3097,7 @@ class SyncAssistantsClient:
payload["if_exists"] = if_exists
if name:
payload["name"] = name
return self.http.post("/assistants", json=payload)
return self.http.post("/assistants", json=payload, headers=headers)
def update(
self,
@@ -3073,6 +3107,7 @@ class SyncAssistantsClient:
config: Optional[Config] = None,
metadata: Json = None,
name: Optional[str] = None,
headers: Optional[dict[str, str]] = None,
) -> Assistant:
"""Update an assistant.
@@ -3085,6 +3120,7 @@ class SyncAssistantsClient:
config: Configuration to use for the graph.
metadata: Metadata to merge with existing assistant metadata.
name: The new name for the assistant.
headers: Optional custom headers to include with the request.
Returns:
Assistant: The updated assistant.
@@ -3111,16 +3147,20 @@ class SyncAssistantsClient:
return self.http.patch(
f"/assistants/{assistant_id}",
json=payload,
headers=headers,
)
def delete(
self,
assistant_id: str,
*,
headers: Optional[dict[str, str]] = None,
) -> None:
"""Delete an assistant.
Args:
assistant_id: The assistant ID to delete.
headers: Optional custom headers to include with the request.
Returns:
None
@@ -3132,7 +3172,7 @@ class SyncAssistantsClient:
)
""" # noqa: E501
self.http.delete(f"/assistants/{assistant_id}")
self.http.delete(f"/assistants/{assistant_id}", headers=headers)
def search(
self,
@@ -3141,6 +3181,7 @@ class SyncAssistantsClient:
graph_id: Optional[str] = None,
limit: int = 10,
offset: int = 0,
headers: Optional[dict[str, str]] = None,
) -> list[Assistant]:
"""Search for assistants.
@@ -3150,6 +3191,7 @@ class SyncAssistantsClient:
The graph ID is normally set in your langgraph.json configuration.
limit: The maximum number of results to return.
offset: The number of results to skip.
headers: Optional custom headers to include with the request.
Returns:
list[Assistant]: A list of assistants.
@@ -3174,6 +3216,7 @@ class SyncAssistantsClient:
return self.http.post(
"/assistants/search",
json=payload,
headers=headers,
)
def get_versions(
@@ -3182,6 +3225,8 @@ class SyncAssistantsClient:
metadata: Json = None,
limit: int = 10,
offset: int = 0,
*,
headers: Optional[dict[str, str]] = None,
) -> list[AssistantVersion]:
"""List all versions of an assistant.
@@ -3190,6 +3235,7 @@ class SyncAssistantsClient:
metadata: Metadata to filter versions by. Exact match filter for each KV pair.
limit: The maximum number of versions to return.
offset: The number of versions to skip.
headers: Optional custom headers to include with the request.
Returns:
list[Assistant]: A list of assistants.
@@ -3208,14 +3254,23 @@ class SyncAssistantsClient:
}
if metadata:
payload["metadata"] = metadata
return self.http.post(f"/assistants/{assistant_id}/versions", json=payload)
return self.http.post(
f"/assistants/{assistant_id}/versions", json=payload, headers=headers
)
def set_latest(self, assistant_id: str, version: int) -> Assistant:
def set_latest(
self,
assistant_id: str,
version: int,
*,
headers: Optional[dict[str, str]] = None,
) -> Assistant:
"""Change the version of an assistant.
Args:
assistant_id: The assistant ID to delete.
version: The version to change to.
headers: Optional custom headers to include with the request.
Returns:
Assistant: Assistant Object.
@@ -3231,7 +3286,9 @@ class SyncAssistantsClient:
payload: Dict[str, Any] = {"version": version}
return self.http.post(f"/assistants/{assistant_id}/latest", json=payload)
return self.http.post(
f"/assistants/{assistant_id}/latest", json=payload, headers=headers
)
class SyncThreadsClient:
@@ -3249,11 +3306,17 @@ class SyncThreadsClient:
def __init__(self, http: SyncHttpClient) -> None:
self.http = http
def get(self, thread_id: str) -> Thread:
def get(
self,
thread_id: str,
*,
headers: Optional[dict[str, str]] = None,
) -> Thread:
"""Get a thread by ID.
Args:
thread_id: The ID of the thread to get.
headers: Optional custom headers to include with the request.
Returns:
Thread: Thread object.
@@ -3276,7 +3339,7 @@ class SyncThreadsClient:
""" # noqa: E501
return self.http.get(f"/threads/{thread_id}")
return self.http.get(f"/threads/{thread_id}", headers=headers)
def create(
self,
@@ -3286,6 +3349,7 @@ class SyncThreadsClient:
if_exists: Optional[OnConflictBehavior] = None,
supersteps: Optional[Sequence[dict[str, Sequence[dict[str, Any]]]]] = None,
graph_id: Optional[str] = None,
headers: Optional[dict[str, str]] = None,
) -> Thread:
"""Create a new thread.
@@ -3298,6 +3362,7 @@ class SyncThreadsClient:
supersteps: Apply a list of supersteps when creating a thread, each containing a sequence of updates.
Each update has `values` or `command` and `as_node`. Used for copying a thread between deployments.
graph_id: Optional graph ID to associate with the thread.
headers: Optional custom headers to include with the request.
Returns:
Thread: The created thread.
@@ -3335,14 +3400,21 @@ class SyncThreadsClient:
for s in supersteps
]
return self.http.post("/threads", json=payload)
return self.http.post("/threads", json=payload, headers=headers)
def update(self, thread_id: str, *, metadata: dict[str, Any]) -> Thread:
def update(
self,
thread_id: str,
*,
metadata: dict[str, Any],
headers: Optional[dict[str, str]] = None,
) -> Thread:
"""Update a thread.
Args:
thread_id: ID of thread to update.
metadata: Metadata to merge with existing thread metadata.
headers: Optional custom headers to include with the request.
Returns:
Thread: The created thread.
@@ -3354,13 +3426,21 @@ class SyncThreadsClient:
metadata={"number":1},
)
""" # noqa: E501
return self.http.patch(f"/threads/{thread_id}", json={"metadata": metadata})
return self.http.patch(
f"/threads/{thread_id}", json={"metadata": metadata}, headers=headers
)
def delete(self, thread_id: str) -> None:
def delete(
self,
thread_id: str,
*,
headers: Optional[dict[str, str]] = None,
) -> None:
"""Delete a thread.
Args:
thread_id: The ID of the thread to delete.
headers: Optional custom headers to include with the request.
Returns:
None
@@ -3372,7 +3452,7 @@ class SyncThreadsClient:
)
""" # noqa: E501
self.http.delete(f"/threads/{thread_id}")
self.http.delete(f"/threads/{thread_id}", headers=headers)
def search(
self,
@@ -3382,6 +3462,7 @@ class SyncThreadsClient:
status: Optional[ThreadStatus] = None,
limit: int = 10,
offset: int = 0,
headers: Optional[dict[str, str]] = None,
) -> list[Thread]:
"""Search for threads.
@@ -3392,6 +3473,7 @@ class SyncThreadsClient:
Must be one of 'idle', 'busy', 'interrupted' or 'error'.
limit: Limit on number of threads to return.
offset: Offset in threads table to start search from.
headers: Optional custom headers to include with the request.
Returns:
list[Thread]: List of the threads matching the search parameters.
@@ -3416,16 +3498,19 @@ class SyncThreadsClient:
payload["values"] = values
if status:
payload["status"] = status
return self.http.post(
"/threads/search",
json=payload,
)
return self.http.post("/threads/search", json=payload, headers=headers)
def copy(self, thread_id: str) -> None:
def copy(
self,
thread_id: str,
*,
headers: Optional[dict[str, str]] = None,
) -> None:
"""Copy a thread.
Args:
thread_id: The ID of the thread to copy.
headers: Optional custom headers to include with the request.
Returns:
None
@@ -3437,7 +3522,7 @@ class SyncThreadsClient:
)
""" # noqa: E501
return self.http.post(f"/threads/{thread_id}/copy", json=None)
return self.http.post(f"/threads/{thread_id}/copy", json=None, headers=headers)
def get_state(
self,
@@ -3446,6 +3531,7 @@ class SyncThreadsClient:
checkpoint_id: Optional[str] = None, # deprecated
*,
subgraphs: bool = False,
headers: Optional[dict[str, str]] = None,
) -> ThreadState:
"""Get the state of a thread.
@@ -3453,6 +3539,7 @@ class SyncThreadsClient:
thread_id: The ID of the thread to get the state of.
checkpoint: The checkpoint to get the state of.
subgraphs: Include subgraphs states.
headers: Optional custom headers to include with the request.
Returns:
ThreadState: the thread of the state.
@@ -3544,16 +3631,19 @@ class SyncThreadsClient:
return self.http.post(
f"/threads/{thread_id}/state/checkpoint",
json={"checkpoint": checkpoint, "subgraphs": subgraphs},
headers=headers,
)
elif checkpoint_id:
return self.http.get(
f"/threads/{thread_id}/state/{checkpoint_id}",
params={"subgraphs": subgraphs},
headers=headers,
)
else:
return self.http.get(
f"/threads/{thread_id}/state",
params={"subgraphs": subgraphs},
headers=headers,
)
def update_state(
@@ -3564,6 +3654,7 @@ class SyncThreadsClient:
as_node: Optional[str] = None,
checkpoint: Optional[Checkpoint] = None,
checkpoint_id: Optional[str] = None, # deprecated
headers: Optional[dict[str, str]] = None,
) -> ThreadUpdateStateResponse:
"""Update the state of a thread.
@@ -3572,6 +3663,7 @@ class SyncThreadsClient:
values: The values to update the state with.
as_node: Update the state as if this node had just executed.
checkpoint: The checkpoint to update the state of.
headers: Optional custom headers to include with the request.
Returns:
ThreadUpdateStateResponse: Response after updating a thread's state.
@@ -3606,7 +3698,9 @@ class SyncThreadsClient:
payload["checkpoint"] = checkpoint
if as_node:
payload["as_node"] = as_node
return self.http.post(f"/threads/{thread_id}/state", json=payload)
return self.http.post(
f"/threads/{thread_id}/state", json=payload, headers=headers
)
def get_history(
self,
@@ -3616,6 +3710,7 @@ class SyncThreadsClient:
before: Optional[str | Checkpoint] = None,
metadata: Optional[dict] = None,
checkpoint: Optional[Checkpoint] = None,
headers: Optional[dict[str, str]] = None,
) -> list[ThreadState]:
"""Get the state history of a thread.
@@ -3625,6 +3720,7 @@ class SyncThreadsClient:
limit: The maximum number of states to return.
before: Return states before this checkpoint.
metadata: Filter states by metadata key-value pairs.
headers: Optional custom headers to include with the request.
Returns:
list[ThreadState]: the state history of the thread.
@@ -3648,7 +3744,9 @@ class SyncThreadsClient:
payload["metadata"] = metadata
if checkpoint:
payload["checkpoint"] = checkpoint
return self.http.post(f"/threads/{thread_id}/history", json=payload)
return self.http.post(
f"/threads/{thread_id}/history", json=payload, headers=headers
)
class SyncRunsClient:
@@ -3688,6 +3786,7 @@ class SyncRunsClient:
multitask_strategy: Optional[MultitaskStrategy] = None,
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
) -> Iterator[StreamPart]: ...
@overload
@@ -3710,6 +3809,7 @@ class SyncRunsClient:
if_not_exists: Optional[IfNotExists] = None,
webhook: Optional[str] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
) -> Iterator[StreamPart]: ...
def stream(
@@ -3734,6 +3834,7 @@ class SyncRunsClient:
multitask_strategy: Optional[MultitaskStrategy] = None,
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
) -> Iterator[StreamPart]:
"""Create a run and stream the results.
@@ -3763,6 +3864,7 @@ class SyncRunsClient:
Must be either 'reject' (raise error if missing), or 'create' (create new thread).
after_seconds: The number of seconds to wait before starting the run.
Use to schedule future runs.
headers: Optional custom headers to include with the request.
Returns:
Iterator[StreamPart]: Iterator of stream results.
@@ -3820,7 +3922,10 @@ class SyncRunsClient:
else "/runs/stream"
)
return self.http.stream(
endpoint, "POST", json={k: v for k, v in payload.items() if v is not None}
endpoint,
"POST",
json={k: v for k, v in payload.items() if v is not None},
headers=headers,
)
@overload
@@ -3841,6 +3946,7 @@ class SyncRunsClient:
on_completion: Optional[OnCompletionBehavior] = None,
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
) -> Run: ...
@overload
@@ -3863,6 +3969,7 @@ class SyncRunsClient:
multitask_strategy: Optional[MultitaskStrategy] = None,
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
) -> Run: ...
def create(
@@ -3885,6 +3992,7 @@ class SyncRunsClient:
on_completion: Optional[OnCompletionBehavior] = None,
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
) -> Run:
"""Create a background run.
@@ -3911,6 +4019,7 @@ class SyncRunsClient:
Must be either 'reject' (raise error if missing), or 'create' (create new thread).
after_seconds: The number of seconds to wait before starting the run.
Use to schedule future runs.
headers: Optional custom headers to include with the request.
Returns:
Run: The created background run.
@@ -4001,18 +4110,22 @@ class SyncRunsClient:
}
payload = {k: v for k, v in payload.items() if v is not None}
if thread_id:
return self.http.post(f"/threads/{thread_id}/runs", json=payload)
return self.http.post(
f"/threads/{thread_id}/runs", json=payload, headers=headers
)
else:
return self.http.post("/runs", json=payload)
return self.http.post("/runs", json=payload, headers=headers)
def create_batch(self, payloads: list[RunCreate]) -> list[Run]:
def create_batch(
self, payloads: list[RunCreate], *, headers: Optional[dict[str, str]] = None
) -> list[Run]:
"""Create a batch of stateless background runs."""
def filter_payload(payload: RunCreate):
return {k: v for k, v in payload.items() if v is not None}
payloads = [filter_payload(payload) for payload in payloads]
return self.http.post("/runs/batch", json=payloads)
return self.http.post("/runs/batch", json=payloads, headers=headers)
@overload
def wait(
@@ -4033,6 +4146,7 @@ class SyncRunsClient:
multitask_strategy: Optional[MultitaskStrategy] = None,
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
) -> Union[list[dict], dict[str, Any]]: ...
@overload
@@ -4052,6 +4166,7 @@ class SyncRunsClient:
on_completion: Optional[OnCompletionBehavior] = None,
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
) -> Union[list[dict], dict[str, Any]]: ...
def wait(
@@ -4073,6 +4188,7 @@ class SyncRunsClient:
multitask_strategy: Optional[MultitaskStrategy] = None,
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
) -> Union[list[dict], dict[str, Any]]:
"""Create a run, wait until it finishes and return the final state.
@@ -4099,6 +4215,7 @@ class SyncRunsClient:
Must be either 'reject' (raise error if missing), or 'create' (create new thread).
after_seconds: The number of seconds to wait before starting the run.
Use to schedule future runs.
headers: Optional custom headers to include with the request.
Returns:
Union[list[dict], dict[str, Any]]: The output of the run.
@@ -4173,13 +4290,21 @@ class SyncRunsClient:
endpoint, json={k: v for k, v in payload.items() if v is not None}
)
def list(self, thread_id: str, *, limit: int = 10, offset: int = 0) -> List[Run]:
def list(
self,
thread_id: str,
*,
limit: int = 10,
offset: int = 0,
headers: Optional[dict[str, str]] = None,
) -> List[Run]:
"""List runs.
Args:
thread_id: The thread ID to list runs for.
limit: The maximum number of results to return.
offset: The number of results to skip.
headers: Optional custom headers to include with the request.
Returns:
List[Run]: The runs for the thread.
@@ -4193,14 +4318,23 @@ class SyncRunsClient:
)
""" # noqa: E501
return self.http.get(f"/threads/{thread_id}/runs?limit={limit}&offset={offset}")
return self.http.get(
f"/threads/{thread_id}/runs?limit={limit}&offset={offset}", headers=headers
)
def get(self, thread_id: str, run_id: str) -> Run:
def get(
self,
thread_id: str,
run_id: str,
*,
headers: Optional[dict[str, str]] = None,
) -> Run:
"""Get a run.
Args:
thread_id: The thread ID to get.
run_id: The run ID to get.
headers: Optional custom headers to include with the request.
Returns:
Run: Run object.
@@ -4214,7 +4348,7 @@ class SyncRunsClient:
""" # noqa: E501
return self.http.get(f"/threads/{thread_id}/runs/{run_id}")
return self.http.get(f"/threads/{thread_id}/runs/{run_id}", headers=headers)
def cancel(
self,
@@ -4223,6 +4357,7 @@ class SyncRunsClient:
*,
wait: bool = False,
action: CancelAction = "interrupt",
headers: Optional[dict[str, str]] = None,
) -> None:
"""Get a run.
@@ -4232,6 +4367,7 @@ class SyncRunsClient:
wait: Whether to wait until run has completed.
action: Action to take when cancelling the run. Possible values
are `interrupt` or `rollback`. Default is `interrupt`.
headers: Optional custom headers to include with the request.
Returns:
None
@@ -4249,14 +4385,22 @@ class SyncRunsClient:
return self.http.post(
f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}&action={action}",
json=None,
headers=headers,
)
def join(self, thread_id: str, run_id: str) -> dict:
def join(
self,
thread_id: str,
run_id: str,
*,
headers: Optional[dict[str, str]] = None,
) -> dict:
"""Block until a run is done. Returns the final state of the thread.
Args:
thread_id: The thread ID to join.
run_id: The run ID to join.
headers: Optional custom headers to include with the request.
Returns:
None
@@ -4269,7 +4413,9 @@ class SyncRunsClient:
)
""" # noqa: E501
return self.http.get(f"/threads/{thread_id}/runs/{run_id}/join")
return self.http.get(
f"/threads/{thread_id}/runs/{run_id}/join", headers=headers
)
def join_stream(
self,
@@ -4278,6 +4424,7 @@ class SyncRunsClient:
*,
stream_mode: Optional[Union[StreamMode, Sequence[StreamMode]]] = None,
cancel_on_disconnect: bool = False,
headers: Optional[dict[str, str]] = None,
) -> Iterator[StreamPart]:
"""Stream output from a run in real-time, until the run is done.
Output is not buffered, so any output produced before this call will
@@ -4290,6 +4437,7 @@ class SyncRunsClient:
when creating the run. Background runs default to having the union of all
stream modes.
cancel_on_disconnect: Whether to cancel the run when the stream is disconnected.
headers: Optional custom headers to include with the request.
Returns:
None
@@ -4310,14 +4458,22 @@ class SyncRunsClient:
"stream_mode": stream_mode,
"cancel_on_disconnect": cancel_on_disconnect,
},
headers=headers,
)
def delete(self, thread_id: str, run_id: str) -> None:
def delete(
self,
thread_id: str,
run_id: str,
*,
headers: Optional[dict[str, str]] = None,
) -> None:
"""Delete a run.
Args:
thread_id: The thread ID to delete.
run_id: The run ID to delete.
headers: Optional custom headers to include with the request.
Returns:
None
@@ -4330,7 +4486,7 @@ class SyncRunsClient:
)
""" # noqa: E501
self.http.delete(f"/threads/{thread_id}/runs/{run_id}")
self.http.delete(f"/threads/{thread_id}/runs/{run_id}", headers=headers)
class SyncCronClient:
@@ -4360,6 +4516,7 @@ class SyncCronClient:
interrupt_after: Optional[Union[All, list[str]]] = None,
webhook: Optional[str] = None,
multitask_strategy: Optional[str] = None,
headers: Optional[dict[str, str]] = None,
) -> Run:
"""Create a cron job for a thread.
@@ -4378,6 +4535,7 @@ class SyncCronClient:
webhook: Webhook to call after LangGraph API call is done.
multitask_strategy: Multitask strategy to use.
Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'.
headers: Optional custom headers to include with the request.
Returns:
Run: The cron run.
@@ -4411,7 +4569,9 @@ class SyncCronClient:
if multitask_strategy:
payload["multitask_strategy"] = multitask_strategy
payload = {k: v for k, v in payload.items() if v is not None}
return self.http.post(f"/threads/{thread_id}/runs/crons", json=payload)
return self.http.post(
f"/threads/{thread_id}/runs/crons", json=payload, headers=headers
)
def create(
self,
@@ -4425,6 +4585,7 @@ class SyncCronClient:
interrupt_after: Optional[Union[All, list[str]]] = None,
webhook: Optional[str] = None,
multitask_strategy: Optional[str] = None,
headers: Optional[dict[str, str]] = None,
) -> Run:
"""Create a cron run.
@@ -4440,6 +4601,7 @@ class SyncCronClient:
webhook: Webhook to call after LangGraph API call is done.
multitask_strategy: Multitask strategy to use.
Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'.
headers: Optional custom headers to include with the request.
Returns:
Run: The cron run.
@@ -4472,13 +4634,19 @@ class SyncCronClient:
if multitask_strategy:
payload["multitask_strategy"] = multitask_strategy
payload = {k: v for k, v in payload.items() if v is not None}
return self.http.post("/runs/crons", json=payload)
return self.http.post("/runs/crons", json=payload, headers=headers)
def delete(self, cron_id: str) -> None:
def delete(
self,
cron_id: str,
*,
headers: Optional[dict[str, str]] = None,
) -> None:
"""Delete a cron.
Args:
cron_id: The cron ID to delete.
headers: Optional custom headers to include with the request.
Returns:
None
@@ -4490,7 +4658,7 @@ class SyncCronClient:
)
""" # noqa: E501
self.http.delete(f"/runs/crons/{cron_id}")
self.http.delete(f"/runs/crons/{cron_id}", headers=headers)
def search(
self,
@@ -4499,6 +4667,7 @@ class SyncCronClient:
thread_id: Optional[str] = None,
limit: int = 10,
offset: int = 0,
headers: Optional[dict[str, str]] = None,
) -> list[Cron]:
"""Get a list of cron jobs.
@@ -4507,6 +4676,7 @@ class SyncCronClient:
thread_id: the thread ID to search for.
limit: The maximum number of results to return.
offset: The number of results to skip.
headers: Optional custom headers to include with the request.
Returns:
list[Cron]: The list of cron jobs returned by the search,
@@ -4551,7 +4721,7 @@ class SyncCronClient:
"offset": offset,
}
payload = {k: v for k, v in payload.items() if v is not None}
return self.http.post("/runs/crons/search", json=payload)
return self.http.post("/runs/crons/search", json=payload, headers=headers)
class SyncStoreClient:
@@ -4577,6 +4747,7 @@ class SyncStoreClient:
value: dict[str, Any],
index: Optional[Union[Literal[False], list[str]]] = None,
ttl: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
) -> None:
"""Store or update an item.
@@ -4586,6 +4757,8 @@ class SyncStoreClient:
value: A dictionary containing the item's data.
index: Controls search indexing - None (use defaults), False (disable), or list of field paths to index.
ttl: Optional time-to-live in minutes for the item, or None for no expiration.
headers: Optional custom headers to include with the request.
Returns:
None
@@ -4609,7 +4782,7 @@ class SyncStoreClient:
"index": index,
"ttl": ttl,
}
self.http.put("/store/items", json=_provided_vals(payload))
self.http.put("/store/items", json=_provided_vals(payload), headers=headers)
def get_item(
self,
@@ -4618,6 +4791,7 @@ class SyncStoreClient:
key: str,
*,
refresh_ttl: Optional[bool] = None,
headers: Optional[dict[str, str]] = None,
) -> Item:
"""Retrieve a single item.
@@ -4625,6 +4799,7 @@ class SyncStoreClient:
key: The unique identifier for the item.
namespace: Optional list of strings representing the namespace path.
refresh_ttl: Whether to refresh the TTL on this read operation. If None, uses the store's default behavior.
headers: Optional custom headers to include with the request.
Returns:
Item: The retrieved item.
@@ -4656,14 +4831,21 @@ class SyncStoreClient:
params = {"key": key, "namespace": ".".join(namespace)}
if refresh_ttl is not None:
params["refresh_ttl"] = refresh_ttl
return self.http.get("/store/items", params=params)
return self.http.get("/store/items", params=params, headers=headers)
def delete_item(self, namespace: Sequence[str], /, key: str) -> None:
def delete_item(
self,
namespace: Sequence[str],
/,
key: str,
headers: Optional[dict[str, str]] = None,
) -> None:
"""Delete an item.
Args:
key: The unique identifier for the item.
namespace: Optional list of strings representing the namespace path.
headers: Optional custom headers to include with the request.
Returns:
None
@@ -4675,7 +4857,9 @@ class SyncStoreClient:
key="item456",
)
"""
self.http.delete("/store/items", json={"key": key, "namespace": namespace})
self.http.delete(
"/store/items", json={"key": key, "namespace": namespace}, headers=headers
)
def search_items(
self,
@@ -4686,6 +4870,7 @@ class SyncStoreClient:
offset: int = 0,
query: Optional[str] = None,
refresh_ttl: Optional[bool] = None,
headers: Optional[dict[str, str]] = None,
) -> SearchItemsResponse:
"""Search for items within a namespace prefix.
@@ -4696,6 +4881,7 @@ class SyncStoreClient:
offset: Number of items to skip before returning results (default is 0).
query: Optional query for natural language search.
refresh_ttl: Whether to refresh the TTL on items returned by this search. If None, uses the store's default behavior.
headers: Optional custom headers to include with the request.
Returns:
List[Item]: A list of items matching the search criteria.
@@ -4736,7 +4922,9 @@ class SyncStoreClient:
"query": query,
"refresh_ttl": refresh_ttl,
}
return self.http.post("/store/items/search", json=_provided_vals(payload))
return self.http.post(
"/store/items/search", json=_provided_vals(payload), headers=headers
)
def list_namespaces(
self,
@@ -4745,6 +4933,7 @@ class SyncStoreClient:
max_depth: Optional[int] = None,
limit: int = 100,
offset: int = 0,
headers: Optional[dict[str, str]] = None,
) -> ListNamespaceResponse:
"""List namespaces with optional match conditions.
@@ -4754,6 +4943,7 @@ class SyncStoreClient:
max_depth: Optional integer specifying the maximum depth of namespaces to return.
limit: Maximum number of namespaces to return (default is 100).
offset: Number of namespaces to skip before returning results (default is 0).
headers: Optional custom headers to include with the request.
Returns:
List[List[str]]: A list of namespaces matching the criteria.
@@ -4783,7 +4973,9 @@ class SyncStoreClient:
"limit": limit,
"offset": offset,
}
return self.http.post("/store/namespaces", json=_provided_vals(payload))
return self.http.post(
"/store/namespaces", json=_provided_vals(payload), headers=headers
)
def _provided_vals(d: dict):
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-sdk"
version = "0.1.58"
version = "0.1.60"
description = "SDK for interacting with LangGraph API"
authors = []
license = "MIT"