Merge branch 'main' into brace/interrupt-schema

This commit is contained in:
Brace Sproul
2025-01-13 13:08:37 -08:00
committed by GitHub
49 changed files with 2925 additions and 241 deletions
+23 -1
View File
@@ -109,9 +109,31 @@ jobs:
- name: Build
run: yarn build
test-js:
runs-on: ubuntu-latest
strategy:
matrix:
working-directory:
- "libs/sdk-js"
defaults:
run:
working-directory: ${{ matrix.working-directory }}
steps:
- uses: actions/checkout@v3
- name: Setup Node.js (LTS)
uses: actions/setup-node@v3
with:
node-version: "20"
cache: "yarn"
cache-dependency-path: ${{ matrix.working-directory }}/yarn.lock
- name: Install dependencies
run: yarn install
- name: Run tests
run: yarn test
ci_success:
name: "CI Success"
needs: [lint, lint-js, test, test-langgraph, test-scheduler-kafka, integration-test]
needs: [lint, lint-js, test, test-langgraph, test-scheduler-kafka, integration-test, test-js]
if: |
always()
runs-on: ubuntu-latest
+93
View File
@@ -0,0 +1,93 @@
import functools
from urllib3 import __version__ as urllib3version # type: ignore[import-untyped]
from urllib3 import connection # type: ignore[import-untyped]
def _ensure_str(s, encoding="utf-8", errors="strict") -> str:
if isinstance(s, str):
return s
if isinstance(s, bytes):
return s.decode(encoding, errors)
return str(s)
# Copied from https://github.com/urllib3/urllib3/blob/1c994dfc8c5d5ecaee8ed3eb585d4785f5febf6e/src/urllib3/connection.py#L231
def request(self, method, url, body=None, headers=None):
"""Make the request.
This function is based on the urllib3 request method, with modifications
to handle potential issues when using vcrpy in concurrent workloads.
Args:
self: The HTTPConnection instance.
method (str): The HTTP method (e.g., 'GET', 'POST').
url (str): The URL for the request.
body (Optional[Any]): The body of the request.
headers (Optional[dict]): Headers to send with the request.
Returns:
The result of calling the parent request method.
"""
# Update the inner socket's timeout value to send the request.
# This only triggers if the connection is re-used.
if getattr(self, "sock", None) is not None:
self.sock.settimeout(self.timeout)
if headers is None:
headers = {}
else:
# Avoid modifying the headers passed into .request()
headers = headers.copy()
if "user-agent" not in (_ensure_str(k.lower()) for k in headers):
headers["User-Agent"] = connection._get_default_user_agent()
# The above is all the same ^^^
# The following is different:
return self._parent_request(method, url, body=body, headers=headers)
_PATCHED = False
def patch_urllib3():
"""Patch the request method of urllib3 to avoid type errors when using vcrpy.
In concurrent workloads (such as the tracing background queue), the
connection pool can get in a state where an HTTPConnection is created
before vcrpy patches the HTTPConnection class. In urllib3 >= 2.0 this isn't
a problem since they use the proper super().request(...) syntax, but in older
versions, super(HTTPConnection, self).request is used, resulting in a TypeError
since self is no longer a subclass of "HTTPConnection" (which at this point
is vcr.stubs.VCRConnection).
This method patches the class to fix the super() syntax to avoid mixed inheritance.
In the case of the LangSmith tracing logic, it doesn't really matter since we always
exclude cache checks for calls to LangSmith.
The patch is only applied for urllib3 versions older than 2.0.
"""
global _PATCHED
if _PATCHED:
return
from packaging import version
if version.parse(urllib3version) >= version.parse("2.0"):
_PATCHED = True
return
# Lookup the parent class and its request method
parent_class = connection.HTTPConnection.__bases__[0]
parent_request = parent_class.request
def new_request(self, *args, **kwargs):
"""Handle parent request.
This method binds the parent's request method to self and then
calls our modified request function.
"""
self._parent_request = functools.partial(parent_request, self)
return request(self, *args, **kwargs)
connection.HTTPConnection.request = new_request
_PATCHED = True
+16 -1
View File
@@ -43,7 +43,9 @@ NOTEBOOKS_NO_EXECUTION = [
"docs/docs/tutorials/lats/lats.ipynb", # issues only when running with VCR
"docs/docs/tutorials/rag/langgraph_crag.ipynb", # flakiness from tavily
"docs/docs/tutorials/rag/langgraph_adaptive_rag.ipynb", # Cannot create a consistent method resolution error from VCR
"docs/docs/how-tos/map-reduce.ipynb" # flakiness from structured output, only when running with VCR
"docs/docs/how-tos/map-reduce.ipynb", # flakiness from structured output, only when running with VCR
"docs/docs/tutorials/tot/tot.ipynb",
"docs/docs/how-tos/visualization.ipynb"
]
@@ -86,6 +88,7 @@ def add_vcr_to_notebook(
) -> nbformat.NotebookNode:
"""Inject `with vcr.cassette` into each code cell of the notebook."""
uses_langsmith = False
# Inject VCR context manager into each code cell
for idx, cell in enumerate(notebook.cells):
if cell.cell_type != "code":
@@ -120,6 +123,9 @@ def add_vcr_to_notebook(
f" {line}" for line in lines
)
if any("hub.pull" in line or "from langsmith import" in line for line in lines):
uses_langsmith = True
# Add import statement
vcr_import_lines = [
"import nest_asyncio",
@@ -152,6 +158,15 @@ def add_vcr_to_notebook(
"custom_vcr.register_serializer('advanced_compressed', AdvancedCompressedSerializer())",
"custom_vcr.serializer = 'advanced_compressed'",
]
if uses_langsmith:
vcr_import_lines.extend(
# patch urllib3 to handle vcr errors, see more here:
# https://github.com/langchain-ai/langsmith-sdk/blob/main/python/langsmith/_internal/_patch.py
"import sys",
f"sys.path.insert(0, '{os.path.join(DOCS_PATH, '_scripts')}')",
"import _patch as patch_urllib3",
"patch_urllib3.patch_urllib3()",
)
import_cell = nbformat.v4.new_code_cell(source="\n".join(vcr_import_lines))
import_cell.pop("id", None)
notebook.cells.insert(0, import_cell)
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
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
@@ -1 +1 @@
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
@@ -1 +0,0 @@
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
@@ -1 +0,0 @@
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
+10 -10
View File
@@ -11,7 +11,7 @@ LangGraph Cloud is available within <a href="https://www.langchain.com/langsmith
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments.
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Cloud deployments.
1. In the top-right corner, select `+ New Deployment` to create a new deployment.
1. In the `Create New Deployment` panel, fill out the required fields.
1. `Deployment details`
@@ -38,7 +38,7 @@ When [creating a new deployment](#create-new-deployment), a new revision is crea
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments.
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Cloud deployments.
1. Select an existing deployment to create a new revision for.
1. In the `Deployment` view, in the top-right corner, select `+ New Revision`.
1. In the `New Revision` modal, fill out the required fields.
@@ -52,15 +52,15 @@ Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmi
1. Update the value of existing secrets or environment variables.
1. Select `Submit`. After a few seconds, the `New Revision` modal will close and the new revision will be queued for deployment.
## View Build and Deployment Logs
## View Build and Server Logs
Build and deployment logs are available for each revision.
Build and server logs are available for each revision.
Starting from the `LangGraph Cloud` view...
Starting from the `LangGraph Platform` view...
1. Select the desired revision from the `Revisions` table. A panel slides open from the right-hand side and the `Build` tab is selected by default, which displays build logs for the revision.
1. In the panel, select the `Deploy` tab to view deployment logs for the revision.
1. Within the `Deploy` tab, adjust the date/time range picker as needed. By default, the date/time range picker is set to the `Last 15 minutes`.
1. In the panel, select the `Server` tab to view server logs for the revision. Server logs are only available after a revision has been deployed.
1. Within the `Server` tab, adjust the date/time range picker as needed. By default, the date/time range picker is set to the `Last 7 days`.
## Interrupt Revision
@@ -69,7 +69,7 @@ Interrupting a revision will stop deployment of the revision.
!!! warning "Undefined Behavior"
Interrupted revisions have undefined behavior. This is only useful if you need to deploy a new revision and you already have a revision "stuck" in progress. In the future, this feature may be removed.
Starting from the `LangGraph Cloud` view...
Starting from the `LangGraph Platform` view...
1. Select the menu icon (three dots) on the right-hand side of the row for the desired revision from the `Revisions` table.
1. Select `Interrupt` from the menu.
@@ -79,13 +79,13 @@ Starting from the `LangGraph Cloud` view...
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments.
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Cloud deployments.
1. Select the menu icon (three dots) on the right-hand side of the row for the desired deployment and select `Delete`.
1. A `Confirmation` modal will appear. Select `Delete`.
## Deployment Settings
Starting from the `LangGraph Cloud` view...
Starting from the `LangGraph Platform` view...
1. In the top-right corner, select the gear icon (`Deployment Settings`).
1. Update the `Git Branch` to the desired branch.
@@ -279,6 +279,58 @@
}
}
},
"/v1/projects/{project_id}/revisions/{revision_id}/deploy": {
"post": {
"tags": ["Revisions (v1)"],
"summary": "Deploy Revision",
"description": "Deploy revision by ID.\n\nThis endpoint redeploys the deployment of a revision without rebuilding the image for the deployment. Redeploying the deployment of a revision may mitigate intermittent issues with a deployment.\n\nThe revision must be in the `DEPLOYED` status and must be the latest revision of the project.",
"operationId": "deploy_revision_projects__project_id__revisions__revision_id__deploy_post",
"parameters": [
{
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Project ID"
},
"name": "project_id",
"in": "path"
},
{
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Revision ID"
},
"name": "revision_id",
"in": "path"
}
],
"responses": {
"400": {
"description": "Revision is not in DEPLOYED status or revision is not the latest revision for the project.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Revision not found.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/projects/{project_id}/revisions/{revision_id}/interrupt": {
"post": {
"tags": ["Revisions (v1)"],
@@ -319,31 +371,6 @@
}
},
"schemas": {
"EnvVar": {
"type": "object",
"description": "An environment variable or secret.",
"properties": {
"name": {
"type": "string",
"description": "Environment variable or secret name.",
"required": true
},
"value": {
"type": "string",
"description": "Environment variable or secret value.",
"required": true
},
"type": {
"type": "string",
"enum": [
"default",
"secret"
],
"description": "Field to designate type of the environment variable (default) or secret.",
"required": true
}
}
},
"ContainerSpec": {
"type": "object",
"description": "Container specification for a revision's deployment.\n\nIf any field is omitted or set to `null`, the internal default value is used depending on the deployment type (`dev` or `prod`).",
@@ -404,6 +431,42 @@
}
}
},
"EnvVar": {
"type": "object",
"description": "An environment variable or secret.",
"properties": {
"name": {
"type": "string",
"description": "Environment variable or secret name.",
"required": true
},
"value": {
"type": "string",
"description": "Environment variable or secret value.",
"required": true
},
"type": {
"type": "string",
"enum": [
"default",
"secret"
],
"description": "Field to designate type of the environment variable (default) or secret.",
"required": true
}
}
},
"ErrorResponse": {
"type": "object",
"description": "Error response.",
"properties": {
"detail": {
"type": "string",
"description": "Error details.",
"required": true
}
}
},
"Project": {
"type": "object",
"description": "A project corresponds to a LangGraph Server deployment and the associated LangSmith tracing project.",
+4 -1
View File
@@ -289,4 +289,7 @@ RUN set -ex && \
RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph", "storm": "/deps/__outer_graphs/src/storm.py:graph"}'
```
```
???+ note "Updating your langgraph.json file"
The `langgraph dockerfile` command translates all the configuration in your `langgraph.json` file into Dockerfile commands. When using this command, you will have to re-run it whenever you update your `langgraph.json` file. Otherwise, your changes will not be reflected when you build or run the dockerfile.
+34 -2
View File
@@ -1,6 +1,6 @@
# Environment Variables
The LangGraph Cloud API supports specific environment variables for configuring a deployment.
The LangGraph Cloud Server supports specific environment variables for configuring a deployment.
## `LANGCHAIN_TRACING_SAMPLING_RATE`
@@ -10,10 +10,42 @@ See <a href="https://docs.smith.langchain.com/how_to_guides/tracing/sample_trace
## `LANGGRAPH_AUTH_TYPE`
Type of authentication for the LangGraph Cloud API deployment. Valid values: `langsmith`, `noop`.
Type of authentication for the LangGraph Cloud Server deployment. Valid values: `langsmith`, `noop`.
For deployments to LangGraph Cloud, this environment variable is set automatically. For local development or deployments where authentication is handled externally (e.g. self-hosted), set this environment variable to `noop`.
## `LANGSMITH_RUNS_ENDPOINTS`
For [Bring Your Own Cloud (BYOC)](../../concepts/bring_your_own_cloud.md) deployments with [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) only.
Set this environment variable to have a BYOC deployment send traces to a self-hosted LangSmith instance. The value of `LANGSMITH_RUNS_ENDPOINTS` is a JSON string: `{"<SELF_HOSTED_LANGSMITH_HOSTNAME>":"<LANGSMITH_API_KEY>"}`.
`SELF_HOSTED_LANGSMITH_HOSTNAME` is the hostname of the self-hosted LangSmith instance. It must be accessible to the BYOC deployment. `LANGSMITH_API_KEY` is a LangSmith API generated from the self-hosted LangSmith instance.
## `N_JOBS_PER_WORKER`
Number of jobs per worker for the LangGraph Cloud task queue. Defaults to `10`.
## `POSTGRES_URI_CUSTOM`
For [Bring Your Own Cloud (BYOC)](../../concepts/bring_your_own_cloud.md) deployments only.
Specify `POSTGRES_URI_CUSTOM` to use an externally managed Postgres instance. The value of `POSTGRES_URI_CUSTOM` must be a valid [Postgres connection URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS).
Postgres:
- Version 15.8 or higher.
- An initial database must be present and the connection URI must reference the database.
Control Plane Functionality:
- If `POSTGRES_URI_CUSTOM` is specified, the LangGraph Control Plane will not provision a database for the server.
- If `POSTGRES_URI_CUSTOM` is removed, the LangGraph Control Plane will not provision a database for the server and will not delete the externally managed Postgres instance.
- If `POSTGRES_URI_CUSTOM` is removed, deployment of the revision will not succeed. Once `POSTGRES_URI_CUSTOM` is specified, it must always be set for the lifecycle of the deployment.
- If the deployment is deleted, the LangGraph Control Plane will not delete the externally managed Postgres instance.
- The value of `POSTGRES_URI_CUSTOM` can be updated. For example, a password in the URI can be updated.
Database Connectivity:
- The externally managed Postgres instance must be accessible by the LangGraph Server service in the ECS cluster. The BYOC user is responsible for ensuring connectivity.
- For example, if an AWS RDS Postgres instance is provisioned, it can be provisioned in the same VPC (`langgraph-cloud-vpc`) as the ECS cluster with the `langgraph-cloud-service-sg` security group to ensure connectivity.
+3 -2
View File
@@ -39,6 +39,7 @@ LangChain has no direct access to the resources created in your cloud account, a
- Read CloudWatch metrics/logs to monitor your instances/push deployment logs
- https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonRDSFullAccess.html
- Provision `RDS` instances for your LangGraph Cloud instances
- Alternatively, an externally managed Postgres instance can be used instead of the default `RDS` instance. LangChain does not monitor or manage the externally managed Postgres instance. See details for [`POSTGRES_URI_CUSTOM` environment variable](../cloud/reference/env_var.md#postgres_uri_custom).
2. Either
- Tags an existing vpc / subnets as `langgraph-cloud-enabled`
- Creates a new vpc and subnets and tags them as `langgraph-cloud-enabled`
@@ -50,5 +51,5 @@ LangChain has no direct access to the resources created in your cloud account, a
Notes for customers using [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting):
- Creation of new LangGraph Cloud projects and revisions currently needs to be done on smith.langchain.com.
- You can however set up the project to trace to your self-hosted LangSmith instance if desired
- Creation of new LangGraph Cloud projects and revisions currently needs to be done on `smith.langchain.com`.
- However, you can set up the project to trace to your self-hosted LangSmith instance if desired. See details for [`LANGSMITH_RUNS_ENDPOINTS` environment variable](../cloud/reference/env_var.md#langsmith_runs_endpoints).
+10 -3
View File
@@ -28,6 +28,10 @@ The guide below will explain the differences between the deployment options.
The Self-Hosted Enterprise version is only available for the **Enterprise** plan.
!!! warning "Note"
The LangGraph Platform Deployments view (within LangSmith SaaS and self-hosted LangSmith) is not available for Self-Hosted Enterprise LangGraph deployments. Self-hosted LangGraph deployments are managed externally from LangSmith (e.g. there is no UI to manage these deployments).
With a Self-Hosted Enterprise deployment, you are responsible for managing the infrastructure, including setting up and maintaining required databases and Redis instances.
Youll build a Docker image using the [LangGraph CLI](./langgraph_cli.md), which can then be deployed on your own infrastructure.
@@ -43,6 +47,10 @@ For more information, please see:
The Self-Hosted Lite version is available for all plans.
!!! warning "Note"
The LangGraph Platform Deployments view (within LangSmith SaaS and self-hosted LangSmith) is not available for Self-Hosted Lite LangGraph deployments. Self-hosted LangGraph deployments are managed externally from LangSmith (e.g. there is no UI to manage these deployments).
The Self-Hosted Lite deployment option is a free (up to 1 million nodes executed), limited version of LangGraph Platform that you can run locally or in a self-hosted manner.
With a Self-Hosted Lite deployment, you are responsible for managing the infrastructure, including setting up and maintaining required databases and Redis instances.
@@ -61,12 +69,11 @@ For more information, please see:
The Cloud SaaS version of LangGraph Platform is only available for **Plus** and **Enterprise** plans.
The [Cloud SaaS](./langgraph_cloud.md) version of LangGraph Platform is hosted as part of [LangSmith](https://smith.langchain.com/).
The Cloud SaaS version of LangGraph Platform provides a simple way to deploy and manage your LangGraph applications.
This deployment option provides an integration with GitHub, allowing you to deploy code from any of your repositories on GitHub.
This deployment option provides access to the LangGraph Platform UI (within LangSmith) and an integration with GitHub, allowing you to deploy code from any of your repositories on GitHub.
For more information, please see:
@@ -81,7 +88,7 @@ For more information, please see:
The Bring Your Own Cloud version of LangGraph Platform is only available for **Enterprise** plans.
This combines the best of both worlds for Cloud and Self-Hosted. We manage the infrastructure, so you don't have to, but the infrastructure all runs within your cloud. This is currently only available on AWS.
This combines the best of both worlds for Cloud and Self-Hosted. Create your deployments through the LangGraph Platform UI (within LangSmith) and we manage the infrastructure so you don't have to. The infrastructure all runs within your cloud. This is currently only available on AWS.
For more information please see:
+3
View File
@@ -62,6 +62,9 @@ The server includes all API endpoints for your graph's runs, threads, assistants
The `langgraph dockerfile` command generates a [Dockerfile](https://docs.docker.com/reference/dockerfile/) that can be used to build images for and deploy instances of the [LangGraph API server](./langgraph_server.md). This is useful if you want to further customize the dockerfile or deploy in a more custom way.
??? note "Updating your langgraph.json file"
The `langgraph dockerfile` command translates all the configuration in your `langgraph.json` file into Dockerfile commands. When using this command, you will have to re-run it whenever you update your `langgraph.json` file. Otherwise, your changes will not be reflected when you build or run the dockerfile.
## Related
- [LangGraph CLI API Reference](../cloud/reference/cli.md)
+4
View File
@@ -32,6 +32,10 @@ To use the Self-Hosted Enterprise version, you must acquire a license key that y
- Build the docker image for [LangGraph Server](./langgraph_server.md) using the [LangGraph CLI](./langgraph_cli.md).
- Deploy a web server that will run the docker image and pass in the necessary environment variables.
!!! warning "Note"
The LangGraph Platform Deployments view (within LangSmith SaaS and self-hosted LangSmith) is not available for Self-Hosted Lite or Self-Hosted Enterprise LangGraph deployments. Self-hosted LangGraph deployments are managed externally from LangSmith (e.g. there is no UI to manage these deployments).
For step-by-step instructions, see [How to set up a self-hosted deployment of LangGraph](../how-tos/deploy-self-hosted.md).
## Helm Chart
+8 -1
View File
@@ -37,7 +37,7 @@ async def authenticate(authorization: str) -> str:
detail="Invalid token"
)
# Optional: Add authorization rules
# Add authorization rules to actually control access to resources
@my_auth.on
async def add_owner(
ctx: Auth.types.AuthContext,
@@ -48,6 +48,13 @@ async def add_owner(
metadata = value.setdefault("metadata", {})
metadata.update(filters)
return filters
# Assumes you organize information in store like (user_id, resource_type, resource_id)
@my_auth.on.store()
async def authorize_store(ctx: Auth.types.AuthContext, value: dict):
namespace: tuple = value["namespace"]
assert namespace[0] == ctx.user.identity, "Not authorized"
```
## 2. Update configuration
+1
View File
@@ -29,6 +29,7 @@ You will eventually need to pass in the following environment variables to the L
- `DATABASE_URI`: Postgres connection details. Postgres will be used to store assistants, threads, runs, persist thread state and long term memory, and to manage the state of the background task queue with 'exactly once' semantics.
- `LANGSMITH_API_KEY`: (If using [Self-Hosted Lite](../concepts/deployment_options.md#self-hosted-lite)) LangSmith API key. This will be used to authenticate ONCE at server start up.
- `LANGGRAPH_CLOUD_LICENSE_KEY`: (If using [Self-Hosted Enterprise](../concepts/deployment_options.md#self-hosted-enterprise)) LangGraph Platform license key. This will be used to authenticate ONCE at server start up.
- `LANGCHAIN_ENDPOINT`: To send traces to a [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) instance, set `LANGCHAIN_ENDPOINT` to the hostname of the self-hosted LangSmith instance.
## Build the Docker Image
@@ -208,7 +208,7 @@
"from typing import Optional\n",
"\n",
"from langchain.chat_models import init_chat_model\n",
"from langchain_core.tools import InjectedToolArg\n",
"from langgraph.prebuilt import InjectedStore\n",
"from langgraph.store.base import BaseStore\n",
"from typing_extensions import Annotated\n",
"\n",
@@ -232,7 +232,7 @@
" content: str,\n",
" *,\n",
" memory_id: Optional[uuid.UUID] = None,\n",
" store: Annotated[BaseStore, InjectedToolArg],\n",
" store: Annotated[BaseStore, InjectedStore],\n",
"):\n",
" \"\"\"Upsert a memory in the database.\"\"\"\n",
" # The LLM can use this tool to store a new memory\n",
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -42,7 +42,10 @@
"checkpointer = # postgres checkpointer (see examples below)\n",
"graph = builder.compile(checkpointer=checkpointer)\n",
"...\n",
"```"
"```\n",
"\n",
"!!! info \"Setup\n",
" You need to run `.setup()` once on your checkpointer to initialize the database before you can use it."
]
},
{
@@ -275,6 +275,13 @@ async def on_assistants(
status_code=403,
detail="User lacks the required permissions.",
)
# Assumes you organize information in store like (user_id, resource_type, resource_id)
@auth.on.store()
async def authorize_store(ctx: Auth.types.AuthContext, value: dict):
# The "namespace" field for each store item is a tuple you can think of as the directory of an item.
namespace: tuple = value["namespace"]
assert namespace[0] == ctx.user.identity, "Not authorized"
```
Notice that instead of one global handler, we now have specific handlers for:
@@ -246,7 +246,7 @@
"\n",
"Define the (`fetch_user_flight_information`) tool to let the agent see the current user's flight information. Then define tools to search for flights and manage the passenger's bookings stored in the SQL database.\n",
"\n",
"We the can [access the RunnableConfig](https://python.langchain.com/docs/how_to/tool_configure/#inferring-by-parameter-type) for a given run to check the `passenger_id` of the user accessing this application. The LLM never has to provide these explicitly, they are provided for a given invocation of the graph so that each user cannot access other passengers' booking information.\n",
"We then can [access the RunnableConfig](https://python.langchain.com/docs/how_to/tool_configure/#inferring-by-parameter-type) for a given run to check the `passenger_id` of the user accessing this application. The LLM never has to provide these explicitly, they are provided for a given invocation of the graph so that each user cannot access other passengers' booking information.\n",
"\n",
"<div class=\"admonition warning\">\n",
" <p class=\"admonition-title\">Compatibility</p>\n",
@@ -444,7 +444,7 @@
"\n",
" # Check the signed-in user actually has this ticket\n",
" cursor.execute(\n",
" \"SELECT flight_id FROM tickets WHERE ticket_no = ? AND passenger_id = ?\",\n",
" \"SELECT ticket_no FROM tickets WHERE ticket_no = ? AND passenger_id = ?\",\n",
" (ticket_no, passenger_id),\n",
" )\n",
" current_ticket = cursor.fetchone()\n",
@@ -3423,7 +3423,7 @@
"\n",
"#### Utility\n",
"\n",
"Create a function to make an \"entry\" node for each workflow, stating \"the current assistant ix `assistant_name`\"."
"Create a function to make an \"entry\" node for each workflow, stating \"the current assistant is `assistant_name`\"."
]
},
{
@@ -4444,7 +4444,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
"version": "3.12.3"
}
},
"nbformat": 4,
@@ -58,8 +58,6 @@ MIGRATIONS = [
);""",
"ALTER TABLE checkpoint_blobs ALTER COLUMN blob DROP not null;",
"""
""",
"""
CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoints_thread_id_idx ON checkpoints(thread_id);
""",
"""
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-checkpoint-postgres"
version = "2.0.9"
version = "2.0.10"
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
authors = []
license = "MIT"
@@ -1,5 +1,6 @@
# type: ignore
import re
from contextlib import contextmanager
from typing import Any, Optional
from uuid import uuid4
@@ -782,3 +783,10 @@ def test_scores(
assert len(results) == 1
assert results[0].score == pytest.approx(similarities[0], abs=1e-3)
def test_nonnull_migrations() -> None:
_leading_comment_remover = re.compile(r"^/\*.*?\*/")
for migration in PostgresStore.MIGRATIONS:
statement = _leading_comment_remover.sub("", migration).split()[0]
assert statement.strip()
@@ -1,5 +1,6 @@
# type: ignore
import re
from contextlib import contextmanager
from typing import Any
from uuid import uuid4
@@ -238,3 +239,10 @@ def test_null_chars(saver_name: str, test_data) -> None:
list(saver.list(None, filter={"my_key": "abc"}))[0].metadata["my_key"]
== "abc"
)
def test_nonnull_migrations() -> None:
_leading_comment_remover = re.compile(r"^/\*.*?\*/")
for migration in PostgresSaver.MIGRATIONS:
statement = _leading_comment_remover.sub("", migration).split()[0]
assert statement.strip()
+2 -2
View File
@@ -100,7 +100,7 @@ class Config(TypedDict, total=False):
def _parse_version(version_str: str) -> tuple[int, int]:
"""Parse a version string into a tuple of (major, minor)."""
try:
major, minor = map(int, version_str.split("."))
major, minor = map(int, version_str.split("-")[0].split("."))
return (major, minor)
except ValueError:
raise click.UsageError(f"Invalid version format: {version_str}") from None
@@ -159,7 +159,7 @@ def validate_config(config: Config) -> Config:
if config.get("python_version"):
pyversion = config["python_version"]
if not pyversion.count(".") == 1 or not all(
part.isdigit() for part in pyversion.split(".")
part.isdigit() for part in pyversion.split("-")[0].split(".")
):
raise click.UsageError(
f"Invalid Python version format: {pyversion}. "
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-cli"
version = "0.1.65"
version = "0.1.66"
description = "CLI for interacting with LangGraph API"
authors = []
license = "MIT"
+6
View File
@@ -91,6 +91,12 @@ def test_validate_config():
validate_config({"python_version": "3.10"})
assert "Minimum required version" in str(exc_info.value)
config = validate_config({"python_version": "3.11-bullseye", "dependencies": ["."], "graphs": {"agent": "./agent.py:graph"}})
assert config["python_version"] == "3.11-bullseye"
config = validate_config({"python_version": "3.12-slim", "dependencies": ["."], "graphs": {"agent": "./agent.py:graph"}})
assert config["python_version"] == "3.12-slim"
def test_validate_config_file():
with tempfile.TemporaryDirectory() as tmpdir:
@@ -1,4 +1,13 @@
from typing import Callable, Literal, Optional, Sequence, Type, TypeVar, Union, cast
from typing import (
Callable,
Literal,
Optional,
Sequence,
Type,
TypeVar,
Union,
cast,
)
from langchain_core.language_models import BaseChatModel, LanguageModelLike
from langchain_core.messages import AIMessage, BaseMessage, SystemMessage, ToolMessage
@@ -8,11 +17,12 @@ from langchain_core.runnables import (
RunnableConfig,
)
from langchain_core.tools import BaseTool
from pydantic import BaseModel
from typing_extensions import Annotated, TypedDict
from langgraph._api.deprecation import deprecated_parameter
from langgraph.errors import ErrorCode, create_error_message
from langgraph.graph import StateGraph
from langgraph.graph import END, StateGraph
from langgraph.graph.graph import CompiledGraph
from langgraph.graph.message import add_messages
from langgraph.managed import IsLastStep, RemainingSteps
@@ -22,11 +32,14 @@ from langgraph.store.base import BaseStore
from langgraph.types import Checkpointer
from langgraph.utils.runnable import RunnableCallable
StructuredResponse = Union[dict, BaseModel]
StructuredResponseSchema = Union[dict, type[BaseModel]]
# We create the AgentState that we will pass around
# This simply involves a list of messages
# We want steps to return messages to append to the list
# So we annotate the messages attribute with operator.add
# So we annotate the messages attribute with `add_messages` reducer
class AgentState(TypedDict):
"""The state of the agent."""
@@ -36,6 +49,8 @@ class AgentState(TypedDict):
remaining_steps: RemainingSteps
structured_response: StructuredResponse
StateSchema = TypeVar("StateSchema", bound=AgentState)
StateSchemaType = Type[StateSchema]
@@ -162,6 +177,19 @@ def _should_bind_tools(model: LanguageModelLike, tools: Sequence[BaseTool]) -> b
return False
def _get_model(model: LanguageModelLike) -> BaseChatModel:
"""Get the underlying model from a RunnableBinding or return the model itself."""
if isinstance(model, RunnableBinding):
model = model.bound
if not isinstance(model, BaseChatModel):
raise TypeError(
f"Expected `model` to be a ChatModel or RunnableBinding (e.g. model.bind_tools(...)), got {type(model)}"
)
return model
def _validate_chat_history(
messages: Sequence[BaseMessage],
) -> None:
@@ -201,6 +229,9 @@ def create_react_agent(
state_schema: Optional[StateSchemaType] = None,
messages_modifier: Optional[MessagesModifier] = None,
state_modifier: Optional[StateModifier] = None,
response_format: Optional[
Union[StructuredResponseSchema, tuple[str, StructuredResponseSchema]]
] = None,
checkpointer: Optional[Checkpointer] = None,
store: Optional[BaseStore] = None,
interrupt_before: Optional[list[str]] = None,
@@ -236,6 +267,25 @@ def create_react_agent(
- str: This is converted to a SystemMessage and added to the beginning of the list of messages in state["messages"].
- Callable: This function should take in full graph state and the output is then passed to the language model.
- Runnable: This runnable should take in full graph state and the output is then passed to the language model.
response_format: An optional schema for the final agent output.
If provided, output will be formatted to match the given schema and returned in the 'structured_response' state key.
If not provided, `structured_response` will not be present in the output state.
Can be passed in as:
- an OpenAI function/tool schema,
- a JSON Schema,
- a TypedDict class,
- or a Pydantic class.
- a tuple (prompt, schema), where schema is one of the above.
The prompt will be used together with the model that is being used to generate the structured response.
!!! Important
`response_format` requires the model to support `.with_structured_output`
!!! Note
The graph will make a separate call to the LLM to generate the structured response after the agent loop is finished.
This is not the only strategy to get structured responses, see more options in [this guide](https://langchain-ai.github.io/langgraph/how-tos/react-agent-structured-output/).
checkpointer: An optional checkpoint saver object. This is used for persisting
the state of the graph (e.g., as chat memory) for a single thread (e.g., a single conversation).
store: An optional store object. This is used for persisting data
@@ -527,9 +577,11 @@ def create_react_agent(
"""
if state_schema is not None:
if missing_keys := {"messages", "is_last_step"} - set(
state_schema.__annotations__
):
required_keys = {"messages", "remaining_steps"}
if response_format is not None:
required_keys.add("structured_response")
if missing_keys := required_keys - set(state_schema.__annotations__):
raise ValueError(f"Missing required key(s) {missing_keys} in state_schema")
if isinstance(tools, ToolExecutor):
@@ -554,6 +606,10 @@ def create_react_agent(
)
model_runnable = preprocessor | model
# If any of the tools are configured to return_directly after running,
# our graph needs to check if these were called
should_return_direct = {t.name for t in tool_classes if t.return_direct}
# Define the function that calls the model
def call_model(state: AgentState, config: RunnableConfig) -> AgentState:
_validate_chat_history(state["messages"])
@@ -629,11 +685,54 @@ def create_react_agent(
# We return a list, because this will get added to the existing list
return {"messages": [response]}
def generate_structured_response(
state: AgentState, config: RunnableConfig
) -> AgentState:
# NOTE: we exclude the last message because there is enough information
# for the LLM to generate the structured response
messages = state["messages"][:-1]
structured_response_schema = response_format
if isinstance(response_format, tuple):
system_prompt, structured_response_schema = response_format
messages = [SystemMessage(content=system_prompt)] + list(messages)
model_with_structured_output = _get_model(model).with_structured_output(
cast(StructuredResponseSchema, structured_response_schema)
)
response = model_with_structured_output.invoke(messages, config)
return {"structured_response": response}
async def agenerate_structured_response(
state: AgentState, config: RunnableConfig
) -> AgentState:
# NOTE: we exclude the last message because there is enough information
# for the LLM to generate the structured response
messages = state["messages"][:-1]
structured_response_schema = response_format
if isinstance(response_format, tuple):
system_prompt, structured_response_schema = response_format
messages = [SystemMessage(content=system_prompt)] + list(messages)
model_with_structured_output = _get_model(model).with_structured_output(
cast(StructuredResponseSchema, structured_response_schema)
)
response = await model_with_structured_output.ainvoke(messages, config)
return {"structured_response": response}
if not tool_calling_enabled:
# Define a new graph
workflow = StateGraph(state_schema or AgentState)
workflow.add_node("agent", RunnableCallable(call_model, acall_model))
workflow.set_entry_point("agent")
if response_format is not None:
workflow.add_node(
"generate_structured_response",
RunnableCallable(
generate_structured_response, agenerate_structured_response
),
)
workflow.add_edge("agent", "generate_structured_response")
return workflow.compile(
checkpointer=checkpointer,
store=store,
@@ -643,12 +742,12 @@ def create_react_agent(
)
# Define the function that determines whether to continue or not
def should_continue(state: AgentState) -> Literal["tools", "__end__"]:
def should_continue(state: AgentState) -> str:
messages = state["messages"]
last_message = messages[-1]
# If there is no function call, then we finish
if not isinstance(last_message, AIMessage) or not last_message.tool_calls:
return "__end__"
return END if response_format is None else "generate_structured_response"
# Otherwise if there is, we continue
else:
return "tools"
@@ -664,6 +763,19 @@ def create_react_agent(
# This means that this node is the first one called
workflow.set_entry_point("agent")
# Add a structured output node if response_format is provided
if response_format is not None:
workflow.add_node(
"generate_structured_response",
RunnableCallable(
generate_structured_response, agenerate_structured_response
),
)
workflow.add_edge("generate_structured_response", END)
should_continue_destinations = ["tools", "generate_structured_response"]
else:
should_continue_destinations = ["tools", END]
# We now add a conditional edge
workflow.add_conditional_edges(
# First, we define the start node. We use `agent`.
@@ -671,18 +783,15 @@ def create_react_agent(
"agent",
# Next, we pass in the function that will determine which node is called next.
should_continue,
path_map=should_continue_destinations,
)
# If any of the tools are configured to return_directly after running,
# our graph needs to check if these were called
should_return_direct = {t.name for t in tool_classes if t.return_direct}
def route_tool_responses(state: AgentState) -> Literal["agent", "__end__"]:
for m in reversed(state["messages"]):
if not isinstance(m, ToolMessage):
break
if m.name in should_return_direct:
return "__end__"
return END
return "agent"
if should_return_direct:
+3 -3
View File
@@ -965,13 +965,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
[[package]]
name = "jinja2"
version = "3.1.4"
version = "3.1.5"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
files = [
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
{file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
{file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
]
[package.dependencies]
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph"
version = "0.2.61"
version = "0.2.62"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
license = "MIT"
@@ -2832,10 +2832,10 @@
'''
# ---
# name: test_prebuilt_tool_chat
'{"$defs": {"BaseMessage": {"additionalProperties": true, "description": "Base abstract message class.\\n\\nMessages are the inputs and outputs of ChatModels.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "type"], "title": "BaseMessage", "type": "object"}}, "properties": {"messages": {"items": {"$ref": "#/$defs/BaseMessage"}, "title": "Messages", "type": "array"}}, "required": ["messages"], "title": "LangGraphInput", "type": "object"}'
'{"$defs": {"BaseMessage": {"additionalProperties": true, "description": "Base abstract message class.\\n\\nMessages are the inputs and outputs of ChatModels.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "type"], "title": "BaseMessage", "type": "object"}, "BaseModel": {"properties": {}, "title": "BaseModel", "type": "object"}}, "properties": {"messages": {"items": {"$ref": "#/$defs/BaseMessage"}, "title": "Messages", "type": "array"}, "structured_response": {"anyOf": [{"type": "object"}, {"$ref": "#/$defs/BaseModel"}], "title": "Structured Response"}}, "required": ["messages", "structured_response"], "title": "LangGraphInput", "type": "object"}'
# ---
# name: test_prebuilt_tool_chat.1
'{"$defs": {"BaseMessage": {"additionalProperties": true, "description": "Base abstract message class.\\n\\nMessages are the inputs and outputs of ChatModels.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "type"], "title": "BaseMessage", "type": "object"}}, "properties": {"messages": {"items": {"$ref": "#/$defs/BaseMessage"}, "title": "Messages", "type": "array"}}, "required": ["messages"], "title": "LangGraphOutput", "type": "object"}'
'{"$defs": {"BaseMessage": {"additionalProperties": true, "description": "Base abstract message class.\\n\\nMessages are the inputs and outputs of ChatModels.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "type"], "title": "BaseMessage", "type": "object"}, "BaseModel": {"properties": {}, "title": "BaseModel", "type": "object"}}, "properties": {"messages": {"items": {"$ref": "#/$defs/BaseMessage"}, "title": "Messages", "type": "array"}, "structured_response": {"anyOf": [{"type": "object"}, {"$ref": "#/$defs/BaseModel"}], "title": "Structured Response"}}, "required": ["messages", "structured_response"], "title": "LangGraphOutput", "type": "object"}'
# ---
# name: test_prebuilt_tool_chat.2
'''
+50 -2
View File
@@ -1,4 +1,5 @@
import dataclasses
import inspect
import json
from functools import partial
from typing import (
@@ -31,7 +32,7 @@ from langchain_core.outputs import ChatGeneration, ChatResult
from langchain_core.runnables import Runnable, RunnableLambda
from langchain_core.tools import BaseTool, ToolException
from langchain_core.tools import tool as dec_tool
from pydantic import BaseModel, ValidationError
from pydantic import BaseModel, Field, ValidationError
from pydantic.v1 import BaseModel as BaseModelV1
from pydantic.v1 import ValidationError as ValidationErrorV1
from typing_extensions import TypedDict
@@ -46,7 +47,11 @@ from langgraph.prebuilt import (
create_react_agent,
tools_condition,
)
from langgraph.prebuilt.chat_agent_executor import AgentState, _validate_chat_history
from langgraph.prebuilt.chat_agent_executor import (
AgentState,
StructuredResponse,
_validate_chat_history,
)
from langgraph.prebuilt.tool_node import (
TOOL_CALL_ERROR_TEMPLATE,
InjectedState,
@@ -70,6 +75,7 @@ pytestmark = pytest.mark.anyio
class FakeToolCallingModel(BaseChatModel):
tool_calls: Optional[list[list[ToolCall]]] = None
structured_response: Optional[StructuredResponse] = None
index: int = 0
tool_style: Literal["openai", "anthropic"] = "openai"
@@ -97,6 +103,14 @@ class FakeToolCallingModel(BaseChatModel):
def _llm_type(self) -> str:
return "fake-tool-call-model"
def with_structured_output(
self, schema: Type[BaseModel]
) -> Runnable[LanguageModelInput, StructuredResponse]:
if self.structured_response is None:
raise ValueError("Structured response is not set")
return RunnableLambda(lambda x: self.structured_response)
def bind_tools(
self,
tools: Sequence[Union[Dict[str, Any], Type[BaseModel], Callable, BaseTool]],
@@ -510,6 +524,34 @@ def test__infer_handled_types() -> None:
_infer_handled_types(handler)
@pytest.mark.skipif(
not IS_LANGCHAIN_CORE_030_OR_GREATER,
reason="Pydantic v1 is required for this test to pass in langchain-core < 0.3",
)
def test_react_agent_with_structured_response() -> None:
class WeatherResponse(BaseModel):
temperature: float = Field(description="The temperature in fahrenheit")
tool_calls = [[{"args": {}, "id": "1", "name": "get_weather"}], []]
def get_weather():
"""Get the weather"""
return "The weather is sunny and 75°F."
expected_structured_response = WeatherResponse(temperature=75)
model = FakeToolCallingModel(
tool_calls=tool_calls, structured_response=expected_structured_response
)
for response_format in (WeatherResponse, ("Meow", WeatherResponse)):
agent = create_react_agent(
model, [get_weather], response_format=response_format
)
response = agent.invoke({"messages": [HumanMessage("What's the weather?")]})
assert response["structured_response"] == expected_structured_response
assert len(response["messages"]) == 4
assert response["messages"][-2].content == "The weather is sunny and 75°F."
# tools for testing Too
def tool1(some_val: int, some_other_val: str) -> str:
"""Tool 1 docstring."""
@@ -2040,3 +2082,9 @@ def test__get_state_args() -> None:
return 0.0
assert _get_state_args(foo) == {"a": None, "b": "bar"}
def test_inspect_react() -> None:
model = FakeToolCallingModel(tool_calls=[])
agent = create_react_agent(model, [])
inspect.getclosurevars(agent.nodes["agent"].bound.func)
+17
View File
@@ -0,0 +1,17 @@
/** @type {import('jest').Config} */
export default {
preset: 'ts-jest',
testEnvironment: 'node',
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
useESM: true,
},
],
},
};
+7 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@langchain/langgraph-sdk",
"version": "0.0.33",
"version": "0.0.36",
"description": "Client library for interacting with the LangGraph API",
"type": "module",
"packageManager": "yarn@1.22.19",
@@ -9,7 +9,8 @@
"build": "yarn clean && yarn lc_build --create-entrypoints --pre --tree-shaking",
"prepublish": "yarn run build",
"format": "prettier --write src",
"lint": "prettier --check src && tsc --noEmit"
"lint": "prettier --check src && tsc --noEmit",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts"
},
"main": "index.js",
"license": "MIT",
@@ -20,12 +21,16 @@
"uuid": "^9.0.0"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@langchain/scripts": "^0.1.4",
"@tsconfig/recommended": "^1.0.2",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.12",
"@types/uuid": "^9.0.1",
"concat-md": "^0.5.1",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"ts-jest": "^29.1.2",
"typedoc": "^0.26.1",
"typedoc-plugin-markdown": "^4.1.0",
"typescript": "^5.4.5"
+21 -11
View File
@@ -18,6 +18,8 @@ import {
ListNamespaceResponse,
Item,
ThreadStatus,
CronCreateResponse,
CronCreateForThreadResponse,
} from "./schema.js";
import { AsyncCaller, AsyncCallerParams } from "./utils/async_caller.js";
import {
@@ -35,7 +37,7 @@ import {
} from "./types.js";
import { mergeSignals } from "./utils/signals.js";
import { getEnvironmentVariable } from "./utils/env.js";
import { _getFetchImplementation } from "./singletons/fetch.js";
/**
* Get the API key from the environment.
* Precedence:
@@ -162,7 +164,8 @@ class BaseClient {
signal?: AbortSignal;
},
): Promise<T> {
const response = await this.asyncCaller.fetch(
const response = await this.asyncCaller.call(
_getFetchImplementation(),
...this.prepareFetchOptions(path, options),
);
if (response.status === 202 || response.status === 204) {
@@ -184,7 +187,7 @@ export class CronsClient extends BaseClient {
threadId: string,
assistantId: string,
payload?: CronsCreatePayload,
): Promise<Run> {
): Promise<CronCreateForThreadResponse> {
const json: Record<string, any> = {
schedule: payload?.schedule,
input: payload?.input,
@@ -197,10 +200,13 @@ export class CronsClient extends BaseClient {
multitask_strategy: payload?.multitaskStrategy,
if_not_exists: payload?.ifNotExists,
};
return this.fetch<Run>(`/threads/${threadId}/runs/crons`, {
method: "POST",
json,
});
return this.fetch<CronCreateForThreadResponse>(
`/threads/${threadId}/runs/crons`,
{
method: "POST",
json,
},
);
}
/**
@@ -212,7 +218,7 @@ export class CronsClient extends BaseClient {
async create(
assistantId: string,
payload?: CronsCreatePayload,
): Promise<Run> {
): Promise<CronCreateResponse> {
const json: Record<string, any> = {
schedule: payload?.schedule,
input: payload?.input,
@@ -225,7 +231,7 @@ export class CronsClient extends BaseClient {
multitask_strategy: payload?.multitaskStrategy,
if_not_exists: payload?.ifNotExists,
};
return this.fetch<Run>(`/runs/crons`, {
return this.fetch<CronCreateResponse>(`/runs/crons`, {
method: "POST",
json,
});
@@ -747,7 +753,8 @@ export class RunsClient extends BaseClient {
const endpoint =
threadId == null ? `/runs/stream` : `/threads/${threadId}/runs/stream`;
const response = await this.asyncCaller.fetch(
const response = await this.asyncCaller.call(
_getFetchImplementation(),
...this.prepareFetchOptions(endpoint, {
method: "POST",
json,
@@ -817,6 +824,8 @@ export class RunsClient extends BaseClient {
command: payload?.command,
config: payload?.config,
metadata: payload?.metadata,
stream_mode: payload?.streamMode,
stream_subgraphs: payload?.streamSubgraphs,
assistant_id: assistantId,
interrupt_before: payload?.interruptBefore,
interrupt_after: payload?.interruptAfter,
@@ -1037,7 +1046,8 @@ export class RunsClient extends BaseClient {
? { signal: options }
: options;
const response = await this.asyncCaller.fetch(
const response = await this.asyncCaller.call(
_getFetchImplementation(),
...this.prepareFetchOptions(`/threads/${threadId}/runs/${runId}/stream`, {
method: "GET",
timeoutMs: null,
+1
View File
@@ -17,5 +17,6 @@ export type {
Checkpoint,
Interrupt,
} from "./schema.js";
export { overrideFetchImplementation } from "./singletons/fetch.js";
export type { OnConflictBehavior, Command } from "./types.js";
+19
View File
@@ -278,3 +278,22 @@ export interface SearchItem extends Item {
export interface SearchItemsResponse {
items: SearchItem[];
}
export interface CronCreateResponse {
cron_id: string;
assistant_id: string;
thread_id: string | undefined;
user_id: string;
payload: Record<string, unknown>;
schedule: string;
next_run_date: string;
end_time: string | undefined;
created_at: string;
updated_at: string;
metadata: Metadata;
}
export interface CronCreateForThreadResponse
extends Omit<CronCreateResponse, "thread_id"> {
thread_id: string;
}
+29
View File
@@ -0,0 +1,29 @@
// Wrap the default fetch call due to issues with illegal invocations
// in some environments:
// https://stackoverflow.com/questions/69876859/why-does-bind-fix-failed-to-execute-fetch-on-window-illegal-invocation-err
// @ts-expect-error Broad typing to support a range of fetch implementations
const DEFAULT_FETCH_IMPLEMENTATION = (...args: any[]) => fetch(...args);
const LANGSMITH_FETCH_IMPLEMENTATION_KEY = Symbol.for(
"lg:fetch_implementation",
);
/**
* Overrides the fetch implementation used for LangSmith calls.
* You should use this if you need to use an implementation of fetch
* other than the default global (e.g. for dealing with proxies).
* @param fetch The new fetch function to use.
*/
export const overrideFetchImplementation = (fetch: (...args: any[]) => any) => {
(globalThis as any)[LANGSMITH_FETCH_IMPLEMENTATION_KEY] = fetch;
};
/**
* @internal
*/
export const _getFetchImplementation: () => (...args: any[]) => any = () => {
return (
(globalThis as any)[LANGSMITH_FETCH_IMPLEMENTATION_KEY] ??
DEFAULT_FETCH_IMPLEMENTATION
);
};
+74
View File
@@ -0,0 +1,74 @@
/* eslint-disable no-process-env */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { jest } from "@jest/globals";
import { Client } from "../client.js";
import { overrideFetchImplementation } from "../singletons/fetch.js";
describe.each([[""], ["mocked"]])("Client uses %s fetch", (description) => {
let globalFetchMock: jest.Mock;
let overriddenFetch: jest.Mock;
let expectedFetchMock: jest.Mock;
let unexpectedFetchMock: jest.Mock;
beforeEach(() => {
globalFetchMock = jest.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
batch_ingest_config: {
use_multipart_endpoint: true,
},
}),
text: () => Promise.resolve(""),
}),
);
overriddenFetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
batch_ingest_config: {
use_multipart_endpoint: true,
},
}),
text: () => Promise.resolve(""),
}),
);
expectedFetchMock =
description === "mocked" ? overriddenFetch : globalFetchMock;
unexpectedFetchMock =
description === "mocked" ? globalFetchMock : overriddenFetch;
if (description === "mocked") {
overrideFetchImplementation(overriddenFetch);
} else {
overrideFetchImplementation(globalFetchMock);
}
// Mock global fetch
(globalThis as any).fetch = globalFetchMock;
});
afterEach(() => {
jest.restoreAllMocks();
});
describe("createRuns", () => {
it("should create an example with the given input and generation", async () => {
const client = new Client({ apiKey: "test-api-key" });
const thread = await client.threads.create();
expect(expectedFetchMock).toHaveBeenCalledTimes(1);
expect(unexpectedFetchMock).not.toHaveBeenCalled();
jest.clearAllMocks(); // Clear all mocks before the next operation
// Then clear & run the function
await client.runs.create(thread.thread_id, "somegraph", {
input: { foo: "bar" },
});
expect(expectedFetchMock).toHaveBeenCalledTimes(1);
expect(unexpectedFetchMock).not.toHaveBeenCalled();
});
});
});
+22 -8
View File
@@ -1,5 +1,15 @@
import { Checkpoint, Config, Metadata } from "./schema.js";
/**
* Stream modes
* - "values": Stream only the state values.
* - "messages": Stream complete messages.
* - "messages-tuple": Stream (message chunk, metadata) tuples.
* - "updates": Stream updates to the state.
* - "events": Stream events occurring during execution.
* - "debug": Stream detailed debug information.
* - "custom": Stream custom events.
*/
export type StreamMode =
| "values"
| "messages"
@@ -140,13 +150,7 @@ interface RunsInvokePayload {
export interface RunsStreamPayload extends RunsInvokePayload {
/**
* One of `"values"`, `"messages"`, `"updates"` or `"events"`.
* - `"values"`: Stream the thread state any time it changes.
* - `"messages"`: Stream chat messages from thread state and calls to chat models,
* token-by-token where possible.
* - `"updates"`: Stream the state updates returned by each node.
* - `"events"`: Stream all events produced by the run. You can also access these
* afterwards using the `client.runs.listEvents()` method.
* One of `"values"`, `"messages"`, `"messages-tuple"`, `"updates"`, `"events"`, `"debug"`, `"custom"`.
*/
streamMode?: StreamMode | Array<StreamMode>;
@@ -162,7 +166,17 @@ export interface RunsStreamPayload extends RunsInvokePayload {
feedbackKeys?: string[];
}
export interface RunsCreatePayload extends RunsInvokePayload {}
export interface RunsCreatePayload extends RunsInvokePayload {
/**
* One of `"values"`, `"messages"`, `"messages-tuple"`, `"updates"`, `"events"`, `"debug"`, `"custom"`.
*/
streamMode?: StreamMode | Array<StreamMode>;
/**
* Stream output from subgraphs. By default, streams only the top graph.
*/
streamSubgraphs?: boolean;
}
export interface CronsCreatePayload extends RunsCreatePayload {
/**
+1880 -7
View File
File diff suppressed because it is too large Load Diff
@@ -69,6 +69,10 @@ class Auth:
async def authorize_thread_create(params: Auth.on.threads.create.value):
# Allow the allowed user to create a thread
assert params.get("metadata", {}).get("owner") == "allowed_user"
@auth.on.store
async def authorize_store(ctx: Auth.types.AuthContext, value: Auth.types.on):
assert ctx.user.identity in value["namespace"], "Not authorized"
```
???+ note "Request Processing Flow"
@@ -157,6 +161,15 @@ class Auth:
# Implement rate limiting for write operations
return await check_rate_limit(ctx.user.identity)
```
Auth for the `store` resource is a bit different since its structure is developer defined.
You typically want to enforce user creds in the namespace. Y
```python
@auth.on.store
async def check_store_access(ctx: AuthContext, value: Auth.types.on) -> bool:
# Assuming you structure your store like (store.aput((user_id, application_context), key, value))
assert value["namespace"][0] == ctx.user.identity
```
"""
# These are accessed by the API. Changes to their names or types is
# will be considered a breaking change.
@@ -461,6 +474,73 @@ class _CronsOn(
Search = types.CronsSearch
class _StoreOn:
def __init__(self, auth: Auth) -> None:
self._auth = auth
@typing.overload
def __call__(
self,
*,
actions: typing.Optional[
typing.Union[
typing.Literal["put", "get", "search", "list_namespaces", "delete"],
Sequence[
typing.Literal["put", "get", "search", "list_namespaces", "delete"]
],
]
] = None,
) -> Callable[[AHO], AHO]: ...
@typing.overload
def __call__(self, fn: AHO) -> AHO: ...
def __call__(
self,
fn: typing.Optional[AHO] = None,
*,
actions: typing.Optional[
typing.Union[
typing.Literal["put", "get", "search", "list_namespaces", "delete"],
Sequence[
typing.Literal["put", "get", "search", "list_namespaces", "delete"]
],
]
] = None,
) -> typing.Union[AHO, Callable[[AHO], AHO]]:
"""Register a handler for specific resources and actions.
Can be used as a decorator or with explicit resource/action parameters:
@auth.on.store
async def handler(): ... # Handle all store ops
@auth.on.store(actions=("put", "get", "search", "delete"))
async def handler(): ... # Handle specific store ops
@auth.on.store.put
async def handler(): ... # Handle store.put ops
"""
if fn is not None:
# Used as a plain decorator
_register_handler(self._auth, "store", None, fn)
return fn
# Used with parameters, return a decorator
def decorator(
handler: AHO,
) -> AHO:
if isinstance(actions, str):
action_list = [actions]
else:
action_list = list(actions) if actions is not None else ["*"]
for action in action_list:
_register_handler(self._auth, "store", action, handler)
return handler
return decorator
AHO = typing.TypeVar("AHO", bound=_ActionHandler[dict[str, typing.Any]])
@@ -524,6 +604,7 @@ class _On:
"threads",
"runs",
"crons",
"store",
"value",
)
@@ -532,6 +613,7 @@ class _On:
self.assistants = _AssistantsOn(auth, "assistants")
self.threads = _ThreadsOn(auth, "threads")
self.crons = _CronsOn(auth, "crons")
self.store = _StoreOn(auth)
self.value = dict[str, typing.Any]
@typing.overload
+143 -5
View File
@@ -5,7 +5,7 @@ request handling in LangGraph. It includes user protocols, authentication contex
and typed dictionaries for various API operations.
Note:
All typing.TypedDict classes use total=False to make all fields optional by default.
All typing.TypedDict classes use total=False to make all fields typing.Optional by default.
"""
import functools
@@ -157,7 +157,7 @@ class MinimalUserDict(typing.TypedDict, total=False):
identity: typing_extensions.Required[str]
"""The required unique identifier for the user."""
display_name: str
"""The optional display name for the user."""
"""The typing.Optional display name for the user."""
is_authenticated: bool
"""Whether the user is authenticated. Defaults to True."""
permissions: Sequence[str]
@@ -358,11 +358,34 @@ class AuthContext(BaseAuthContext):
allowing for fine-grained access control decisions.
"""
resource: typing.Literal["runs", "threads", "crons", "assistants"]
resource: typing.Literal["runs", "threads", "crons", "assistants", "store"]
"""The resource being accessed."""
action: typing.Literal["create", "read", "update", "delete", "search", "create_run"]
"""The action being performed on the resource."""
action: typing.Literal[
"create",
"read",
"update",
"delete",
"search",
"create_run",
"put",
"get",
"list_namespaces",
]
"""The action being performed on the resource.
Most resources support the following actions:
- create: Create a new resource
- read: Read information about a resource
- update: Update an existing resource
- delete: Delete a resource
- search: Search for resources
The store supports the following actions:
- put: Add or update a document in the store
- get: Get a document from the store
- list_namespaces: List the namespaces in the store
"""
class ThreadsCreate(typing.TypedDict, total=False):
@@ -759,6 +782,84 @@ class CronsSearch(typing.TypedDict, total=False):
"""Offset for pagination."""
class StoreGet(typing.TypedDict):
"""Operation to retrieve a specific item by its namespace and key."""
namespace: tuple[str, ...]
"""Hierarchical path that uniquely identifies the item's location."""
key: str
"""Unique identifier for the item within its specific namespace."""
class StoreSearch(typing.TypedDict):
"""Operation to search for items within a specified namespace hierarchy."""
namespace: tuple[str, ...]
"""Prefix filter for defining the search scope."""
filter: typing.Optional[dict[str, typing.Any]]
"""Key-value pairs for filtering results based on exact matches or comparison operators."""
limit: int
"""Maximum number of items to return in the search results."""
offset: int
"""Number of matching items to skip for pagination."""
query: typing.Optional[str]
"""Naturalj language search query for semantic search capabilities."""
class StoreListNamespaces(typing.TypedDict):
"""Operation to list and filter namespaces in the store."""
namespace: typing.Optional[tuple[str, ...]]
"""Prefix filter namespaces."""
suffix: typing.Optional[tuple[str, ...]]
"""Optional conditions for filtering namespaces."""
max_depth: typing.Optional[int]
"""Maximum depth of namespace hierarchy to return.
Note:
Namespaces deeper than this level will be truncated.
"""
limit: int
"""Maximum number of namespaces to return."""
offset: int
"""Number of namespaces to skip for pagination."""
class StorePut(typing.TypedDict):
"""Operation to store, update, or delete an item in the store."""
namespace: tuple[str, ...]
"""Hierarchical path that identifies the location of the item."""
key: str
"""Unique identifier for the item within its namespace."""
value: typing.Optional[dict[str, typing.Any]]
"""The data to store, or None to mark the item for deletion."""
index: typing.Optional[typing.Union[typing.Literal[False], list[str]]]
"""Optional index configuration for full-text search."""
class StoreDelete(typing.TypedDict):
"""Operation to delete an item from the store."""
namespace: tuple[str, ...]
"""Hierarchical path that uniquely identifies the item's location."""
key: str
"""Unique identifier for the item within its specific namespace."""
class on:
"""Namespace for type definitions of different API operations.
@@ -894,6 +995,38 @@ class on:
value = CronsSearch
class store:
"""Types for store-related operations."""
value = typing.Union[
StoreGet, StoreSearch, StoreListNamespaces, StorePut, StoreDelete
]
class put:
"""Type for store put parameters."""
value = StorePut
class get:
"""Type for store get parameters."""
value = StoreGet
class search:
"""Type for store search parameters."""
value = StoreSearch
class delete:
"""Type for store delete parameters."""
value = StoreDelete
class list_namespaces:
"""Type for store list namespaces parameters."""
value = StoreListNamespaces
__all__ = [
"on",
@@ -909,4 +1042,9 @@ __all__ = [
"AssistantsUpdate",
"AssistantsDelete",
"AssistantsSearch",
"StoreGet",
"StoreSearch",
"StoreListNamespaces",
"StorePut",
"StoreDelete",
]
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-sdk"
version = "0.1.49"
version = "0.1.51"
description = "SDK for interacting with LangGraph API"
authors = []
license = "MIT"
Generated
+54 -41
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "aiohappyeyeballs"
@@ -2862,21 +2862,21 @@ adal = ["adal (>=1.0.2)"]
[[package]]
name = "langchain"
version = "0.3.9"
version = "0.3.14"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "langchain-0.3.9-py3-none-any.whl", hash = "sha256:ade5a1fee2f94f2e976a6c387f97d62cc7f0b9f26cfe0132a41d2bda761e1045"},
{file = "langchain-0.3.9.tar.gz", hash = "sha256:4950c4ad627d0aa95ce6bda7de453e22059b7e7836b562a8f781fb0b05d7294c"},
{file = "langchain-0.3.14-py3-none-any.whl", hash = "sha256:5df9031702f7fe6c956e84256b4639a46d5d03a75be1ca4c1bc9479b358061a2"},
{file = "langchain-0.3.14.tar.gz", hash = "sha256:4a5ae817b5832fa0e1fcadc5353fbf74bebd2f8e550294d4dc039f651ddcd3d1"},
]
[package.dependencies]
aiohttp = ">=3.8.3,<4.0.0"
async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
langchain-core = ">=0.3.21,<0.4.0"
langchain-text-splitters = ">=0.3.0,<0.4.0"
langsmith = ">=0.1.17,<0.2.0"
langchain-core = ">=0.3.29,<0.4.0"
langchain-text-splitters = ">=0.3.3,<0.4.0"
langsmith = ">=0.1.17,<0.3"
numpy = [
{version = ">=1.22.4,<2", markers = "python_version < \"3.12\""},
{version = ">=1.26.2,<3", markers = "python_version >= \"3.12\""},
@@ -2906,45 +2906,46 @@ pydantic = ">=2.7.4,<3.0.0"
[[package]]
name = "langchain-community"
version = "0.3.1"
version = "0.3.14"
description = "Community contributed LangChain integrations."
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "langchain_community-0.3.1-py3-none-any.whl", hash = "sha256:627eb26c16417764762ac47dd0d3005109f750f40242a88bb8f2958b798bcf90"},
{file = "langchain_community-0.3.1.tar.gz", hash = "sha256:c964a70628f266a61647e58f2f0434db633d4287a729f100a81dd8b0654aec93"},
{file = "langchain_community-0.3.14-py3-none-any.whl", hash = "sha256:cc02a0abad0551edef3e565dff643386a5b2ee45b933b6d883d4a935b9649f3c"},
{file = "langchain_community-0.3.14.tar.gz", hash = "sha256:d8ba0fe2dbb5795bff707684b712baa5ee379227194610af415ccdfdefda0479"},
]
[package.dependencies]
aiohttp = ">=3.8.3,<4.0.0"
dataclasses-json = ">=0.5.7,<0.7"
langchain = ">=0.3.1,<0.4.0"
langchain-core = ">=0.3.6,<0.4.0"
langsmith = ">=0.1.125,<0.2.0"
httpx-sse = ">=0.4.0,<0.5.0"
langchain = ">=0.3.14,<0.4.0"
langchain-core = ">=0.3.29,<0.4.0"
langsmith = ">=0.1.125,<0.3"
numpy = [
{version = ">=1,<2", markers = "python_version < \"3.12\""},
{version = ">=1.26.0,<2.0.0", markers = "python_version >= \"3.12\""},
{version = ">=1.22.4,<2", markers = "python_version < \"3.12\""},
{version = ">=1.26.2,<3", markers = "python_version >= \"3.12\""},
]
pydantic-settings = ">=2.4.0,<3.0.0"
PyYAML = ">=5.3"
requests = ">=2,<3"
SQLAlchemy = ">=1.4,<3"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10"
[[package]]
name = "langchain-core"
version = "0.3.23"
version = "0.3.29"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "langchain_core-0.3.23-py3-none-any.whl", hash = "sha256:550c0b996990830fa6515a71a1192a8a0343367999afc36d4ede14222941e420"},
{file = "langchain_core-0.3.23.tar.gz", hash = "sha256:f9e175e3b82063cc3b160c2ca2b155832e1c6f915312e1204828f97d4aabf6e1"},
{file = "langchain_core-0.3.29-py3-none-any.whl", hash = "sha256:817db1474871611a81105594a3e4d11704949661008e455a10e38ca9ff601a1a"},
{file = "langchain_core-0.3.29.tar.gz", hash = "sha256:773d6aeeb612e7ce3d996c0be403433d8c6a91e77bbb7a7461c13e15cfbe5b06"},
]
[package.dependencies]
jsonpatch = ">=1.33,<2.0"
langsmith = ">=0.1.125,<0.2.0"
langsmith = ">=0.1.125,<0.3"
packaging = ">=23.2,<25"
pydantic = [
{version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""},
@@ -3021,21 +3022,21 @@ tiktoken = ">=0.7,<1"
[[package]]
name = "langchain-text-splitters"
version = "0.3.0"
version = "0.3.5"
description = "LangChain text splitting utilities"
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "langchain_text_splitters-0.3.0-py3-none-any.whl", hash = "sha256:e84243e45eaff16e5b776cd9c81b6d07c55c010ebcb1965deb3d1792b7358e83"},
{file = "langchain_text_splitters-0.3.0.tar.gz", hash = "sha256:f9fe0b4d244db1d6de211e7343d4abc4aa90295aa22e1f0c89e51f33c55cd7ce"},
{file = "langchain_text_splitters-0.3.5-py3-none-any.whl", hash = "sha256:8c9b059827438c5fa8f327b4df857e307828a5ec815163c9b5c9569a3e82c8ee"},
{file = "langchain_text_splitters-0.3.5.tar.gz", hash = "sha256:11cb7ca3694e5bdd342bc16d3875b7f7381651d4a53cbb91d34f22412ae16443"},
]
[package.dependencies]
langchain-core = ">=0.3.0,<0.4.0"
langchain-core = ">=0.3.29,<0.4.0"
[[package]]
name = "langgraph"
version = "0.2.59"
version = "0.2.61"
description = "Building stateful, multi-actor applications with LLMs"
optional = false
python-versions = ">=3.9.0,<4.0"
@@ -3053,7 +3054,7 @@ url = "libs/langgraph"
[[package]]
name = "langgraph-checkpoint"
version = "2.0.8"
version = "2.0.9"
description = "Library with base interfaces for LangGraph checkpoint savers."
optional = false
python-versions = "^3.9.0,<4.0"
@@ -3087,7 +3088,7 @@ pymongo = ">=4.9.0,<4.10.0"
[[package]]
name = "langgraph-checkpoint-postgres"
version = "2.0.8"
version = "2.0.9"
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
optional = false
python-versions = "^3.9.0,<4.0"
@@ -3123,7 +3124,7 @@ url = "libs/checkpoint-sqlite"
[[package]]
name = "langgraph-sdk"
version = "0.1.43"
version = "0.1.49"
description = "SDK for interacting with LangGraph API"
optional = false
python-versions = "^3.9.0,<4.0"
@@ -3140,23 +3141,28 @@ url = "libs/sdk-py"
[[package]]
name = "langsmith"
version = "0.1.129"
version = "0.2.10"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false
python-versions = "<4.0,>=3.8.1"
python-versions = "<4.0,>=3.9"
files = [
{file = "langsmith-0.1.129-py3-none-any.whl", hash = "sha256:31393fbbb17d6be5b99b9b22d530450094fab23c6c37281a6a6efb2143d05347"},
{file = "langsmith-0.1.129.tar.gz", hash = "sha256:6c3ba66471bef41b9f87da247cc0b493268b3f54656f73648a256a205261b6a0"},
{file = "langsmith-0.2.10-py3-none-any.whl", hash = "sha256:b02f2f174189ff72e54c88b1aa63343defd6f0f676c396a690c63a4b6495dcc2"},
{file = "langsmith-0.2.10.tar.gz", hash = "sha256:153c7b3ccbd823528ff5bec84801e7e50a164e388919fc583252df5b27dd7830"},
]
[package.dependencies]
httpx = ">=0.23.0,<1"
orjson = ">=3.9.14,<4.0.0"
orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""}
pydantic = [
{version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
]
requests = ">=2,<3"
requests-toolbelt = ">=1.0.0,<2.0.0"
[package.extras]
compression = ["zstandard (>=0.23.0,<0.24.0)"]
langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"]
[[package]]
name = "loguru"
@@ -5114,7 +5120,6 @@ description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs
optional = false
python-versions = ">=3.8"
files = [
{file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"},
{file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"},
]
@@ -5125,7 +5130,6 @@ description = "A collection of ASN.1-based protocols modules"
optional = false
python-versions = ">=3.8"
files = [
{file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"},
{file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"},
]
@@ -5967,6 +5971,20 @@ requests = ">=2.0.0"
[package.extras]
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
[[package]]
name = "requests-toolbelt"
version = "1.0.0"
description = "A utility belt for advanced users of python-requests"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
{file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"},
{file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"},
]
[package.dependencies]
requests = ">=2.0.1,<3.0.0"
[[package]]
name = "rfc3339-validator"
version = "0.1.4"
@@ -6185,11 +6203,6 @@ files = [
{file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"},
{file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"},
{file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"},
{file = "scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5"},
{file = "scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908"},
{file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3"},
{file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12"},
{file = "scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f"},
{file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"},
{file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"},
{file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"},
@@ -7492,4 +7505,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "367f5fb480a8fa5d8ab1c0964a1e9450dbb28e6998097e7536966e7a5fe30c90"
content-hash = "981f40de9c31530b17537a089651f9e51901b945fbc01b43ac33a466c8a7d9eb"
+1 -1
View File
@@ -42,7 +42,7 @@ langchain-fireworks = "^0.2.0"
langchain-community = "^0.3.0"
langchain-experimental = "^0.3.2"
langgraph-checkpoint-mongodb = "^0.1.0"
langsmith = "^0.1.129"
langsmith = "^0.2.0"
chromadb = "^0.5.5"
gpt4all = "^2.8.2"
scikit-learn = "^1.5.2"