mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-22 23:52:23 +02:00
Compare commits
62
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1af6b812c4 | ||
|
|
575de221fe | ||
|
|
d2fc5f0a0f | ||
|
|
8e4b8b11ff | ||
|
|
2808a7859a | ||
|
|
fb3c61ea4f | ||
|
|
69cecd872c | ||
|
|
9abc1c8174 | ||
|
|
de90ced29d | ||
|
|
b8b973fc0c | ||
|
|
89e3709a2a | ||
|
|
a7f012a19c | ||
|
|
9b05ab6453 | ||
|
|
5401d2ea81 | ||
|
|
4401612aa6 | ||
|
|
49bb08a3f9 | ||
|
|
4f2e9b838f | ||
|
|
d30da72f6e | ||
|
|
7f079adfee | ||
|
|
4c74af606f | ||
|
|
900824089b | ||
|
|
522caa643f | ||
|
|
ef71656f05 | ||
|
|
3ba7c7fbed | ||
|
|
5cdab86d48 | ||
|
|
f5fe7e5195 | ||
|
|
2b728410e9 | ||
|
|
30221da4a8 | ||
|
|
1c5a354a7d | ||
|
|
bfd271e00e | ||
|
|
9647b1e55f | ||
|
|
e4aa204110 | ||
|
|
2c29edadec | ||
|
|
96847e644b | ||
|
|
4b102638c2 | ||
|
|
7021ce3742 | ||
|
|
d0c0aa9697 | ||
|
|
520de30350 | ||
|
|
81c0d47363 | ||
|
|
87603d8a00 | ||
|
|
7ad7329c7d | ||
|
|
e981d27f84 | ||
|
|
71db4f2ad5 | ||
|
|
4ced277e2d | ||
|
|
34738fa566 | ||
|
|
0286c38784 | ||
|
|
fb5a1c4028 | ||
|
|
39d85466f8 | ||
|
|
3c797529bb | ||
|
|
0d185d43ed | ||
|
|
70b8391a89 | ||
|
|
1a37f2d5a2 | ||
|
|
949af8abe5 | ||
|
|
094255c3fe | ||
|
|
b6055ff3fe | ||
|
|
e082ba4f85 | ||
|
|
f36b7f61fb | ||
|
|
4095f0a927 | ||
|
|
8af09714ff | ||
|
|
5d7e818882 | ||
|
|
37429ff73b | ||
|
|
dd733a3389 |
@@ -43,7 +43,7 @@ jobs:
|
||||
run: |
|
||||
{
|
||||
echo 'OUTPUT<<EOF'
|
||||
make -s benchmark
|
||||
make -s benchmark-fast
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
- name: Compare benchmarks
|
||||
|
||||
@@ -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/.*" \
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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".
|
||||
|
||||
{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 |
@@ -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",
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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. |
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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>}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 © 2025 LangChain, Inc | <a href="#__consent">Consent Preferences</a>
|
||||
extra_css:
|
||||
- stylesheets/version_admonitions.css
|
||||
|
||||
Generated
+825
-63
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]()
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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:{}"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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:"),
|
||||
|
||||
@@ -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:"),
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
@@ -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": {},
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
@@ -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}`);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
Reference in New Issue
Block a user