mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-28 18:59:42 +02:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f36b7f61fb | ||
|
|
4095f0a927 | ||
|
|
8af09714ff | ||
|
|
5d7e818882 | ||
|
|
37429ff73b | ||
|
|
af7515c37a | ||
|
|
b97cda4290 | ||
|
|
01fed0fae2 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
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
|
||||
|
||||
@@ -684,7 +684,7 @@ class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
|
||||
store.put(("docs",), "doc2", {"text": "Other guide"}, index=False) # don't index
|
||||
|
||||
# Search by similarity
|
||||
results = store.search(("docs",), "programming guides", limit=2)
|
||||
results = store.search(("docs",), query="programming guides", limit=2)
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
@@ -2,6 +2,7 @@ import asyncio
|
||||
import inspect
|
||||
import json
|
||||
from copy import copy, deepcopy
|
||||
from dataclasses import replace
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
@@ -35,7 +36,7 @@ from typing_extensions import Annotated, get_args, get_origin
|
||||
|
||||
from langgraph.errors import GraphBubbleUp
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import Command
|
||||
from langgraph.types import Command, Send
|
||||
from langgraph.utils.runnable import RunnableCallable
|
||||
|
||||
INVALID_TOOL_NAME_ERROR_TEMPLATE = (
|
||||
@@ -239,25 +240,7 @@ class ToolNode(RunnableCallable):
|
||||
*executor.map(self._run_one, tool_calls, input_types, config_list)
|
||||
]
|
||||
|
||||
# preserve existing behavior for non-command tool outputs for backwards
|
||||
# compatibility
|
||||
if not any(isinstance(output, Command) for output in outputs):
|
||||
# TypedDict, pydantic, dataclass, etc. should all be able to load from dict
|
||||
return outputs if input_type == "list" else {self.messages_key: outputs}
|
||||
|
||||
# LangGraph will automatically handle list of Command and non-command node
|
||||
# updates
|
||||
combined_outputs: list[
|
||||
Command | list[ToolMessage] | dict[str, list[ToolMessage]]
|
||||
] = []
|
||||
for output in outputs:
|
||||
if isinstance(output, Command):
|
||||
combined_outputs.append(output)
|
||||
else:
|
||||
combined_outputs.append(
|
||||
[output] if input_type == "list" else {self.messages_key: [output]}
|
||||
)
|
||||
return combined_outputs
|
||||
return self._combine_tool_outputs(outputs, input_type)
|
||||
|
||||
async def _afunc(
|
||||
self,
|
||||
@@ -275,22 +258,50 @@ class ToolNode(RunnableCallable):
|
||||
*(self._arun_one(call, input_type, config) for call in tool_calls)
|
||||
)
|
||||
|
||||
# preserve existing behavior for non-command tool outputs for backwards compatibility
|
||||
return self._combine_tool_outputs(outputs, input_type)
|
||||
|
||||
def _combine_tool_outputs(
|
||||
self,
|
||||
outputs: list[ToolMessage],
|
||||
input_type: Literal["list", "dict", "tool_calls"],
|
||||
) -> list[Union[Command, list[ToolMessage], dict[str, list[ToolMessage]]]]:
|
||||
# preserve existing behavior for non-command tool outputs for backwards
|
||||
# compatibility
|
||||
if not any(isinstance(output, Command) for output in outputs):
|
||||
# TypedDict, pydantic, dataclass, etc. should all be able to load from dict
|
||||
return outputs if input_type == "list" else {self.messages_key: outputs}
|
||||
|
||||
# LangGraph will automatically handle list of Command and non-command node updates
|
||||
# LangGraph will automatically handle list of Command and non-command node
|
||||
# updates
|
||||
combined_outputs: list[
|
||||
Command | list[ToolMessage] | dict[str, list[ToolMessage]]
|
||||
] = []
|
||||
|
||||
# combine all parent commands with goto into a single parent command
|
||||
parent_command: Optional[Command] = None
|
||||
for output in outputs:
|
||||
if isinstance(output, Command):
|
||||
combined_outputs.append(output)
|
||||
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) + output.goto,
|
||||
)
|
||||
else:
|
||||
parent_command = output
|
||||
else:
|
||||
combined_outputs.append(output)
|
||||
else:
|
||||
combined_outputs.append(
|
||||
[output] if input_type == "list" else {self.messages_key: [output]}
|
||||
)
|
||||
|
||||
if parent_command:
|
||||
combined_outputs.append(parent_command)
|
||||
return combined_outputs
|
||||
|
||||
def _run_one(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-prebuilt"
|
||||
version = "0.1.4"
|
||||
version = "0.1.6"
|
||||
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
|
||||
authors = []
|
||||
license = "MIT"
|
||||
|
||||
@@ -17,7 +17,7 @@ from pydantic.v1 import ValidationError as ValidationErrorV1
|
||||
from langgraph.errors import NodeInterrupt
|
||||
from langgraph.prebuilt import ToolNode
|
||||
from langgraph.prebuilt.tool_node import TOOL_CALL_ERROR_TEMPLATE
|
||||
from langgraph.types import Command
|
||||
from langgraph.types import Command, Send
|
||||
from tests.conftest import IS_LANGCHAIN_CORE_030_OR_GREATER
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
@@ -1051,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,
|
||||
)
|
||||
]
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user