mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-10-09 09:55:13 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a302cf919 |
@@ -10,6 +10,6 @@ contact_links:
|
||||
- name: Show and tell
|
||||
about: Show what you built with LangChain
|
||||
url: https://github.com/langchain-ai/langgraph/discussions/categories/show-and-tell
|
||||
- name: LangChain Forum
|
||||
url: https://forum.langchain.com/
|
||||
about: General community discussions and support
|
||||
- name: Slack
|
||||
url: https://www.langchain.com/join-community
|
||||
about: General community discussions
|
||||
|
||||
+5
-12
@@ -1,18 +1,11 @@
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
# and
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directories:
|
||||
- "libs/checkpoint"
|
||||
- "libs/checkpoint-postgres"
|
||||
- "libs/checkpoint-sqlite"
|
||||
- "libs/cli"
|
||||
- "libs/langgraph"
|
||||
- "libs/prebuilt"
|
||||
- "libs/sdk-py"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
@@ -3,9 +3,6 @@ name: CLI integration test
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -8,9 +8,6 @@ on:
|
||||
type: string
|
||||
description: "From which folder this pipeline executes"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
# This env var allows us to get inline annotations when ruff has complaints.
|
||||
RUFF_OUTPUT_FORMAT: github
|
||||
|
||||
@@ -8,9 +8,6 @@ on:
|
||||
type: string
|
||||
description: "From which folder this pipeline executes"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -3,9 +3,6 @@ name: test
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -7,9 +7,6 @@ on:
|
||||
paths:
|
||||
- "libs/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -5,9 +5,6 @@ on:
|
||||
paths:
|
||||
- "libs/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -6,9 +6,6 @@ on:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# If another push to the same PR or branch happens while this workflow is still running,
|
||||
# cancel the earlier run in favor of the next run.
|
||||
#
|
||||
@@ -25,7 +22,6 @@ jobs:
|
||||
outputs:
|
||||
python: ${{ steps.filter.outputs.python }}
|
||||
sdk-js: ${{ steps.filter.outputs.sdk-js }}
|
||||
deps: ${{ steps.filter.outputs.deps }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
@@ -42,9 +38,6 @@ jobs:
|
||||
- 'libs/prebuilt/**'
|
||||
sdk-js:
|
||||
- 'libs/sdk-js/**'
|
||||
deps:
|
||||
- '**/pyproject.toml'
|
||||
- '**/uv.lock'
|
||||
|
||||
lint:
|
||||
needs: changes
|
||||
@@ -62,7 +55,7 @@ jobs:
|
||||
|
||||
"libs/prebuilt",
|
||||
]
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
uses: ./.github/workflows/_lint.yml
|
||||
with:
|
||||
working-directory: ${{ matrix.working-directory }}
|
||||
@@ -81,7 +74,7 @@ jobs:
|
||||
"libs/checkpoint-postgres",
|
||||
"libs/prebuilt",
|
||||
]
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
uses: ./.github/workflows/_test.yml
|
||||
with:
|
||||
working-directory: ${{ matrix.working-directory }}
|
||||
@@ -90,7 +83,7 @@ jobs:
|
||||
# NOTE: we're testing langgraph separately because it requires a different matrix
|
||||
test-langgraph:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
name: "cd libs/langgraph"
|
||||
uses: ./.github/workflows/_test_langgraph.yml
|
||||
secrets: inherit
|
||||
@@ -147,7 +140,7 @@ jobs:
|
||||
|
||||
integration-test:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
name: CLI integration test
|
||||
uses: ./.github/workflows/_integration_test.yml
|
||||
secrets: inherit
|
||||
|
||||
@@ -11,9 +11,6 @@ on:
|
||||
- cron: "0 5 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
markdown-link-check:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -8,9 +8,6 @@ on:
|
||||
type: string
|
||||
default: "libs/langgraph"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.11"
|
||||
|
||||
|
||||
@@ -3,9 +3,6 @@ name: JS Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
# Disallow publishing from branches that aren't `main`.
|
||||
|
||||
@@ -11,9 +11,6 @@ on:
|
||||
schedule:
|
||||
- cron: "0 13 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
name: UV Lock Upgrade
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# run at midnight every Sunday
|
||||
- cron: '0 0 * * 0'
|
||||
# allow manual triggering
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
upgrade-dependencies:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
# use minimum supported Python version
|
||||
python-version: "3.9"
|
||||
enable-cache: true
|
||||
cache-suffix: "uv-lock-upgrade"
|
||||
|
||||
- name: Run uv lock --upgrade in all Python packages
|
||||
run: make lock-upgrade
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "chore: upgrade dependencies with `uv lock --upgrade`"
|
||||
title: "chore: upgrade dependencies with `uv lock --upgrade`"
|
||||
body: |
|
||||
This PR updates the dependencies in all Python packages using `uv lock --upgrade`.
|
||||
|
||||
This is an automated PR created by the UV Lock Upgrade workflow.
|
||||
branch: deps/uv-lock-upgrade
|
||||
delete-branch: true
|
||||
labels: |
|
||||
dependencies
|
||||
@@ -47,16 +47,6 @@ lock:
|
||||
fi; \
|
||||
done
|
||||
|
||||
# Lock all projects and upgrade dependencies
|
||||
.PHONY: lock-upgrade
|
||||
lock-upgrade:
|
||||
@for dir in $(LIBS_DIRS); do \
|
||||
if [ -f $$dir/Makefile ]; then \
|
||||
echo "Running lock-upgrade in $$dir"; \
|
||||
(cd $$dir && uv lock --upgrade); \
|
||||
fi; \
|
||||
done
|
||||
|
||||
# Test all projects
|
||||
.PHONY: test
|
||||
test:
|
||||
|
||||
@@ -63,7 +63,7 @@ LangGraph provides low-level supporting infrastructure for *any* long-running, s
|
||||
While LangGraph can be used standalone, it also integrates seamlessly with any LangChain product, giving developers a full suite of tools for building agents. To improve your LLM application development, pair LangGraph with:
|
||||
|
||||
- [LangSmith](http://www.langchain.com/langsmith) — Helpful for agent evals and observability. Debug poor-performing LLM app runs, evaluate agent trajectories, gain visibility in production, and improve performance over time.
|
||||
- [LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) — Deploy and scale agents effortlessly with a purpose-built deployment platform for long running, stateful workflows. Discover, reuse, configure, and share agents across teams — and iterate quickly with visual prototyping in [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/).
|
||||
- [LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/#langgraph-platform) — Deploy and scale agents effortlessly with a purpose-built deployment platform for long running, stateful workflows. Discover, reuse, configure, and share agents across teams — and iterate quickly with visual prototyping in [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/).
|
||||
- [LangChain](https://python.langchain.com/docs/introduction/) – Provides integrations and composable components to streamline LLM application development.
|
||||
|
||||
> [!NOTE]
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# Data Storage and Privacy
|
||||
|
||||
This document describes how data is processed in the LangGraph CLI and the LangGraph Server for both the in-memory server (`langgraph dev`) and the local Docker server (`langgraph up`). It also describes what data is tracked when interacting with the hosted LangGraph Studio frontend.
|
||||
|
||||
## CLI
|
||||
|
||||
LangGraph **CLI** is the command-line interface for building and running LangGraph applications; see the [CLI guide](../../concepts/langgraph_cli.md) to learn more.
|
||||
|
||||
By default, calls to most CLI commands log a single analytics event upon invocation. This helps us better prioritize improvements to the CLI experience. Each telemetry event contains the calling process's OS, OS version, Python version, the CLI version, the command name (`dev`, `up`, `run`, etc.), and booleans representing whether a flag was passed to the command. You can see the full analytics logic [here](https://github.com/langchain-ai/langgraph/blob/main/libs/cli/langgraph_cli/analytics.py).
|
||||
|
||||
You can disable all CLI telemetry by setting `LANGGRAPH_CLI_NO_ANALYTICS=1`.
|
||||
|
||||
## LangGraph Server (in-memory & docker)
|
||||
|
||||
The [LangGraph Server](../../concepts/langgraph_server.md) provides a durable execution runtime that relies on persisting checkpoints of your application state, long-term memories, thread metadata, assistants, and similar resources to the local file system or a database. Unless you have deliberately customized the storage location, this information is either written to local disk (for `langgraph dev`) or a PostgreSQL database (for `langgraph up` and in all deployments).
|
||||
|
||||
### LangSmith Tracing
|
||||
|
||||
When running the LangGraph server (either in-memory or in Docker), LangSmith tracing may be enabled to facilitate faster debugging and offer observability of graph state and LLM prompts in production. You can always disable tracing by setting `LANGSMITH_TRACING=false` in your server's runtime environment.
|
||||
|
||||
### In-memory development server (`langgraph dev`)
|
||||
|
||||
`langgraph dev` runs an [in-memory development server](../../tutorials/langgraph-platform/local-server.md) as a single Python process, designed for quick development and testing. It saves all checkpointing and memory data to disk within a `.langgraph_api` directory in the current working directory. Apart from the telemetry data described in the [CLI](#cli) section, no data leaves the machine unless you have enabled tracing or your graph code explicitly contacts an external service.
|
||||
|
||||
### Standalone Container (`langgraph up`)
|
||||
|
||||
`langgraph up` builds your local package into a Docker image and runs the server as a [standalone container](../../concepts/deployment_options.md#standalone-container) consisting of three containers: the API server, a PostgreSQL container, and a Redis container. All persistent data (checkpoints, assistants, etc.) are stored in the PostgreSQL database. Redis is used as a pubsub connection for real-time streaming of events. You can encrypt all checkpoints before saving to the database by setting a valid `LANGGRAPH_AES_KEY` environment variable. You can also specify [TTLs](../../how-tos/ttl/configure_ttl.md) for checkpoints and cross-thread memories in `langgraph.json` to control how long data is stored. All persisted threads, memories, and other data can be deleted via the relevant API endpoints.
|
||||
|
||||
Additional API calls are made to confirm that the server has a valid license and to track the number of executed runs and tasks. Periodically, the API server validates the provided license key (or API key).
|
||||
|
||||
If you've disabled [tracing](#langsmith-tracing), no user data is persisted externally unless your graph code explicitly contacts an external service.
|
||||
|
||||
## Studio
|
||||
|
||||
[LangGraph Studio](../../concepts/langgraph_studio.md) is a graphical interface for interacting with your LangGraph server. It does not persist any private data (the data you send to your server is not sent to LangSmith). Though the studio interface is served at [smith.langchain.com](https://smith.langchain.com), it is run in your browser and connects directly to your local LangGraph server so that no data needs to be sent to LangSmith.
|
||||
|
||||
If you are logged in, LangSmith does collect some usage analytics to help improve studio's user experience. This includes:
|
||||
|
||||
- Page visits and navigation patterns
|
||||
- User actions (button clicks)
|
||||
- Browser type and version
|
||||
- Screen resolution and viewport size
|
||||
|
||||
Importantly, no application data or code (or other sensitive configuration details) are collected. All of that is stored in the persistence layer of your LangGraph server. When using Studio anonymously, no account creation is required and usage analytics are not collected.
|
||||
|
||||
## Quick reference
|
||||
|
||||
In summary, you can opt-out of server-side telemetry by turning off CLI analytics and disabling tracing.
|
||||
|
||||
| Variable | Purpose | Default |
|
||||
| ------------------------------ | ------------------------- | -------------------------------- |
|
||||
| `LANGGRAPH_CLI_NO_ANALYTICS=1` | Disable CLI analytics | Analytics enabled |
|
||||
| `LANGSMITH_API_KEY` | Enable LangSmith tracing | Tracing disabled |
|
||||
| `LANGSMITH_TRACING=false` | Disable LangSmith tracing | Depends on environment |
|
||||
@@ -20,7 +20,7 @@ my-app/
|
||||
|-- openai_agent.py # code for your graph
|
||||
```
|
||||
|
||||
where the graph is defined in `openai_agent.py`.
|
||||
where the graph is defined in `openai_agent.py`.
|
||||
|
||||
### No rebuild
|
||||
|
||||
@@ -28,11 +28,11 @@ In the standard LangGraph API configuration, the server uses the compiled graph
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, START, MessageGraph
|
||||
from langgraph.graph import END, START, StateGraph, MessagesState
|
||||
|
||||
model = ChatOpenAI(temperature=0)
|
||||
|
||||
graph_workflow = MessageGraph()
|
||||
graph_workflow = StateGraph(MessagesState)
|
||||
|
||||
graph_workflow.add_node("agent", model)
|
||||
graph_workflow.add_edge("agent", END)
|
||||
@@ -61,7 +61,7 @@ To make your graph rebuild on each new run with custom configuration, you need t
|
||||
from typing import Annotated
|
||||
from typing_extensions import TypedDict
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, START, MessageGraph
|
||||
from langgraph.graph import END, START
|
||||
from langgraph.graph.state import StateGraph
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.prebuilt import ToolNode
|
||||
@@ -144,4 +144,4 @@ Finally, you need to specify the path to your graph-making function (`make_graph
|
||||
}
|
||||
```
|
||||
|
||||
See more info on LangGraph API configuration file [here](../reference/cli.md#configuration-file)
|
||||
See more info on LangGraph API configuration file [here](../reference/cli.md#configuration-file)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Before deploying, review the [conceptual guide for the Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployment option.
|
||||
|
||||
!!! info "Important"
|
||||
The Self-Hosted Control Plane deployment option requires an [Enterprise](../../concepts/plans.md) plan.
|
||||
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Before deploying, review the [conceptual guide for the Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) deployment option.
|
||||
|
||||
!!! info "Important"
|
||||
The Self-Hosted Data Plane deployment option requires an [Enterprise](../../concepts/plans.md) plan.
|
||||
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# How to integrate LangGraph into your React application
|
||||
How to integrate LangGraph into your React application# How to integrate LangGraph into your React application
|
||||
|
||||
!!! info "Prerequisites"
|
||||
|
||||
@@ -503,74 +503,6 @@ const handleSubmit = (text: string) => {
|
||||
};
|
||||
```
|
||||
|
||||
### Cached Thread Display
|
||||
|
||||
Use the `initialValues` option to display cached thread data immediately while the history is being loaded from the server. This improves user experience by showing cached data instantly when navigating to existing threads.
|
||||
|
||||
```tsx
|
||||
import { useStream } from "@langchain/langgraph-sdk/react";
|
||||
|
||||
const CachedThreadExample = ({ threadId, cachedThreadData }) => {
|
||||
const stream = useStream({
|
||||
apiUrl: "http://localhost:2024",
|
||||
assistantId: "agent",
|
||||
threadId,
|
||||
// Show cached data immediately while history loads
|
||||
initialValues: cachedThreadData?.values,
|
||||
messagesKey: "messages",
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
{stream.messages.map((message) => (
|
||||
<div key={message.id}>{message.content as string}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Optimistic Thread Creation
|
||||
|
||||
Use the `threadId` option in `submit` function to enable optimistic UI patterns where you need to know the thread ID before the thread is actually created.
|
||||
|
||||
```tsx
|
||||
import { useState } from "react";
|
||||
import { useStream } from "@langchain/langgraph-sdk/react";
|
||||
|
||||
const OptimisticThreadExample = () => {
|
||||
const [threadId, setThreadId] = useState<string | null>(null);
|
||||
const [optimisticThreadId] = useState(() => crypto.randomUUID());
|
||||
|
||||
const stream = useStream({
|
||||
apiUrl: "http://localhost:2024",
|
||||
assistantId: "agent",
|
||||
threadId,
|
||||
onThreadId: setThreadId, // (3) Updated after thread has been created.
|
||||
messagesKey: "messages",
|
||||
});
|
||||
|
||||
const handleSubmit = (text: string) => {
|
||||
// (1) Perform a soft navigation to /threads/${optimisticThreadId}
|
||||
// without waiting for thread creation.
|
||||
window.history.pushState({}, "", `/threads/${optimisticThreadId}`);
|
||||
|
||||
// (2) Submit message to create thread with the predetermined ID.
|
||||
stream.submit(
|
||||
{ messages: [{ type: "human", content: text }] },
|
||||
{ threadId: optimisticThreadId }
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Thread ID: {threadId ?? optimisticThreadId}</p>
|
||||
{/* Rest of component */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### TypeScript
|
||||
|
||||
The `useStream()` hook is friendly for apps written in TypeScript and you can specify types for the state to get better type safety and IDE support.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -395,7 +395,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
|
||||
|
||||
=== "Python"
|
||||
|
||||
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform. Requires a license key for production use.
|
||||
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform closed beta. Requires a license key for production use.
|
||||
|
||||
**Usage**
|
||||
|
||||
@@ -422,7 +422,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
|
||||
|
||||
=== "JS"
|
||||
|
||||
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform. Requires a license key for production use.
|
||||
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform closed beta. Requires a license key for production use.
|
||||
|
||||
**Usage**
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Below are examples of directory structures for Python and JavaScript application
|
||||
│ ├── utils # utilities for your graph
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── tools.py # tools for your graph
|
||||
│ │ ├── nodes.py # node functions for your graph
|
||||
│ │ ├── nodes.py # node functions for you graph
|
||||
│ │ └── state.py # state definition of your graph
|
||||
│ ├── __init__.py
|
||||
│ └── agent.py # code for constructing your graph
|
||||
@@ -64,7 +64,7 @@ Below are examples of directory structures for Python and JavaScript application
|
||||
├── src # all project code lies within here
|
||||
│ ├── utils # optional utilities for your graph
|
||||
│ │ ├── tools.ts # tools for your graph
|
||||
│ │ ├── nodes.ts # node functions for your graph
|
||||
│ │ ├── nodes.ts # node functions for you graph
|
||||
│ │ └── state.ts # state definition of your graph
|
||||
│ └── agent.ts # code for constructing your graph
|
||||
├── package.json # package dependencies
|
||||
|
||||
@@ -18,9 +18,9 @@ There are 4 main options for deploying with the [LangGraph Platform](langgraph_p
|
||||
|
||||
1. [Cloud SaaS](#cloud-saas)
|
||||
|
||||
1. [Self-Hosted Data Plane](#self-hosted-data-plane)
|
||||
1. [Self-Hosted Data Plane<sup>(Beta)</sup>](#self-hosted-data-plane)
|
||||
|
||||
1. [Self-Hosted Control Plane](#self-hosted-control-plane)
|
||||
1. [Self-Hosted Control Plane<sup>(Beta)</sup>](#self-hosted-control-plane)
|
||||
|
||||
1. [Standalone Container](#standalone-container)
|
||||
|
||||
@@ -50,7 +50,7 @@ For more information, please see:
|
||||
## Self-Hosted Data Plane
|
||||
|
||||
!!! info "Important"
|
||||
The Self-Hosted Data Plane deployment option requires an [Enterprise](../concepts/plans.md) plan.
|
||||
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](../concepts/plans.md) plan.
|
||||
|
||||
The [Self-Hosted Data Plane](./langgraph_self_hosted_data_plane.md) deployment option is a "hybrid" model for deployment where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud. This option provides a way to securely manage your data plane infrastructure, while offloading control plane management to us.
|
||||
|
||||
@@ -66,7 +66,7 @@ For more information, please see:
|
||||
## Self-Hosted Control Plane
|
||||
|
||||
!!! info "Important"
|
||||
The Self-Hosted Control Plane deployment option requires an [Enterprise](../concepts/plans.md) plan.
|
||||
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](../concepts/plans.md) plan.
|
||||
|
||||
The [Self-Hosted Control Plane](./langgraph_self_hosted_control_plane.md) deployment option is a fully self-hosted model for deployment where you manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in your cloud. This option gives you full control and responsibility of the control plane and data plane infrastructure.
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ LangGraph is a stateful, orchestration framework that brings added control to ag
|
||||
|
||||
No. LangGraph Platform is proprietary software.
|
||||
|
||||
There is a free, self-hosted version of LangGraph Platform with access to basic features. The Cloud SaaS deployment option and the Self-Hosted deployment options are paid services. [Contact our sales team](https://www.langchain.com/contact-sales) to learn more.
|
||||
There is a free, self-hosted version of LangGraph Platform with access to basic features. The Cloud SaaS deployment option is free while in beta, but will eventually be a paid service. We will always give ample notice before charging for a service and reward our early adopters with preferential pricing. The Self-Hosted deployment options are paid services. [Contact our sales team](https://www.langchain.com/contact-sales) to learn more.
|
||||
|
||||
For more information, see our [LangGraph Platform pricing page](https://www.langchain.com/pricing-langgraph-platform).
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
|
||||
|
||||
!!! info "Important"
|
||||
The Self-Hosted Control Plane deployment option requires an [Enterprise](plans.md) plan.
|
||||
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan.
|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ search:
|
||||
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
|
||||
|
||||
!!! info "Important"
|
||||
The Self-Hosted Data Plane deployment option requires an [Enterprise](plans.md) plan.
|
||||
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan.
|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ The Standalone Container deployment option is the least restrictive model for de
|
||||
|
||||
!!! warning
|
||||
|
||||
LangGraph Platform should not be deployed in serverless environments. Scale to zero may cause task loss and scaling up will not work reliably.
|
||||
LangGraph Platform should not be deployed in serverless environments.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": null,
|
||||
"id": "baf669a0-04ee-492d-80d8-8fcb658ed128",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -313,8 +313,8 @@
|
||||
"\n",
|
||||
" builder.add_edge(\"finalizer\", END)\n",
|
||||
"\n",
|
||||
" # These functions let the step be used in a MessageGraph\n",
|
||||
" # or a StateGraph with 'messages' as the key.\n",
|
||||
" # These functions let the step be used in a\n",
|
||||
" # StateGraph with 'messages' as the key.\n",
|
||||
" def encode(x: Union[Sequence[AnyMessage], PromptValue]) -> dict:\n",
|
||||
" \"\"\"Ensure the input is the correct format.\"\"\"\n",
|
||||
" if isinstance(x, PromptValue):\n",
|
||||
|
||||
+3
-5
@@ -205,17 +205,16 @@ nav:
|
||||
- cloud/how-tos/reject_concurrent.md
|
||||
- cloud/how-tos/enqueue_concurrent.md
|
||||
- Webhooks:
|
||||
- Overview: cloud/concepts/webhooks.md
|
||||
- Use webhooks: cloud/how-tos/webhooks.md
|
||||
- Overview: cloud/how-tos/webhooks.md
|
||||
- cloud/how-tos/webhooks.md
|
||||
- Cron jobs:
|
||||
- Overview: cloud/concepts/cron_jobs.md
|
||||
- Overview: cloud/how-tos/cron_jobs.md
|
||||
- cloud/how-tos/cron_jobs.md
|
||||
- Server customization:
|
||||
- how-tos/http/custom_lifespan.md
|
||||
- how-tos/http/custom_middleware.md
|
||||
- how-tos/http/custom_routes.md
|
||||
- Data management:
|
||||
- cloud/concepts/data_storage_and_privacy.md
|
||||
- Add semantic search: cloud/deployment/semantic_search.md
|
||||
- Add TTLs: how-tos/ttl/configure_ttl.md
|
||||
- Deployment:
|
||||
@@ -290,7 +289,6 @@ nav:
|
||||
- Case studies: adopters.md
|
||||
- concepts/faq.md
|
||||
- llms.txt: llms-txt-overview.md
|
||||
- LangChain Forum: https://forum.langchain.com/
|
||||
- Troubleshooting:
|
||||
- Errors:
|
||||
- troubleshooting/errors/index.md
|
||||
|
||||
@@ -12,7 +12,7 @@ readme = "README.md"
|
||||
license = "MIT"
|
||||
license-files = ['LICENSE']
|
||||
dependencies = [
|
||||
"langgraph-checkpoint>=2.0.21,<3.0.0",
|
||||
"langgraph-checkpoint>=2.0.21",
|
||||
"orjson>=3.10.1",
|
||||
"psycopg>=3.2.0",
|
||||
"psycopg-pool>=3.2.0",
|
||||
|
||||
@@ -12,7 +12,7 @@ readme = "README.md"
|
||||
license = "MIT"
|
||||
license-files = ['LICENSE']
|
||||
dependencies = [
|
||||
"langgraph-checkpoint>=2.0.21,<3.0.0",
|
||||
"langgraph-checkpoint>=2.0.21",
|
||||
"aiosqlite>=0.20",
|
||||
"sqlite-vec>=0.1.6",
|
||||
]
|
||||
|
||||
@@ -208,7 +208,7 @@ def up(
|
||||
):
|
||||
click.secho("Starting LangGraph API server...", fg="green")
|
||||
click.secho(
|
||||
"""For local dev, requires env var LANGSMITH_API_KEY with access to LangGraph Platform.
|
||||
"""For local dev, requires env var LANGSMITH_API_KEY with access to LangGraph Platform closed beta.
|
||||
For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KEY.""",
|
||||
)
|
||||
with Runner() as runner, Progress(message="Pulling...") as set:
|
||||
|
||||
@@ -18,8 +18,8 @@ dependencies = [
|
||||
|
||||
[project.optional-dependencies]
|
||||
inmem = [
|
||||
"langgraph-api>=0.2.67,<0.3.0 ; python_version >= '3.11'",
|
||||
"langgraph-runtime-inmem>=0.3.0,<0.4.0 ; python_version >= '3.11'",
|
||||
"langgraph-api>=0.2.67 ; python_version >= '3.11'",
|
||||
"langgraph-runtime-inmem>=0.3.0 ; python_version >= '3.11'",
|
||||
"python-dotenv>=0.8.0",
|
||||
]
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -530,8 +530,8 @@ dev = [
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "click", specifier = ">=8.1.7" },
|
||||
{ name = "langgraph-api", marker = "python_full_version >= '3.11' and extra == 'inmem'", specifier = ">=0.2.67,<0.3.0" },
|
||||
{ name = "langgraph-runtime-inmem", marker = "python_full_version >= '3.11' and extra == 'inmem'", specifier = ">=0.3.0,<0.4.0" },
|
||||
{ name = "langgraph-api", marker = "python_full_version >= '3.11' and extra == 'inmem'", specifier = ">=0.2.67" },
|
||||
{ name = "langgraph-runtime-inmem", marker = "python_full_version >= '3.11' and extra == 'inmem'", specifier = ">=0.3.0" },
|
||||
{ name = "langgraph-sdk", marker = "python_full_version >= '3.11'", specifier = ">=0.1.0" },
|
||||
{ name = "python-dotenv", marker = "extra == 'inmem'", specifier = ">=0.8.0" },
|
||||
]
|
||||
|
||||
@@ -63,7 +63,7 @@ LangGraph provides low-level supporting infrastructure for *any* long-running, s
|
||||
While LangGraph can be used standalone, it also integrates seamlessly with any LangChain product, giving developers a full suite of tools for building agents. To improve your LLM application development, pair LangGraph with:
|
||||
|
||||
- [LangSmith](http://www.langchain.com/langsmith) — Helpful for agent evals and observability. Debug poor-performing LLM app runs, evaluate agent trajectories, gain visibility in production, and improve performance over time.
|
||||
- [LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) — Deploy and scale agents effortlessly with a purpose-built deployment platform for long running, stateful workflows. Discover, reuse, configure, and share agents across teams — and iterate quickly with visual prototyping in [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/).
|
||||
- [LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/#langgraph-platform) — Deploy and scale agents effortlessly with a purpose-built deployment platform for long running, stateful workflows. Discover, reuse, configure, and share agents across teams — and iterate quickly with visual prototyping in [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/).
|
||||
- [LangChain](https://python.langchain.com/docs/introduction/) – Provides integrations and composable components to streamline LLM application development.
|
||||
|
||||
> [!NOTE]
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
from langgraph.constants import END, START
|
||||
from langgraph.graph.message import MessageGraph, MessagesState, add_messages
|
||||
from langgraph.graph.message import MessagesState, add_messages
|
||||
from langgraph.graph.state import StateGraph
|
||||
|
||||
__all__ = [
|
||||
"END",
|
||||
"START",
|
||||
"StateGraph",
|
||||
"MessageGraph",
|
||||
"add_messages",
|
||||
"MessagesState",
|
||||
]
|
||||
|
||||
@@ -25,7 +25,6 @@ from langchain_core.messages import (
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.constants import CONF, CONFIG_KEY_SEND
|
||||
from langgraph.graph.state import StateGraph
|
||||
|
||||
Messages = Union[list[MessageLikeRepresentation], MessageLikeRepresentation]
|
||||
|
||||
@@ -227,57 +226,6 @@ def add_messages(
|
||||
return merged
|
||||
|
||||
|
||||
class MessageGraph(StateGraph):
|
||||
"""A StateGraph where every node receives a list of messages as input and returns one or more messages as output.
|
||||
|
||||
MessageGraph is a subclass of StateGraph whose entire state is a single, append-only* list of messages.
|
||||
Each node in a MessageGraph takes a list of messages as input and returns zero or more
|
||||
messages as output. The `add_messages` function is used to merge the output messages from each node
|
||||
into the existing list of messages in the graph's state.
|
||||
|
||||
Examples:
|
||||
```pycon
|
||||
>>> from langgraph.graph.message import MessageGraph
|
||||
...
|
||||
>>> builder = MessageGraph()
|
||||
>>> builder.add_node("chatbot", lambda state: [("assistant", "Hello!")])
|
||||
>>> builder.set_entry_point("chatbot")
|
||||
>>> builder.set_finish_point("chatbot")
|
||||
>>> builder.compile().invoke([("user", "Hi there.")])
|
||||
[HumanMessage(content="Hi there.", id='...'), AIMessage(content="Hello!", id='...')]
|
||||
```
|
||||
|
||||
```pycon
|
||||
>>> from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
|
||||
>>> from langgraph.graph.message import MessageGraph
|
||||
...
|
||||
>>> builder = MessageGraph()
|
||||
>>> builder.add_node(
|
||||
... "chatbot",
|
||||
... lambda state: [
|
||||
... AIMessage(
|
||||
... content="Hello!",
|
||||
... tool_calls=[{"name": "search", "id": "123", "args": {"query": "X"}}],
|
||||
... )
|
||||
... ],
|
||||
... )
|
||||
>>> builder.add_node(
|
||||
... "search", lambda state: [ToolMessage(content="Searching...", tool_call_id="123")]
|
||||
... )
|
||||
>>> builder.set_entry_point("chatbot")
|
||||
>>> builder.add_edge("chatbot", "search")
|
||||
>>> builder.set_finish_point("search")
|
||||
>>> builder.compile().invoke([HumanMessage(content="Hi there. Can you search for X?")])
|
||||
{'messages': [HumanMessage(content="Hi there. Can you search for X?", id='b8b7d8f4-7f4d-4f4d-9c1d-f8b8d8f4d9c1'),
|
||||
AIMessage(content="Hello!", id='f4d9c1d8-8d8f-4d9c-b8b7-d8f4f4d9c1d8'),
|
||||
ToolMessage(content="Searching...", id='d8f4f4d9-c1d8-4f4d-b8b7-d8f4f4d9c1d8', tool_call_id="123")]}
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(Annotated[list[AnyMessage], add_messages]) # type: ignore[arg-type]
|
||||
|
||||
|
||||
class MessagesState(TypedDict):
|
||||
messages: Annotated[list[AnyMessage], add_messages]
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ from typing import (
|
||||
)
|
||||
|
||||
from langchain_core.runnables import Runnable, RunnableConfig
|
||||
from pydantic import BaseModel, TypeAdapter
|
||||
from typing_extensions import Self, TypeAlias, Unpack, is_typeddict
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Self, TypeAlias, Unpack
|
||||
|
||||
from langgraph._typing import UNSET, DeprecatedKwargs
|
||||
from langgraph.cache.base import BaseCache
|
||||
@@ -904,20 +904,18 @@ class CompiledStateGraph(
|
||||
self.builder = builder
|
||||
self.schema_to_mapper = schema_to_mapper
|
||||
|
||||
def get_input_jsonschema(
|
||||
self, config: RunnableConfig | None = None
|
||||
) -> dict[str, Any]:
|
||||
return _get_json_schema(
|
||||
def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]:
|
||||
return _get_schema(
|
||||
typ=self.builder.input_schema,
|
||||
schemas=self.builder.schemas,
|
||||
channels=self.builder.channels,
|
||||
name=self.get_name("Input"),
|
||||
)
|
||||
|
||||
def get_output_jsonschema(
|
||||
def get_output_schema(
|
||||
self, config: RunnableConfig | None = None
|
||||
) -> dict[str, Any]:
|
||||
return _get_json_schema(
|
||||
) -> type[BaseModel]:
|
||||
return _get_schema(
|
||||
typ=self.builder.output_schema,
|
||||
schemas=self.builder.schemas,
|
||||
channels=self.builder.channels,
|
||||
@@ -1384,23 +1382,21 @@ def _is_field_managed_value(name: str, typ: type[Any]) -> ManagedValueSpec | Non
|
||||
return None
|
||||
|
||||
|
||||
def _get_json_schema(
|
||||
def _get_schema(
|
||||
typ: type,
|
||||
schemas: dict,
|
||||
channels: dict,
|
||||
name: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> type[BaseModel]:
|
||||
if isclass(typ) and issubclass(typ, BaseModel):
|
||||
return typ.model_json_schema()
|
||||
elif is_typeddict(typ):
|
||||
return TypeAdapter(typ).json_schema()
|
||||
return typ
|
||||
else:
|
||||
keys = list(schemas[typ].keys())
|
||||
if len(keys) == 1 and keys[0] == "__root__":
|
||||
return create_model(
|
||||
name,
|
||||
root=(channels[keys[0]].UpdateType, None),
|
||||
).model_json_schema()
|
||||
)
|
||||
else:
|
||||
return create_model(
|
||||
name,
|
||||
@@ -1418,7 +1414,7 @@ def _get_json_schema(
|
||||
for k in schemas[typ]
|
||||
if k in channels and isinstance(channels[k], BaseChannel)
|
||||
},
|
||||
).model_json_schema()
|
||||
)
|
||||
|
||||
|
||||
CHANNEL_BRANCH_TO = "branch:to:{}"
|
||||
|
||||
@@ -771,7 +771,10 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
|
||||
self, *, include: Sequence[str] | None = None
|
||||
) -> dict[str, Any]:
|
||||
schema = self.config_schema(include=include)
|
||||
return schema.model_json_schema()
|
||||
if hasattr(schema, "model_json_schema"):
|
||||
return schema.model_json_schema()
|
||||
else:
|
||||
return schema.schema()
|
||||
|
||||
@property
|
||||
def InputType(self) -> Any:
|
||||
@@ -798,7 +801,10 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
|
||||
self, config: RunnableConfig | None = None
|
||||
) -> dict[str, Any]:
|
||||
schema = self.get_input_schema(config)
|
||||
return schema.model_json_schema()
|
||||
if hasattr(schema, "model_json_schema"):
|
||||
return schema.model_json_schema()
|
||||
else:
|
||||
return schema.schema()
|
||||
|
||||
@property
|
||||
def OutputType(self) -> Any:
|
||||
@@ -827,7 +833,10 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
|
||||
self, config: RunnableConfig | None = None
|
||||
) -> dict[str, Any]:
|
||||
schema = self.get_output_schema(config)
|
||||
return schema.model_json_schema()
|
||||
if hasattr(schema, "model_json_schema"):
|
||||
return schema.model_json_schema()
|
||||
else:
|
||||
return schema.schema()
|
||||
|
||||
@property
|
||||
def stream_channels_list(self) -> Sequence[str]:
|
||||
|
||||
@@ -128,8 +128,8 @@ def get_enhanced_type_hints(
|
||||
|
||||
# Pydantic models
|
||||
try:
|
||||
if hasattr(type, "model_fields") and name in type.model_fields:
|
||||
field = type.model_fields[name]
|
||||
if hasattr(type, "__fields__") and name in type.__fields__:
|
||||
field = type.__fields__[name]
|
||||
|
||||
if hasattr(field, "description") and field.description is not None:
|
||||
description = field.description
|
||||
@@ -163,7 +163,7 @@ def get_update_as_tuples(input: Any, keys: Sequence[str]) -> list[tuple[str, Any
|
||||
"""Get Pydantic state update as a list of (key, value) tuples."""
|
||||
if isinstance(input, BaseModel):
|
||||
keep = input.model_fields_set
|
||||
defaults = {k: v.default for k, v in type(input).model_fields.items()}
|
||||
defaults = {k: v.default for k, v in input.model_fields.items()}
|
||||
else:
|
||||
keep = None
|
||||
defaults = {}
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "langgraph"
|
||||
version = "0.5.1"
|
||||
version = "0.5.0"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
authors = []
|
||||
requires-python = ">=3.9"
|
||||
@@ -13,9 +13,9 @@ license = "MIT"
|
||||
license-files = ['LICENSE']
|
||||
dependencies = [
|
||||
"langchain-core>=0.1",
|
||||
"langgraph-checkpoint>=2.1.0,<3.0.0",
|
||||
"langgraph-sdk>=0.1.42,<0.2.0",
|
||||
"langgraph-prebuilt>=0.5.0,<0.6.0",
|
||||
"langgraph-checkpoint>=2.1.0",
|
||||
"langgraph-sdk>=0.1.42",
|
||||
"langgraph-prebuilt>=0.5.0",
|
||||
"xxhash>=3.5.0",
|
||||
"pydantic>=2.7.4",
|
||||
]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -89,10 +89,10 @@
|
||||
'''
|
||||
# ---
|
||||
# name: test_conditional_entrypoint_graph_state
|
||||
'{"properties": {"input": {"title": "Input", "type": "string"}, "output": {"title": "Output", "type": "string"}, "steps": {"items": {"type": "string"}, "title": "Steps", "type": "array"}}, "title": "AgentState", "type": "object"}'
|
||||
'{"properties": {"input": {"default": null, "title": "Input", "type": "string"}, "output": {"default": null, "title": "Output", "type": "string"}, "steps": {"default": null, "items": {"type": "string"}, "title": "Steps", "type": "array"}}, "title": "LangGraphInput", "type": "object"}'
|
||||
# ---
|
||||
# name: test_conditional_entrypoint_graph_state.1
|
||||
'{"properties": {"input": {"title": "Input", "type": "string"}, "output": {"title": "Output", "type": "string"}, "steps": {"items": {"type": "string"}, "title": "Steps", "type": "array"}}, "title": "AgentState", "type": "object"}'
|
||||
'{"properties": {"input": {"default": null, "title": "Input", "type": "string"}, "output": {"default": null, "title": "Output", "type": "string"}, "steps": {"default": null, "items": {"type": "string"}, "title": "Steps", "type": "array"}}, "title": "LangGraphOutput", "type": "object"}'
|
||||
# ---
|
||||
# name: test_conditional_entrypoint_graph_state.2
|
||||
'''
|
||||
@@ -178,10 +178,10 @@
|
||||
'''
|
||||
# ---
|
||||
# name: test_conditional_entrypoint_to_multiple_state_graph
|
||||
'{"properties": {"locations": {"items": {"type": "string"}, "title": "Locations", "type": "array"}, "results": {"items": {"type": "string"}, "title": "Results", "type": "array"}}, "required": ["locations", "results"], "title": "OverallState", "type": "object"}'
|
||||
'{"properties": {"locations": {"items": {"type": "string"}, "title": "Locations", "type": "array"}, "results": {"items": {"type": "string"}, "title": "Results", "type": "array"}}, "required": ["locations", "results"], "title": "LangGraphInput", "type": "object"}'
|
||||
# ---
|
||||
# name: test_conditional_entrypoint_to_multiple_state_graph.1
|
||||
'{"properties": {"locations": {"items": {"type": "string"}, "title": "Locations", "type": "array"}, "results": {"items": {"type": "string"}, "title": "Results", "type": "array"}}, "required": ["locations", "results"], "title": "OverallState", "type": "object"}'
|
||||
'{"properties": {"locations": {"items": {"type": "string"}, "title": "Locations", "type": "array"}, "results": {"items": {"type": "string"}, "title": "Results", "type": "array"}}, "required": ["locations", "results"], "title": "LangGraphOutput", "type": "object"}'
|
||||
# ---
|
||||
# name: test_conditional_entrypoint_to_multiple_state_graph.2
|
||||
'''
|
||||
@@ -917,10 +917,10 @@
|
||||
'{"$defs": {"Config": {"properties": {"tools": {"items": {"type": "string"}, "title": "Tools", "type": "array"}}, "title": "Config", "type": "object"}}, "properties": {"configurable": {"$ref": "#/$defs/Config", "default": null}}, "title": "LangGraphConfig", "type": "object"}'
|
||||
# ---
|
||||
# name: test_state_graph_w_config_inherited_state_keys.1
|
||||
'{"$defs": {"AgentAction": {"description": "Represents a request to execute an action by an agent.\\n\\nThe action consists of the name of the tool to execute and the input to pass\\nto the tool. The log is used to pass along extra information about the action.", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"anyOf": [{"type": "string"}, {"additionalProperties": true, "type": "object"}], "title": "Tool Input"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentAction", "default": "AgentAction", "title": "Type", "type": "string"}}, "required": ["tool", "tool_input", "log"], "title": "AgentAction", "type": "object"}, "AgentFinish": {"description": "Final return value of an ActionAgent.\\n\\nAgents return an AgentFinish when they have reached a stopping condition.", "properties": {"return_values": {"additionalProperties": true, "title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentFinish", "default": "AgentFinish", "title": "Type", "type": "string"}}, "required": ["return_values", "log"], "title": "AgentFinish", "type": "object"}}, "properties": {"input": {"title": "Input", "type": "string"}, "agent_outcome": {"anyOf": [{"$ref": "#/$defs/AgentAction"}, {"$ref": "#/$defs/AgentFinish"}, {"type": "null"}], "title": "Agent Outcome"}, "intermediate_steps": {"items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"$ref": "#/$defs/AgentAction"}, {"type": "string"}], "type": "array"}, "title": "Intermediate Steps", "type": "array"}}, "required": ["input", "agent_outcome"], "title": "AgentState", "type": "object"}'
|
||||
'{"$defs": {"AgentAction": {"description": "Represents a request to execute an action by an agent.\\n\\nThe action consists of the name of the tool to execute and the input to pass\\nto the tool. The log is used to pass along extra information about the action.", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"anyOf": [{"type": "string"}, {"type": "object"}], "title": "Tool Input"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentAction", "default": "AgentAction", "enum": ["AgentAction"], "title": "Type", "type": "string"}}, "required": ["tool", "tool_input", "log"], "title": "AgentAction", "type": "object"}, "AgentFinish": {"description": "Final return value of an ActionAgent.\\n\\nAgents return an AgentFinish when they have reached a stopping condition.", "properties": {"return_values": {"title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentFinish", "default": "AgentFinish", "enum": ["AgentFinish"], "title": "Type", "type": "string"}}, "required": ["return_values", "log"], "title": "AgentFinish", "type": "object"}}, "properties": {"input": {"title": "Input", "type": "string"}, "agent_outcome": {"anyOf": [{"$ref": "#/$defs/AgentAction"}, {"$ref": "#/$defs/AgentFinish"}, {"type": "null"}], "default": null, "title": "Agent Outcome"}, "intermediate_steps": {"default": null, "items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"$ref": "#/$defs/AgentAction"}, {"type": "string"}], "type": "array"}, "title": "Intermediate Steps", "type": "array"}}, "required": ["input"], "title": "LangGraphInput", "type": "object"}'
|
||||
# ---
|
||||
# name: test_state_graph_w_config_inherited_state_keys.2
|
||||
'{"$defs": {"AgentAction": {"description": "Represents a request to execute an action by an agent.\\n\\nThe action consists of the name of the tool to execute and the input to pass\\nto the tool. The log is used to pass along extra information about the action.", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"anyOf": [{"type": "string"}, {"additionalProperties": true, "type": "object"}], "title": "Tool Input"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentAction", "default": "AgentAction", "title": "Type", "type": "string"}}, "required": ["tool", "tool_input", "log"], "title": "AgentAction", "type": "object"}, "AgentFinish": {"description": "Final return value of an ActionAgent.\\n\\nAgents return an AgentFinish when they have reached a stopping condition.", "properties": {"return_values": {"additionalProperties": true, "title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentFinish", "default": "AgentFinish", "title": "Type", "type": "string"}}, "required": ["return_values", "log"], "title": "AgentFinish", "type": "object"}}, "properties": {"input": {"title": "Input", "type": "string"}, "agent_outcome": {"anyOf": [{"$ref": "#/$defs/AgentAction"}, {"$ref": "#/$defs/AgentFinish"}, {"type": "null"}], "title": "Agent Outcome"}, "intermediate_steps": {"items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"$ref": "#/$defs/AgentAction"}, {"type": "string"}], "type": "array"}, "title": "Intermediate Steps", "type": "array"}}, "required": ["input", "agent_outcome"], "title": "AgentState", "type": "object"}'
|
||||
'{"$defs": {"AgentAction": {"description": "Represents a request to execute an action by an agent.\\n\\nThe action consists of the name of the tool to execute and the input to pass\\nto the tool. The log is used to pass along extra information about the action.", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"anyOf": [{"type": "string"}, {"type": "object"}], "title": "Tool Input"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentAction", "default": "AgentAction", "enum": ["AgentAction"], "title": "Type", "type": "string"}}, "required": ["tool", "tool_input", "log"], "title": "AgentAction", "type": "object"}, "AgentFinish": {"description": "Final return value of an ActionAgent.\\n\\nAgents return an AgentFinish when they have reached a stopping condition.", "properties": {"return_values": {"title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentFinish", "default": "AgentFinish", "enum": ["AgentFinish"], "title": "Type", "type": "string"}}, "required": ["return_values", "log"], "title": "AgentFinish", "type": "object"}}, "properties": {"input": {"title": "Input", "type": "string"}, "agent_outcome": {"anyOf": [{"$ref": "#/$defs/AgentAction"}, {"$ref": "#/$defs/AgentFinish"}, {"type": "null"}], "default": null, "title": "Agent Outcome"}, "intermediate_steps": {"default": null, "items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"$ref": "#/$defs/AgentAction"}, {"type": "string"}], "type": "array"}, "title": "Intermediate Steps", "type": "array"}}, "required": ["input"], "title": "LangGraphOutput", "type": "object"}'
|
||||
# ---
|
||||
# name: test_xray_bool
|
||||
'''
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,10 +20,9 @@ from langgraph.channels.last_value import LastValue
|
||||
from langgraph.channels.untracked_value import UntrackedValue
|
||||
from langgraph.checkpoint.base import BaseCheckpointSaver
|
||||
from langgraph.constants import END, PULL, PUSH, START
|
||||
from langgraph.graph.message import MessageGraph, add_messages
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.graph.state import StateGraph
|
||||
from langgraph.prebuilt.chat_agent_executor import create_react_agent
|
||||
from langgraph.prebuilt.tool_node import ToolNode
|
||||
from langgraph.pregel import NodeBuilder, Pregel
|
||||
from langgraph.types import PregelTask, Send, StateSnapshot, StreamWriter
|
||||
from tests.any_int import AnyInt
|
||||
@@ -2059,415 +2058,7 @@ async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> N
|
||||
)
|
||||
|
||||
|
||||
async def test_message_graph(async_checkpointer: BaseCheckpointSaver) -> None:
|
||||
from langchain_core.language_models.fake_chat_models import (
|
||||
FakeMessagesListChatModel,
|
||||
)
|
||||
from langchain_core.messages import AIMessage, HumanMessage
|
||||
from langchain_core.tools import tool
|
||||
|
||||
class FakeFuntionChatModel(FakeMessagesListChatModel):
|
||||
def bind_functions(self, functions: list):
|
||||
return self
|
||||
|
||||
@tool()
|
||||
def search_api(query: str) -> str:
|
||||
"""Searches the API for the query."""
|
||||
return f"result for {query}"
|
||||
|
||||
tools = [search_api]
|
||||
|
||||
model = FakeFuntionChatModel(
|
||||
responses=[
|
||||
AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call123",
|
||||
"name": "search_api",
|
||||
"args": {"query": "query"},
|
||||
}
|
||||
],
|
||||
id="ai1",
|
||||
),
|
||||
AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call456",
|
||||
"name": "search_api",
|
||||
"args": {"query": "another"},
|
||||
}
|
||||
],
|
||||
id="ai2",
|
||||
),
|
||||
AIMessage(content="answer", id="ai3"),
|
||||
]
|
||||
)
|
||||
|
||||
# Define the function that determines whether to continue or not
|
||||
def should_continue(messages):
|
||||
last_message = messages[-1]
|
||||
# If there is no function call, then we finish
|
||||
if not last_message.tool_calls:
|
||||
return "end"
|
||||
# Otherwise if there is, we continue
|
||||
else:
|
||||
return "continue"
|
||||
|
||||
# Define a new graph
|
||||
workflow = MessageGraph()
|
||||
|
||||
# Define the two nodes we will cycle between
|
||||
workflow.add_node("agent", model)
|
||||
workflow.add_node("tools", ToolNode(tools))
|
||||
|
||||
# Set the entrypoint as `agent`
|
||||
# This means that this node is the first one called
|
||||
workflow.set_entry_point("agent")
|
||||
|
||||
# We now add a conditional edge
|
||||
workflow.add_conditional_edges(
|
||||
# First, we define the start node. We use `agent`.
|
||||
# This means these are the edges taken after the `agent` node is called.
|
||||
"agent",
|
||||
# Next, we pass in the function that will determine which node is called next.
|
||||
should_continue,
|
||||
# Finally we pass in a mapping.
|
||||
# The keys are strings, and the values are other nodes.
|
||||
# END is a special node marking that the graph should finish.
|
||||
# What will happen is we will call `should_continue`, and then the output of that
|
||||
# will be matched against the keys in this mapping.
|
||||
# Based on which one it matches, that node will then be called.
|
||||
{
|
||||
# If `tools`, then we call the tool node.
|
||||
"continue": "tools",
|
||||
# Otherwise we finish.
|
||||
"end": END,
|
||||
},
|
||||
)
|
||||
|
||||
# We now add a normal edge from `tools` to `agent`.
|
||||
# This means that after `tools` is called, `agent` node is called next.
|
||||
workflow.add_edge("tools", "agent")
|
||||
|
||||
# Finally, we compile it!
|
||||
# This compiles it into a LangChain Runnable,
|
||||
# meaning you can use it as you would any other runnable
|
||||
app = workflow.compile()
|
||||
|
||||
assert await app.ainvoke(HumanMessage(content="what is weather in sf")) == [
|
||||
_AnyIdHumanMessage(
|
||||
content="what is weather in sf",
|
||||
),
|
||||
AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call123",
|
||||
"name": "search_api",
|
||||
"args": {"query": "query"},
|
||||
}
|
||||
],
|
||||
id="ai1", # respects ids passed in
|
||||
),
|
||||
_AnyIdToolMessage(
|
||||
content="result for query",
|
||||
name="search_api",
|
||||
tool_call_id="tool_call123",
|
||||
),
|
||||
AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call456",
|
||||
"name": "search_api",
|
||||
"args": {"query": "another"},
|
||||
}
|
||||
],
|
||||
id="ai2",
|
||||
),
|
||||
_AnyIdToolMessage(
|
||||
content="result for another",
|
||||
name="search_api",
|
||||
tool_call_id="tool_call456",
|
||||
),
|
||||
AIMessage(content="answer", id="ai3"),
|
||||
]
|
||||
|
||||
assert [
|
||||
c async for c in app.astream([HumanMessage(content="what is weather in sf")])
|
||||
] == [
|
||||
{
|
||||
"agent": AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call123",
|
||||
"name": "search_api",
|
||||
"args": {"query": "query"},
|
||||
}
|
||||
],
|
||||
id="ai1",
|
||||
)
|
||||
},
|
||||
{
|
||||
"tools": [
|
||||
_AnyIdToolMessage(
|
||||
content="result for query",
|
||||
name="search_api",
|
||||
tool_call_id="tool_call123",
|
||||
)
|
||||
]
|
||||
},
|
||||
{
|
||||
"agent": AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call456",
|
||||
"name": "search_api",
|
||||
"args": {"query": "another"},
|
||||
}
|
||||
],
|
||||
id="ai2",
|
||||
)
|
||||
},
|
||||
{
|
||||
"tools": [
|
||||
_AnyIdToolMessage(
|
||||
content="result for another",
|
||||
name="search_api",
|
||||
tool_call_id="tool_call456",
|
||||
)
|
||||
]
|
||||
},
|
||||
{"agent": AIMessage(content="answer", id="ai3")},
|
||||
]
|
||||
|
||||
app_w_interrupt = workflow.compile(
|
||||
checkpointer=async_checkpointer,
|
||||
interrupt_after=["agent"],
|
||||
)
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
|
||||
assert [
|
||||
c
|
||||
async for c in app_w_interrupt.astream(
|
||||
HumanMessage(content="what is weather in sf"),
|
||||
config,
|
||||
checkpoint_during=False,
|
||||
)
|
||||
] == [
|
||||
{
|
||||
"agent": AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call123",
|
||||
"name": "search_api",
|
||||
"args": {"query": "query"},
|
||||
}
|
||||
],
|
||||
id="ai1",
|
||||
)
|
||||
},
|
||||
{"__interrupt__": ()},
|
||||
]
|
||||
|
||||
tup = await app_w_interrupt.checkpointer.aget_tuple(config)
|
||||
assert await app_w_interrupt.aget_state(config) == StateSnapshot(
|
||||
values=[
|
||||
_AnyIdHumanMessage(content="what is weather in sf"),
|
||||
AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call123",
|
||||
"name": "search_api",
|
||||
"args": {"query": "query"},
|
||||
}
|
||||
],
|
||||
id="ai1",
|
||||
),
|
||||
],
|
||||
tasks=(PregelTask(AnyStr(), "tools", (PULL, "tools")),),
|
||||
next=("tools",),
|
||||
config=tup.config,
|
||||
created_at=tup.checkpoint["ts"],
|
||||
metadata={
|
||||
"parents": {},
|
||||
"source": "loop",
|
||||
"step": 1,
|
||||
},
|
||||
parent_config=None,
|
||||
interrupts=(),
|
||||
)
|
||||
|
||||
# modify ai message
|
||||
last_message = (await app_w_interrupt.aget_state(config)).values[-1]
|
||||
last_message.tool_calls[0]["args"] = {"query": "a different query"}
|
||||
await app_w_interrupt.aupdate_state(config, last_message)
|
||||
|
||||
# message was replaced instead of appended
|
||||
tup = await app_w_interrupt.checkpointer.aget_tuple(config)
|
||||
assert await app_w_interrupt.aget_state(config) == StateSnapshot(
|
||||
values=[
|
||||
_AnyIdHumanMessage(content="what is weather in sf"),
|
||||
AIMessage(
|
||||
content="",
|
||||
id="ai1",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call123",
|
||||
"name": "search_api",
|
||||
"args": {"query": "a different query"},
|
||||
}
|
||||
],
|
||||
),
|
||||
],
|
||||
tasks=(PregelTask(AnyStr(), "tools", (PULL, "tools")),),
|
||||
next=("tools",),
|
||||
config=tup.config,
|
||||
created_at=tup.checkpoint["ts"],
|
||||
metadata={
|
||||
"parents": {},
|
||||
"source": "update",
|
||||
"step": 2,
|
||||
},
|
||||
parent_config=(
|
||||
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
|
||||
-1
|
||||
].config
|
||||
),
|
||||
interrupts=(),
|
||||
)
|
||||
|
||||
assert [c async for c in app_w_interrupt.astream(None, config)] == [
|
||||
{
|
||||
"tools": [
|
||||
_AnyIdToolMessage(
|
||||
content="result for a different query",
|
||||
name="search_api",
|
||||
tool_call_id="tool_call123",
|
||||
)
|
||||
]
|
||||
},
|
||||
{
|
||||
"agent": AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call456",
|
||||
"name": "search_api",
|
||||
"args": {"query": "another"},
|
||||
}
|
||||
],
|
||||
id="ai2",
|
||||
)
|
||||
},
|
||||
{"__interrupt__": ()},
|
||||
]
|
||||
|
||||
tup = await app_w_interrupt.checkpointer.aget_tuple(config)
|
||||
assert await app_w_interrupt.aget_state(config) == StateSnapshot(
|
||||
values=[
|
||||
_AnyIdHumanMessage(content="what is weather in sf"),
|
||||
AIMessage(
|
||||
content="",
|
||||
id="ai1",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call123",
|
||||
"name": "search_api",
|
||||
"args": {"query": "a different query"},
|
||||
}
|
||||
],
|
||||
),
|
||||
_AnyIdToolMessage(
|
||||
content="result for a different query",
|
||||
name="search_api",
|
||||
tool_call_id="tool_call123",
|
||||
),
|
||||
AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call456",
|
||||
"name": "search_api",
|
||||
"args": {"query": "another"},
|
||||
}
|
||||
],
|
||||
id="ai2",
|
||||
),
|
||||
],
|
||||
tasks=(PregelTask(AnyStr(), "tools", (PULL, "tools")),),
|
||||
next=("tools",),
|
||||
config=tup.config,
|
||||
created_at=tup.checkpoint["ts"],
|
||||
metadata={
|
||||
"parents": {},
|
||||
"source": "loop",
|
||||
"step": 4,
|
||||
},
|
||||
parent_config=(
|
||||
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
|
||||
-1
|
||||
].config
|
||||
),
|
||||
interrupts=(),
|
||||
)
|
||||
|
||||
await app_w_interrupt.aupdate_state(
|
||||
config,
|
||||
AIMessage(content="answer", id="ai2"),
|
||||
)
|
||||
|
||||
# replaces message even if object identity is different, as long as id is the same
|
||||
tup = await app_w_interrupt.checkpointer.aget_tuple(config)
|
||||
assert await app_w_interrupt.aget_state(config) == StateSnapshot(
|
||||
values=[
|
||||
_AnyIdHumanMessage(content="what is weather in sf"),
|
||||
AIMessage(
|
||||
content="",
|
||||
id="ai1",
|
||||
tool_calls=[
|
||||
{
|
||||
"id": "tool_call123",
|
||||
"name": "search_api",
|
||||
"args": {"query": "a different query"},
|
||||
}
|
||||
],
|
||||
),
|
||||
_AnyIdToolMessage(
|
||||
content="result for a different query",
|
||||
name="search_api",
|
||||
tool_call_id="tool_call123",
|
||||
),
|
||||
AIMessage(content="answer", id="ai2"),
|
||||
],
|
||||
tasks=(),
|
||||
next=(),
|
||||
config=tup.config,
|
||||
created_at=tup.checkpoint["ts"],
|
||||
metadata={
|
||||
"parents": {},
|
||||
"source": "update",
|
||||
"step": 5,
|
||||
},
|
||||
parent_config=(
|
||||
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
|
||||
-1
|
||||
].config
|
||||
),
|
||||
interrupts=(),
|
||||
)
|
||||
|
||||
|
||||
async def test_in_one_fan_out_out_one_graph_state() -> None:
|
||||
def sorted_add(x: list[str], y: list[str]) -> list[str]:
|
||||
return sorted(operator.add(x, y))
|
||||
|
||||
class State(TypedDict, total=False):
|
||||
query: str
|
||||
answer: str
|
||||
|
||||
@@ -46,7 +46,7 @@ from langgraph.constants import CONFIG_KEY_NODE_FINISHED, ERROR, PULL, START
|
||||
from langgraph.errors import InvalidUpdateError, ParentCommand
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.graph import END, StateGraph
|
||||
from langgraph.graph.message import MessageGraph, MessagesState, add_messages
|
||||
from langgraph.graph.message import MessagesState, add_messages
|
||||
from langgraph.prebuilt.tool_node import ToolNode
|
||||
from langgraph.pregel import (
|
||||
GraphRecursionError,
|
||||
@@ -1723,8 +1723,8 @@ def test_conditional_entrypoint_to_multiple_state_graph(
|
||||
|
||||
app = workflow.compile()
|
||||
|
||||
assert json.dumps(app.get_input_jsonschema()) == snapshot
|
||||
assert json.dumps(app.get_output_jsonschema()) == snapshot
|
||||
assert json.dumps(app.get_input_schema().model_json_schema()) == snapshot
|
||||
assert json.dumps(app.get_output_schema().model_json_schema()) == snapshot
|
||||
assert json.dumps(app.get_graph().to_json(), indent=2) == snapshot
|
||||
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
|
||||
|
||||
@@ -1848,8 +1848,8 @@ def test_state_graph_w_config_inherited_state_keys(snapshot: SnapshotAssertion)
|
||||
app = builder.compile()
|
||||
|
||||
assert json.dumps(app.config_schema().model_json_schema()) == snapshot
|
||||
assert json.dumps(app.get_input_jsonschema()) == snapshot
|
||||
assert json.dumps(app.get_output_jsonschema()) == snapshot
|
||||
assert json.dumps(app.get_input_schema().model_json_schema()) == snapshot
|
||||
assert json.dumps(app.get_output_schema().model_json_schema()) == snapshot
|
||||
|
||||
assert builder.channels.keys() == {"input", "agent_outcome", "intermediate_steps"}
|
||||
|
||||
@@ -1912,8 +1912,8 @@ def test_conditional_entrypoint_graph_state(snapshot: SnapshotAssertion) -> None
|
||||
|
||||
app = workflow.compile()
|
||||
|
||||
assert json.dumps(app.get_input_jsonschema()) == snapshot
|
||||
assert json.dumps(app.get_output_jsonschema()) == snapshot
|
||||
assert json.dumps(app.get_input_schema().model_json_schema()) == snapshot
|
||||
assert json.dumps(app.get_output_schema().model_json_schema()) == snapshot
|
||||
assert json.dumps(app.get_graph().to_json(), indent=2) == snapshot
|
||||
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
|
||||
|
||||
@@ -2507,8 +2507,8 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2(
|
||||
|
||||
if isinstance(sync_checkpointer, InMemorySaver):
|
||||
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
|
||||
assert app.get_input_jsonschema() == snapshot
|
||||
assert app.get_output_jsonschema() == snapshot
|
||||
assert app.get_input_schema().model_json_schema() == snapshot
|
||||
assert app.get_output_schema().model_json_schema() == snapshot
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
app.invoke({"query": {}})
|
||||
@@ -3984,9 +3984,14 @@ def test_checkpoint_metadata(sync_checkpointer: BaseCheckpointSaver) -> None:
|
||||
def test_remove_message_via_state_update(
|
||||
sync_checkpointer: BaseCheckpointSaver,
|
||||
) -> None:
|
||||
from langchain_core.messages import AIMessage, HumanMessage, RemoveMessage
|
||||
from langchain_core.messages import (
|
||||
AIMessage,
|
||||
AnyMessage,
|
||||
HumanMessage,
|
||||
RemoveMessage,
|
||||
)
|
||||
|
||||
workflow = MessageGraph()
|
||||
workflow = StateGraph(Annotated[list[AnyMessage], add_messages])
|
||||
workflow.add_node(
|
||||
"chatbot",
|
||||
lambda state: [
|
||||
@@ -4017,9 +4022,14 @@ def test_remove_message_via_state_update(
|
||||
|
||||
|
||||
def test_remove_message_from_node():
|
||||
from langchain_core.messages import AIMessage, HumanMessage, RemoveMessage
|
||||
from langchain_core.messages import (
|
||||
AIMessage,
|
||||
AnyMessage,
|
||||
HumanMessage,
|
||||
RemoveMessage,
|
||||
)
|
||||
|
||||
workflow = MessageGraph()
|
||||
workflow = StateGraph(Annotated[list[AnyMessage], add_messages])
|
||||
workflow.add_node(
|
||||
"chatbot",
|
||||
lambda state: [
|
||||
@@ -6549,7 +6559,7 @@ def test_entrypoint_output_schema_with_return_and_save() -> None:
|
||||
def foo2(inputs, *, previous: Any) -> entrypoint.final:
|
||||
return entrypoint.final(value="foo", save=1)
|
||||
|
||||
assert foo2.get_output_jsonschema() == {
|
||||
assert foo2.get_output_schema().model_json_schema() == {
|
||||
"title": "LangGraphOutput",
|
||||
}
|
||||
|
||||
@@ -6557,7 +6567,7 @@ def test_entrypoint_output_schema_with_return_and_save() -> None:
|
||||
def foo(inputs, *, previous: Any) -> entrypoint.final[str, int]:
|
||||
return entrypoint.final(value="foo", save=1)
|
||||
|
||||
assert foo.get_output_jsonschema() == {
|
||||
assert foo.get_output_schema().model_json_schema() == {
|
||||
"title": "LangGraphOutput",
|
||||
"type": "string",
|
||||
}
|
||||
@@ -6584,7 +6594,7 @@ def test_entrypoint_with_return_and_save(
|
||||
previous = previous or []
|
||||
return entrypoint.final(value=len(previous), save=previous + [msg])
|
||||
|
||||
assert foo.get_output_jsonschema() == {
|
||||
assert foo.get_output_schema().model_json_schema() == {
|
||||
"title": "LangGraphOutput",
|
||||
"type": "integer",
|
||||
}
|
||||
|
||||
@@ -4493,8 +4493,8 @@ async def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydant
|
||||
|
||||
if isinstance(async_checkpointer, InMemorySaver):
|
||||
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
|
||||
assert app.get_input_jsonschema() == snapshot
|
||||
assert app.get_output_jsonschema() == snapshot
|
||||
assert app.get_input_schema().model_json_schema() == snapshot
|
||||
assert app.get_output_schema().model_json_schema() == snapshot
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
await app.ainvoke({"query": {}})
|
||||
|
||||
@@ -163,12 +163,13 @@ def test_state_schema_optional_values(total_: bool):
|
||||
expected_required = set()
|
||||
expected_optional = {"val2", "val1"}
|
||||
else:
|
||||
expected_required = {"val1", "val2"}
|
||||
expected_optional = set()
|
||||
expected_required = {"val1"}
|
||||
|
||||
expected_optional = {"val2"}
|
||||
|
||||
# The others should always have precedence based on the required annotation
|
||||
expected_required |= {"val0a", "val0b", "val3", "val5"}
|
||||
expected_optional |= {"val4", "val6"}
|
||||
expected_required |= {"val0a", "val3", "val5"}
|
||||
expected_optional |= {"val0b", "val4", "val6"}
|
||||
|
||||
assert set(json_schema.get("required", set())) == expected_required
|
||||
assert (
|
||||
@@ -181,11 +182,11 @@ def test_state_schema_optional_values(total_: bool):
|
||||
expected_required = set()
|
||||
expected_optional = {"out_val2", "out_val1"}
|
||||
else:
|
||||
expected_required = {"out_val1", "out_val2"}
|
||||
expected_optional = set()
|
||||
expected_required = {"out_val1"}
|
||||
expected_optional = {"out_val2"}
|
||||
|
||||
expected_required |= {"val0a", "val0b", "out_val3", "out_val5"}
|
||||
expected_optional |= {"out_val4", "out_val6"}
|
||||
expected_required |= {"val0a", "out_val3", "out_val5"}
|
||||
expected_optional |= {"val0b", "out_val4", "out_val6"}
|
||||
|
||||
assert set(output_schema.get("required", set())) == expected_required
|
||||
assert (
|
||||
|
||||
Generated
+90
-125
@@ -1183,7 +1183,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langchain-core"
|
||||
version = "0.3.67"
|
||||
version = "0.3.63"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jsonpatch" },
|
||||
@@ -1194,14 +1194,14 @@ dependencies = [
|
||||
{ name = "tenacity" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c2/40/875af0194024d0006874f061958fa417d3500bbfdc9a57e1bd1c2f4e6ed2/langchain_core-0.3.67.tar.gz", hash = "sha256:2c14aa44a0e78e014e96d7f2f8916ac109d0a0ba87ed67ee25bf7296bed7e7ba", size = 561952, upload-time = "2025-06-30T17:09:35.142Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/0a/b71a9a5d42e743d6876cce23d803e284b191ed4d6544e2f7fe1b37f7854c/langchain_core-0.3.63.tar.gz", hash = "sha256:e2e30cfbb7684a5a0319f6cbf065fc3c438bfd1060302f085a122527890fb01e", size = 558302, upload-time = "2025-05-29T18:57:19.933Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/2b/a0d283089c6d08c12d47dca39a55029ff714e939ec04f4560420426ab613/langchain_core-0.3.67-py3-none-any.whl", hash = "sha256:b699f1f24b24fa2747c05e2daa280aa64478a51e01a4e82c7f8e20b6167dfa99", size = 440237, upload-time = "2025-06-30T17:09:33.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/71/a748861e6a69ab6ef50ab8e65120422a1f36245c71a0dd0f02de49c208e1/langchain_core-0.3.63-py3-none-any.whl", hash = "sha256:f91db8221b1bc6808f70b2e72fded1a94d50ee3f1dff1636fb5a5a514c64b7f5", size = 438468, upload-time = "2025-05-29T18:57:17.424Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.5.1"
|
||||
version = "0.5.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
@@ -1423,7 +1423,7 @@ inmem = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-prebuilt"
|
||||
version = "0.5.2"
|
||||
version = "0.5.1"
|
||||
source = { editable = "../prebuilt" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
@@ -1432,7 +1432,7 @@ dependencies = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "langchain-core", specifier = ">=0.3.67" },
|
||||
{ name = "langchain-core", specifier = ">=0.3.22" },
|
||||
{ name = "langgraph-checkpoint", editable = "../checkpoint" },
|
||||
]
|
||||
|
||||
@@ -1497,7 +1497,7 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "langsmith"
|
||||
version = "0.4.4"
|
||||
version = "0.3.43"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
@@ -1508,9 +1508,9 @@ dependencies = [
|
||||
{ name = "requests-toolbelt" },
|
||||
{ name = "zstandard" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/c8/8d2e0fc438d2d3d8d4300f7684ea30a754344ed00d7ba9cc2705241d2a5f/langsmith-0.4.4.tar.gz", hash = "sha256:70c53bbff24a7872e88e6fa0af98270f4986a6e364f9e85db1cc5636defa4d66", size = 352105, upload-time = "2025-06-27T19:20:36.207Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/21/df84fe8b5c16971999650cbfc95a49f176d044a606e2b4eb957bbc122e1c/langsmith-0.3.43.tar.gz", hash = "sha256:7dab99b635859e24a1a252ad4f7e23170a45f4ea742567a10b4b26c50478ed43", size = 346328, upload-time = "2025-05-29T00:21:11.637Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/33/a3337eb70d795495a299a1640d7a75f17fb917155a64309b96106e7b9452/langsmith-0.4.4-py3-none-any.whl", hash = "sha256:014c68329bd085bd6c770a6405c61bb6881f82eb554ce8c4d1984b0035fd1716", size = 367687, upload-time = "2025-06-27T19:20:33.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/72/f5304de3e7e80e6dc266c161230aecb7895958f78b5af1541317d7615fc6/langsmith-0.3.43-py3-none-any.whl", hash = "sha256:2d4558068abf2eeb60ff80871187724e07f5e657d7d6be9e0c603df36c41140a", size = 361148, upload-time = "2025-05-29T00:21:08.759Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2166,126 +2166,103 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.11.7"
|
||||
version = "2.9.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917, upload-time = "2024-09-17T15:59:54.273Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928, upload-time = "2024-09-17T15:59:51.827Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.33.2"
|
||||
version = "2.23.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156, upload-time = "2024-09-16T16:06:44.786Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/ea/bbe9095cdd771987d13c82d104a9c8559ae9aec1e29f139e286fd2e9256e/pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d", size = 2028677, upload-time = "2025-04-23T18:32:27.227Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/1d/4ac5ed228078737d457a609013e8f7edc64adc37b91d619ea965758369e5/pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954", size = 1864735, upload-time = "2025-04-23T18:32:29.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/9a/2e70d6388d7cda488ae38f57bc2f7b03ee442fbcf0d75d848304ac7e405b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb", size = 1898467, upload-time = "2025-04-23T18:32:31.119Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/2e/1568934feb43370c1ffb78a77f0baaa5a8b6897513e7a91051af707ffdc4/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7", size = 1983041, upload-time = "2025-04-23T18:32:33.655Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/1a/1a1118f38ab64eac2f6269eb8c120ab915be30e387bb561e3af904b12499/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4", size = 2136503, upload-time = "2025-04-23T18:32:35.519Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/da/44754d1d7ae0f22d6d3ce6c6b1486fc07ac2c524ed8f6eca636e2e1ee49b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b", size = 2736079, upload-time = "2025-04-23T18:32:37.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/98/f43cd89172220ec5aa86654967b22d862146bc4d736b1350b4c41e7c9c03/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3", size = 2006508, upload-time = "2025-04-23T18:32:39.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/cc/f77e8e242171d2158309f830f7d5d07e0531b756106f36bc18712dc439df/pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a", size = 2113693, upload-time = "2025-04-23T18:32:41.818Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/7a/7be6a7bd43e0a47c147ba7fbf124fe8aaf1200bc587da925509641113b2d/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782", size = 2074224, upload-time = "2025-04-23T18:32:44.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/07/31cf8fadffbb03be1cb520850e00a8490c0927ec456e8293cafda0726184/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9", size = 2245403, upload-time = "2025-04-23T18:32:45.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/8d/bbaf4c6721b668d44f01861f297eb01c9b35f612f6b8e14173cb204e6240/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e", size = 2242331, upload-time = "2025-04-23T18:32:47.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/93/3cc157026bca8f5006250e74515119fcaa6d6858aceee8f67ab6dc548c16/pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9", size = 1910571, upload-time = "2025-04-23T18:32:49.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/90/7edc3b2a0d9f0dda8806c04e511a67b0b7a41d2187e2003673a996fb4310/pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3", size = 1956504, upload-time = "2025-04-23T18:32:51.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/98/dbf3fdfabaf81cda5622154fda78ea9965ac467e3239078e0dcd6df159e7/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101", size = 2024034, upload-time = "2025-04-23T18:33:32.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/99/7810aa9256e7f2ccd492590f86b79d370df1e9292f1f80b000b6a75bd2fb/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64", size = 1858578, upload-time = "2025-04-23T18:33:34.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/60/bc06fa9027c7006cc6dd21e48dbf39076dc39d9abbaf718a1604973a9670/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d", size = 1892858, upload-time = "2025-04-23T18:33:36.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/40/9d03997d9518816c68b4dfccb88969756b9146031b61cd37f781c74c9b6a/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535", size = 2068498, upload-time = "2025-04-23T18:33:38.997Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/62/d490198d05d2d86672dc269f52579cad7261ced64c2df213d5c16e0aecb1/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d", size = 2108428, upload-time = "2025-04-23T18:33:41.18Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/ec/4cd215534fd10b8549015f12ea650a1a973da20ce46430b68fc3185573e8/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6", size = 2069854, upload-time = "2025-04-23T18:33:43.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/1a/abbd63d47e1d9b0d632fee6bb15785d0889c8a6e0a6c3b5a8e28ac1ec5d2/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca", size = 2237859, upload-time = "2025-04-23T18:33:45.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/1c/fa883643429908b1c90598fd2642af8839efd1d835b65af1f75fba4d94fe/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039", size = 2239059, upload-time = "2025-04-23T18:33:47.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/29/3cade8a924a61f60ccfa10842f75eb12787e1440e2b8660ceffeb26685e7/pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27", size = 2066661, upload-time = "2025-04-23T18:33:49.995Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835, upload-time = "2024-09-16T16:03:57.223Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689, upload-time = "2024-09-16T16:03:59.266Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748, upload-time = "2024-09-16T16:04:01.011Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469, upload-time = "2024-09-16T16:04:02.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246, upload-time = "2024-09-16T16:04:03.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404, upload-time = "2024-09-16T16:04:05.299Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940, upload-time = "2024-09-16T16:04:06.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437, upload-time = "2024-09-16T16:04:08.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129, upload-time = "2024-09-16T16:04:10.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908, upload-time = "2024-09-16T16:04:12.412Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278, upload-time = "2024-09-16T16:04:13.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453, upload-time = "2024-09-16T16:04:15.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160, upload-time = "2024-09-16T16:04:18.628Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777, upload-time = "2024-09-16T16:04:20.038Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244, upload-time = "2024-09-16T16:04:21.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307, upload-time = "2024-09-16T16:04:23.324Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663, upload-time = "2024-09-16T16:04:25.203Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941, upload-time = "2024-09-16T16:04:27.211Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105, upload-time = "2024-09-16T16:04:28.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967, upload-time = "2024-09-16T16:04:30.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291, upload-time = "2024-09-16T16:04:32.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666, upload-time = "2024-09-16T16:04:33.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940, upload-time = "2024-09-16T16:04:35.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804, upload-time = "2024-09-16T16:04:37.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459, upload-time = "2024-09-16T16:04:38.438Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007, upload-time = "2024-09-16T16:04:40.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245, upload-time = "2024-09-16T16:04:41.794Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260, upload-time = "2024-09-16T16:04:43.991Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872, upload-time = "2024-09-16T16:04:45.593Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617, upload-time = "2024-09-16T16:04:47.3Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831, upload-time = "2024-09-16T16:04:48.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453, upload-time = "2024-09-16T16:04:51.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793, upload-time = "2024-09-16T16:04:52.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872, upload-time = "2024-09-16T16:04:54.41Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535, upload-time = "2024-09-16T16:04:55.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992, upload-time = "2024-09-16T16:04:57.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143, upload-time = "2024-09-16T16:04:59.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063, upload-time = "2024-09-16T16:05:00.522Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013, upload-time = "2024-09-16T16:05:02.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077, upload-time = "2024-09-16T16:05:04.154Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782, upload-time = "2024-09-16T16:05:06.931Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375, upload-time = "2024-09-16T16:05:08.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635, upload-time = "2024-09-16T16:05:10.456Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994, upload-time = "2024-09-16T16:05:12.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877, upload-time = "2024-09-16T16:05:14.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814, upload-time = "2024-09-16T16:05:15.684Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360, upload-time = "2024-09-16T16:05:17.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411, upload-time = "2024-09-16T16:05:18.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/04/2580b2deaae37b3e30fc30c54298be938b973990b23612d6b61c7bdd01c7/pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a", size = 1868200, upload-time = "2024-09-16T16:05:48.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/6e/e311bd0751505350f0cdcee3077841eb1f9253c5a1ddbad048cd9fbf7c6e/pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36", size = 1749316, upload-time = "2024-09-16T16:05:50.176Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/b4/95b5eb47c6dc8692508c3ca04a1f8d6f0884c9dacb34cf3357595cbe73be/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b", size = 1800880, upload-time = "2024-09-16T16:05:52.58Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/79/41c4f817acd7f42d94cd1e16526c062a7b089f66faed4bd30852314d9a66/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323", size = 1807077, upload-time = "2024-09-16T16:05:54.313Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/53/d13d1eb0a97d5c06cf7a225935d471e9c241afd389a333f40c703f214973/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3", size = 2002859, upload-time = "2024-09-16T16:05:56.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/7d/6b8a1eff453774b46cac8c849e99455b27167971a003212f668e94bc4c9c/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df", size = 2661437, upload-time = "2024-09-16T16:05:57.96Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/ea/8820f57f0b46e6148ee42d8216b15e8fe3b360944284bbc705bf34fac888/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c", size = 2054404, upload-time = "2024-09-16T16:05:59.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/36/d4ae869e473c3c7868e1cd1e2a1b9e13bce5cd1a7d287f6ac755a0b1575e/pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55", size = 1921680, upload-time = "2024-09-16T16:06:01.554Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/f8/eed5c65b80c4ac4494117e2101973b45fc655774ef647d17dde40a70f7d2/pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040", size = 1966093, upload-time = "2024-09-16T16:06:03.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/c8/1d42ce51d65e571ab53d466cae83434325a126811df7ce4861d9d97bee4b/pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605", size = 2111437, upload-time = "2024-09-16T16:06:05.016Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/c9/7fea9d13383c2ec6865919e09cffe44ab77e911eb281b53a4deaafd4c8e8/pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6", size = 1735049, upload-time = "2024-09-16T16:06:06.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/95/dd7045c4caa2b73d0bf3b989d66b23cfbb7a0ef14ce99db15677a000a953/pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29", size = 1920180, upload-time = "2024-09-16T16:06:08.528Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135, upload-time = "2024-09-16T16:06:10.45Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583, upload-time = "2024-09-16T16:06:12.298Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637, upload-time = "2024-09-16T16:06:14.092Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963, upload-time = "2024-09-16T16:06:16.757Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332, upload-time = "2024-09-16T16:06:18.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926, upload-time = "2024-09-16T16:06:20.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342, upload-time = "2024-09-16T16:06:22.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344, upload-time = "2024-09-16T16:06:24.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/fd/ac9cdfaaa7cf2d32590b807d900612b39acb25e5527c3c7e482f0553025b/pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21", size = 1857850, upload-time = "2024-09-16T16:06:26.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/fe/038f4b2bcae325ea643c8ad353191187a4c92a9c3b913b139289a6f2ef04/pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb", size = 1740265, upload-time = "2024-09-16T16:06:28.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/14/b215c9c3cbd1edaaea23014d4b3304260823f712d3fdee52549b19b25d62/pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59", size = 1793912, upload-time = "2024-09-16T16:06:30.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/de/2c3ad79b63ba564878cbce325be725929ba50089cd5156f89ea5155cb9b3/pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577", size = 1942870, upload-time = "2024-09-16T16:06:33.298Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/55/c222af19e4644c741b3f3fe4fd8bbb6b4cdca87d8a49258b61cf7826b19e/pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744", size = 1915610, upload-time = "2024-09-16T16:06:35.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/7a/9a8760692a6f76bb54bcd43f245ff3d8b603db695899bbc624099c00af80/pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef", size = 1958403, upload-time = "2024-09-16T16:06:37.722Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/91/9b03166feb914bb5698e2f6499e07c2617e2eebf69f9374d0358d7eb2009/pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8", size = 2101154, upload-time = "2024-09-16T16:06:40.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/d9/1d7ecb98318da4cb96986daaf0e20d66f1651d0aeb9e2d4435b916ce031d/pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e", size = 1920855, upload-time = "2024-09-16T16:06:42.707Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3096,18 +3073,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
|
||||
@@ -30,10 +30,7 @@ from langchain_core.runnables.config import (
|
||||
)
|
||||
from langchain_core.tools import BaseTool, InjectedToolArg
|
||||
from langchain_core.tools import tool as create_tool
|
||||
from langchain_core.tools.base import (
|
||||
TOOL_MESSAGE_BLOCK_TYPES,
|
||||
get_all_basemodel_annotations,
|
||||
)
|
||||
from langchain_core.tools.base import get_all_basemodel_annotations
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Annotated, get_args, get_origin
|
||||
|
||||
@@ -49,11 +46,12 @@ TOOL_CALL_ERROR_TEMPLATE = "Error: {error}\n Please fix your mistakes."
|
||||
|
||||
|
||||
def msg_content_output(output: Any) -> Union[str, list[dict]]:
|
||||
recognized_content_block_types = ("image", "image_url", "text", "json")
|
||||
if isinstance(output, str):
|
||||
return output
|
||||
elif isinstance(output, list) and all(
|
||||
[
|
||||
isinstance(x, dict) and x.get("type") in TOOL_MESSAGE_BLOCK_TYPES
|
||||
isinstance(x, dict) and x.get("type") in recognized_content_block_types
|
||||
for x in output
|
||||
]
|
||||
):
|
||||
@@ -631,7 +629,7 @@ def tools_condition(
|
||||
|
||||
Args:
|
||||
state: The state to check for
|
||||
tool calls. Must have a list of messages (MessageGraph) or have the
|
||||
tool calls. Must have a list of messages or have the
|
||||
"messages" key (StateGraph).
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
in a langchain graph. It applies a pydantic schema to tool_calls in the models' outputs,
|
||||
and returns a ToolMessage with the validated content. If the schema is not valid, it
|
||||
returns a ToolMessage with the error message. The ValidationNode can be used in a
|
||||
StateGraph with a "messages" key or in a MessageGraph. If multiple tool calls are
|
||||
StateGraph with a "messages" key. If multiple tool calls are
|
||||
requested, they will be run in parallel.
|
||||
"""
|
||||
|
||||
@@ -49,7 +49,7 @@ def _default_format_error(
|
||||
class ValidationNode(RunnableCallable):
|
||||
"""A node that validates all tools requests from the last AIMessage.
|
||||
|
||||
It can be used either in StateGraph with a "messages" key or in MessageGraph.
|
||||
It can be used in StateGraph with a "messages" key.
|
||||
|
||||
!!! note
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "langgraph-prebuilt"
|
||||
version = "0.5.2"
|
||||
version = "0.5.1"
|
||||
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
|
||||
authors = []
|
||||
requires-python = ">=3.9"
|
||||
@@ -12,8 +12,8 @@ readme = "README.md"
|
||||
license = "MIT"
|
||||
license-files = ['LICENSE']
|
||||
dependencies = [
|
||||
"langgraph-checkpoint>=2.1.0,<3.0.0",
|
||||
"langchain-core>=0.3.67",
|
||||
"langgraph-checkpoint>=2.1.0",
|
||||
"langchain-core>=0.3.22",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
||||
Generated
+9
-9
@@ -302,7 +302,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langchain-core"
|
||||
version = "0.3.67"
|
||||
version = "0.3.60"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jsonpatch" },
|
||||
@@ -313,14 +313,14 @@ dependencies = [
|
||||
{ name = "tenacity" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c2/40/875af0194024d0006874f061958fa417d3500bbfdc9a57e1bd1c2f4e6ed2/langchain_core-0.3.67.tar.gz", hash = "sha256:2c14aa44a0e78e014e96d7f2f8916ac109d0a0ba87ed67ee25bf7296bed7e7ba", size = 561952, upload-time = "2025-06-30T17:09:35.142Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5b/75/95129aaada92980a002a31e002610a80af3c8967ae7884710372e89cdde0/langchain_core-0.3.60.tar.gz", hash = "sha256:63dd1bdf7939816115399522661ca85a2f3686a61440f2f46ebd86d1b028595b", size = 557456, upload-time = "2025-05-15T15:23:23.642Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/2b/a0d283089c6d08c12d47dca39a55029ff714e939ec04f4560420426ab613/langchain_core-0.3.67-py3-none-any.whl", hash = "sha256:b699f1f24b24fa2747c05e2daa280aa64478a51e01a4e82c7f8e20b6167dfa99", size = 440237, upload-time = "2025-06-30T17:09:33.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/bc/344f5b11fdfe0e27f7064d2e829921a791461dc32e5ed285fe6325518c26/langchain_core-0.3.60-py3-none-any.whl", hash = "sha256:2ccdf06b12e699b1b0962bc02837056c075b4981c3d13f82a4d4c30bb22ea3dc", size = 437890, upload-time = "2025-05-15T15:23:22.278Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.5.1"
|
||||
version = "0.5.0"
|
||||
source = { editable = "../langgraph" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
@@ -464,7 +464,7 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-prebuilt"
|
||||
version = "0.5.2"
|
||||
version = "0.5.1"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
@@ -489,7 +489,7 @@ dev = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "langchain-core", specifier = ">=0.3.67" },
|
||||
{ name = "langchain-core", specifier = ">=0.3.22" },
|
||||
{ name = "langgraph-checkpoint", editable = "../checkpoint" },
|
||||
]
|
||||
|
||||
@@ -537,7 +537,7 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "langsmith"
|
||||
version = "0.4.4"
|
||||
version = "0.3.42"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
@@ -548,9 +548,9 @@ dependencies = [
|
||||
{ name = "requests-toolbelt" },
|
||||
{ name = "zstandard" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/c8/8d2e0fc438d2d3d8d4300f7684ea30a754344ed00d7ba9cc2705241d2a5f/langsmith-0.4.4.tar.gz", hash = "sha256:70c53bbff24a7872e88e6fa0af98270f4986a6e364f9e85db1cc5636defa4d66", size = 352105, upload-time = "2025-06-27T19:20:36.207Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/44/fe171c0b0fb0377b191aebf0b7779e0c7b2a53693c6a01ddad737212495d/langsmith-0.3.42.tar.gz", hash = "sha256:2b5cbc450ab808b992362aac6943bb1d285579aa68a3a8be901d30a393458f25", size = 345619, upload-time = "2025-05-03T03:07:17.873Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/33/a3337eb70d795495a299a1640d7a75f17fb917155a64309b96106e7b9452/langsmith-0.4.4-py3-none-any.whl", hash = "sha256:014c68329bd085bd6c770a6405c61bb6881f82eb554ce8c4d1984b0035fd1716", size = 367687, upload-time = "2025-06-27T19:20:33.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/8e/e8a58e0abaae3f3ac4702e9ca35d1fc6159711556b64ffd0e247771a3f12/langsmith-0.3.42-py3-none-any.whl", hash = "sha256:18114327f3364385dae4026ebfd57d1c1cb46d8f80931098f0f10abe533475ff", size = 360334, upload-time = "2025-05-03T03:07:15.491Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@langchain/langgraph-sdk",
|
||||
"version": "0.0.89",
|
||||
"version": "0.0.87",
|
||||
"description": "Client library for interacting with the LangGraph API",
|
||||
"type": "module",
|
||||
"packageManager": "yarn@1.22.19",
|
||||
@@ -22,9 +22,7 @@
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@langchain/langgraph-api": "~0.0.41",
|
||||
"@langchain/core": "^0.3.61",
|
||||
"@langchain/langgraph": "^0.3.5",
|
||||
"@langchain/core": "^0.3.31",
|
||||
"@langchain/scripts": "^0.1.4",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
@@ -37,7 +35,6 @@
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"concat-md": "^0.5.1",
|
||||
"hono": "^4.8.2",
|
||||
"jsdom": "^26.1.0",
|
||||
"msw": "^2.8.2",
|
||||
"prettier": "^3.2.5",
|
||||
|
||||
@@ -1014,8 +1014,8 @@ export class RunsClient<
|
||||
const stream: ReadableStream<{ event: any; data: any }> = (
|
||||
response.body || new ReadableStream({ start: (ctrl) => ctrl.close() })
|
||||
)
|
||||
.pipeThrough(BytesLineDecoder())
|
||||
.pipeThrough(SSEDecoder());
|
||||
.pipeThrough(new BytesLineDecoder())
|
||||
.pipeThrough(new SSEDecoder());
|
||||
|
||||
yield* IterableReadableStream.fromReadableStream(stream);
|
||||
}
|
||||
@@ -1318,8 +1318,8 @@ export class RunsClient<
|
||||
const stream: ReadableStream<{ event: string; data: any }> = (
|
||||
response.body || new ReadableStream({ start: (ctrl) => ctrl.close() })
|
||||
)
|
||||
.pipeThrough(BytesLineDecoder())
|
||||
.pipeThrough(SSEDecoder());
|
||||
.pipeThrough(new BytesLineDecoder())
|
||||
.pipeThrough(new SSEDecoder());
|
||||
|
||||
yield* IterableReadableStream.fromReadableStream(stream);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import type {
|
||||
} from "../types.stream.js";
|
||||
|
||||
import {
|
||||
type RefObject,
|
||||
type MutableRefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
@@ -316,8 +316,8 @@ function fetchHistory<StateType extends Record<string, unknown>>(
|
||||
function useThreadHistory<StateType extends Record<string, unknown>>(
|
||||
threadId: string | undefined | null,
|
||||
client: Client,
|
||||
clearCallbackRef: RefObject<(() => void) | undefined>,
|
||||
submittingRef: RefObject<boolean>,
|
||||
clearCallbackRef: MutableRefObject<(() => void) | undefined>,
|
||||
submittingRef: MutableRefObject<boolean>,
|
||||
) {
|
||||
const [history, setHistory] = useState<ThreadState<StateType>[]>([]);
|
||||
|
||||
@@ -511,31 +511,6 @@ export interface UseStreamOptions<
|
||||
*/
|
||||
onDebugEvent?: (data: DebugStreamEvent["data"]) => void;
|
||||
|
||||
/**
|
||||
* Callback that is called when the stream is stopped by the user.
|
||||
* Provides a mutate function to update the stream state immediately
|
||||
* without requiring a server roundtrip.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* onStop: ({ mutate }) => {
|
||||
* mutate((prev) => ({
|
||||
* ...prev,
|
||||
* ui: prev.ui?.map(component =>
|
||||
* component.props.isLoading
|
||||
* ? { ...component, props: { ...component.props, stopped: true, isLoading: false }}
|
||||
* : component
|
||||
* )
|
||||
* }));
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
onStop?: (options: {
|
||||
mutate: (
|
||||
update: Partial<StateType> | ((prev: StateType) => Partial<StateType>),
|
||||
) => void;
|
||||
}) => void;
|
||||
|
||||
/**
|
||||
* The ID of the thread to fetch history and current values from.
|
||||
*/
|
||||
@@ -548,17 +523,6 @@ export interface UseStreamOptions<
|
||||
|
||||
/** Will reconnect the stream on mount */
|
||||
reconnectOnMount?: boolean | (() => RunMetadataStorage);
|
||||
|
||||
/**
|
||||
* Initial values to display immediately when loading a thread.
|
||||
* Useful for displaying cached thread data while official history loads.
|
||||
* These values will be replaced when official thread data is fetched.
|
||||
*
|
||||
* Note: UI components from initialValues will render immediately if they're
|
||||
* predefined in LoadExternalComponent's components prop, providing instant
|
||||
* cached UI display without server fetches.
|
||||
*/
|
||||
initialValues?: StateType | null;
|
||||
}
|
||||
|
||||
interface RunMetadataStorage {
|
||||
@@ -657,11 +621,7 @@ export interface UseStream<
|
||||
/**
|
||||
* Join an active stream.
|
||||
*/
|
||||
joinStream: (
|
||||
runId: string,
|
||||
lastEventId?: string,
|
||||
options?: { streamMode?: StreamMode | StreamMode[] },
|
||||
) => Promise<void>;
|
||||
joinStream: (runId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
type ConfigWithConfigurable<ConfigurableType extends Record<string, unknown>> =
|
||||
@@ -692,61 +652,6 @@ interface SubmitOptions<
|
||||
*/
|
||||
streamSubgraphs?: boolean;
|
||||
streamResumable?: boolean;
|
||||
/**
|
||||
* The ID to use when creating a new thread. When provided, this ID will be used
|
||||
* for thread creation when threadId is `null` or `undefined`.
|
||||
* This enables optimistic UI updates where you know the thread ID
|
||||
* before the thread is actually created.
|
||||
*/
|
||||
threadId?: string;
|
||||
}
|
||||
|
||||
function useStreamValuesState<StateType extends Record<string, unknown>>() {
|
||||
type Kind = "stream" | "stop";
|
||||
type Values = StateType | null;
|
||||
type Update = Values | ((prev: Values, kind?: Kind) => Values);
|
||||
type Mutate = Partial<StateType> | ((prev: StateType) => Partial<StateType>);
|
||||
|
||||
const [values, setValues] = useState<[values: StateType, kind: Kind] | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const setStreamValues = useCallback(
|
||||
(values: Update, kind: Kind = "stream") => {
|
||||
if (typeof values === "function") {
|
||||
setValues((prevTuple) => {
|
||||
const [prevValues, prevKind] = prevTuple ?? [null, "stream"];
|
||||
const next = values(prevValues, prevKind);
|
||||
|
||||
if (next == null) return null;
|
||||
return [next, kind] as [StateType, Kind];
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (values == null) setValues(null);
|
||||
setValues([values, kind] as [StateType, Kind]);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const mutate = useCallback(
|
||||
(kind: Kind, serverValues: StateType) => (update: Mutate) => {
|
||||
setStreamValues((clientValues) => {
|
||||
const prev = { ...serverValues, ...clientValues };
|
||||
const next = typeof update === "function" ? update(prev) : update;
|
||||
return { ...prev, ...next };
|
||||
}, kind);
|
||||
},
|
||||
[setStreamValues],
|
||||
);
|
||||
|
||||
return [values?.[0] ?? null, setStreamValues, mutate] as [
|
||||
Values,
|
||||
(update: Update, kind?: Kind) => void,
|
||||
(kind: Kind, serverValues: StateType) => (update: Mutate) => void,
|
||||
];
|
||||
}
|
||||
|
||||
export function useStream<
|
||||
@@ -812,8 +717,7 @@ export function useStream<
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [streamError, setStreamError] = useState<unknown>(undefined);
|
||||
const [streamValues, setStreamValues, getMutateFn] =
|
||||
useStreamValuesState<StateType>();
|
||||
const [streamValues, setStreamValues] = useState<StateType | null>(null);
|
||||
|
||||
const messageManagerRef = useRef(new MessageTupleManager());
|
||||
const submittingRef = useRef(false);
|
||||
@@ -884,9 +788,7 @@ export function useStream<
|
||||
);
|
||||
|
||||
const threadHead: ThreadState<StateType> | undefined = flatHistory.at(-1);
|
||||
const historyValues =
|
||||
threadHead?.values ?? options.initialValues ?? ({} as StateType);
|
||||
|
||||
const historyValues = threadHead?.values ?? ({} as StateType);
|
||||
const historyError = (() => {
|
||||
const error = threadHead?.tasks?.at(-1)?.error;
|
||||
if (error == null) return undefined;
|
||||
@@ -951,8 +853,6 @@ export function useStream<
|
||||
if (runId) client.runs.cancel(threadId, runId);
|
||||
runMetadataStorage.removeItem(`lg:stream:${threadId}`);
|
||||
}
|
||||
|
||||
options?.onStop?.({ mutate: getMutateFn("stop", historyValues) });
|
||||
};
|
||||
|
||||
async function consumeStream(
|
||||
@@ -985,7 +885,15 @@ export function useStream<
|
||||
if (event === "updates") options.onUpdateEvent?.(data);
|
||||
if (event === "custom")
|
||||
options.onCustomEvent?.(data, {
|
||||
mutate: getMutateFn("stream", historyValues),
|
||||
mutate: (update) =>
|
||||
setStreamValues((prev) => {
|
||||
// should not happen
|
||||
if (prev == null) return prev;
|
||||
return {
|
||||
...prev,
|
||||
...(typeof update === "function" ? update(prev) : update),
|
||||
};
|
||||
}),
|
||||
});
|
||||
if (event === "metadata") options.onMetadataEvent?.(data);
|
||||
if (event === "events") options.onLangChainEvent?.(data);
|
||||
@@ -1027,11 +935,8 @@ export function useStream<
|
||||
|
||||
// TODO: stream created checkpoints to avoid an unnecessary network request
|
||||
const result = await run.onSuccess();
|
||||
setStreamValues((values, kind) => {
|
||||
// Do not clear out the user values set on `stop`.
|
||||
if (kind === "stop") return values;
|
||||
return null;
|
||||
});
|
||||
|
||||
setStreamValues(null);
|
||||
if (streamError != null) throw streamError;
|
||||
|
||||
const lastHead = result.at(0);
|
||||
@@ -1057,18 +962,13 @@ export function useStream<
|
||||
}
|
||||
}
|
||||
|
||||
const joinStream = async (
|
||||
runId: string,
|
||||
lastEventId?: string,
|
||||
options?: { streamMode?: StreamMode | StreamMode[] },
|
||||
) => {
|
||||
const joinStream = async (runId: string, lastEventId?: string) => {
|
||||
lastEventId ??= "-1";
|
||||
if (!threadId) return;
|
||||
await consumeStream(async (signal: AbortSignal) => {
|
||||
const stream = client.runs.joinStream(threadId, runId, {
|
||||
signal,
|
||||
lastEventId,
|
||||
streamMode: options?.streamMode,
|
||||
}) as AsyncGenerator<EventStreamEvent>;
|
||||
|
||||
return {
|
||||
@@ -1094,24 +994,26 @@ export function useStream<
|
||||
|
||||
if (newPath != null) setBranch(newPath ?? "");
|
||||
|
||||
// Assumption: we're setting the initial value
|
||||
// Used for instant feedback
|
||||
setStreamValues(() => {
|
||||
const values = { ...historyValues };
|
||||
|
||||
if (submitOptions?.optimisticValues != null) {
|
||||
return {
|
||||
...historyValues,
|
||||
...values,
|
||||
...(typeof submitOptions.optimisticValues === "function"
|
||||
? submitOptions.optimisticValues(historyValues)
|
||||
? submitOptions.optimisticValues(values)
|
||||
: submitOptions.optimisticValues),
|
||||
};
|
||||
}
|
||||
|
||||
return { ...historyValues };
|
||||
return values;
|
||||
});
|
||||
|
||||
let usableThreadId = threadId;
|
||||
if (!usableThreadId) {
|
||||
const thread = await client.threads.create({
|
||||
threadId: submitOptions?.threadId,
|
||||
});
|
||||
const thread = await client.threads.create();
|
||||
onThreadId(thread.thread_id);
|
||||
usableThreadId = thread.thread_id;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ describe("BytesLineDecoder", () => {
|
||||
|
||||
test("handles single line with newline", async () => {
|
||||
const input = createStream([textEncoder.encode("hello\n")]);
|
||||
const decoded = input.pipeThrough(BytesLineDecoder());
|
||||
const decoded = input.pipeThrough(new BytesLineDecoder());
|
||||
const results = await gather(decoded);
|
||||
|
||||
expect(results.length).toBe(1);
|
||||
@@ -29,7 +29,7 @@ describe("BytesLineDecoder", () => {
|
||||
|
||||
test("handles multiple lines", async () => {
|
||||
const input = createStream([textEncoder.encode("line1\nline2\nline3\n")]);
|
||||
const decoded = input.pipeThrough(BytesLineDecoder());
|
||||
const decoded = input.pipeThrough(new BytesLineDecoder());
|
||||
const results = await gather(decoded);
|
||||
|
||||
expect(results.length).toBe(3);
|
||||
@@ -44,7 +44,7 @@ describe("BytesLineDecoder", () => {
|
||||
textEncoder.encode("ne1\nli"),
|
||||
textEncoder.encode("ne2\n"),
|
||||
]);
|
||||
const decoded = input.pipeThrough(BytesLineDecoder());
|
||||
const decoded = input.pipeThrough(new BytesLineDecoder());
|
||||
const results = await gather(decoded);
|
||||
|
||||
expect(results.length).toBe(2);
|
||||
@@ -54,7 +54,7 @@ describe("BytesLineDecoder", () => {
|
||||
|
||||
test("handles CR LF line endings", async () => {
|
||||
const input = createStream([textEncoder.encode("line1\r\nline2\r\n")]);
|
||||
const decoded = input.pipeThrough(BytesLineDecoder());
|
||||
const decoded = input.pipeThrough(new BytesLineDecoder());
|
||||
const results = await gather(decoded);
|
||||
|
||||
expect(results.length).toBe(2);
|
||||
@@ -67,7 +67,7 @@ describe("BytesLineDecoder", () => {
|
||||
textEncoder.encode("line1\r"),
|
||||
textEncoder.encode("\nline2\r\n"),
|
||||
]);
|
||||
const decoded = input.pipeThrough(BytesLineDecoder());
|
||||
const decoded = input.pipeThrough(new BytesLineDecoder());
|
||||
const results = await gather(decoded);
|
||||
|
||||
expect(results.length).toBe(2);
|
||||
@@ -77,7 +77,7 @@ describe("BytesLineDecoder", () => {
|
||||
|
||||
test("handles stale line", async () => {
|
||||
const input = createStream([textEncoder.encode("hello")]);
|
||||
const decoded = input.pipeThrough(BytesLineDecoder());
|
||||
const decoded = input.pipeThrough(new BytesLineDecoder());
|
||||
const results = await gather(decoded);
|
||||
|
||||
expect(results.length).toBe(1);
|
||||
@@ -99,8 +99,8 @@ describe("SSEDecoder", () => {
|
||||
"\n",
|
||||
]);
|
||||
const decoded = input
|
||||
.pipeThrough(BytesLineDecoder())
|
||||
.pipeThrough(SSEDecoder());
|
||||
.pipeThrough(new BytesLineDecoder())
|
||||
.pipeThrough(new SSEDecoder());
|
||||
|
||||
const results = await gather(decoded);
|
||||
expect(results.length).toBe(1);
|
||||
@@ -117,8 +117,8 @@ describe("SSEDecoder", () => {
|
||||
'data: {"message": "hello"}\n',
|
||||
]);
|
||||
const decoded = input
|
||||
.pipeThrough(BytesLineDecoder())
|
||||
.pipeThrough(SSEDecoder());
|
||||
.pipeThrough(new BytesLineDecoder())
|
||||
.pipeThrough(new SSEDecoder());
|
||||
|
||||
const results = await gather(decoded);
|
||||
expect(results.length).toBe(1);
|
||||
@@ -138,8 +138,8 @@ describe("SSEDecoder", () => {
|
||||
"\n",
|
||||
]);
|
||||
const decoded = input
|
||||
.pipeThrough(BytesLineDecoder())
|
||||
.pipeThrough(SSEDecoder());
|
||||
.pipeThrough(new BytesLineDecoder())
|
||||
.pipeThrough(new SSEDecoder());
|
||||
|
||||
const results = await gather(decoded);
|
||||
expect(results.length).toBe(2);
|
||||
@@ -156,8 +156,8 @@ describe("SSEDecoder", () => {
|
||||
test("end event without data", async () => {
|
||||
const input = createStream(["event: test\n"]);
|
||||
const decoded = input
|
||||
.pipeThrough(BytesLineDecoder())
|
||||
.pipeThrough(SSEDecoder());
|
||||
.pipeThrough(new BytesLineDecoder())
|
||||
.pipeThrough(new SSEDecoder());
|
||||
|
||||
const results = await gather(decoded);
|
||||
expect(results.length).toBe(1);
|
||||
@@ -170,8 +170,8 @@ describe("SSEDecoder", () => {
|
||||
test("end event without newline", async () => {
|
||||
const input = createStream(["event: end"]);
|
||||
const decoded = input
|
||||
.pipeThrough(BytesLineDecoder())
|
||||
.pipeThrough(SSEDecoder());
|
||||
.pipeThrough(new BytesLineDecoder())
|
||||
.pipeThrough(new SSEDecoder());
|
||||
|
||||
const results = await gather(decoded);
|
||||
expect(results.length).toBe(1);
|
||||
|
||||
@@ -1,58 +1,14 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { userEvent } from "@testing-library/user-event";
|
||||
import { setupServer } from "msw/node";
|
||||
import { http } from "msw";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { useStream } from "../react/stream.js";
|
||||
import type { Message } from "../types.messages.js";
|
||||
|
||||
import { StateGraph, MessagesAnnotation, START } from "@langchain/langgraph";
|
||||
import { MemorySaver } from "@langchain/langgraph-checkpoint";
|
||||
import { FakeStreamingChatModel } from "@langchain/core/utils/testing";
|
||||
import { AIMessage } from "@langchain/core/messages";
|
||||
import { createEmbedServer } from "@langchain/langgraph-api/experimental/embed";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { useState } from "react";
|
||||
|
||||
const threads = (() => {
|
||||
const THREADS: Record<
|
||||
string,
|
||||
{ thread_id: string; metadata: Record<string, unknown> }
|
||||
> = {};
|
||||
|
||||
return {
|
||||
get: async (id: string) => THREADS[id],
|
||||
put: async (
|
||||
threadId: string,
|
||||
{ metadata }: { metadata?: Record<string, unknown> },
|
||||
) => {
|
||||
THREADS[threadId] = { thread_id: threadId, metadata: metadata ?? {} };
|
||||
},
|
||||
delete: async (threadId: string) => {
|
||||
delete THREADS[threadId];
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
||||
const checkpointer = new MemorySaver();
|
||||
|
||||
const model = new FakeStreamingChatModel({ responses: [new AIMessage("Hey")] });
|
||||
const agent = new StateGraph(MessagesAnnotation)
|
||||
.addNode("agent", async (state: { messages: Message[] }) => {
|
||||
const response = await model.invoke(state.messages);
|
||||
return { messages: [response] };
|
||||
})
|
||||
.addEdge(START, "agent")
|
||||
.compile();
|
||||
|
||||
const app = createEmbedServer({ graph: { agent }, checkpointer, threads });
|
||||
const server = setupServer(http.all("*", (ctx) => app.fetch(ctx.request)));
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
|
||||
function TestChatComponent() {
|
||||
const { messages, isLoading, error, submit, stop } = useStream({
|
||||
assistantId: "agent",
|
||||
assistantId: "test-assistant",
|
||||
apiKey: "test-api-key",
|
||||
});
|
||||
|
||||
@@ -86,6 +42,353 @@ function TestChatComponent() {
|
||||
);
|
||||
}
|
||||
|
||||
// Mock server setup
|
||||
|
||||
const server = setupServer(
|
||||
// Mock thread creation
|
||||
http.post("*/threads", () => {
|
||||
return HttpResponse.json({ thread_id: "test-thread-id" });
|
||||
}),
|
||||
|
||||
// Mock stream endpoint
|
||||
http.post("*/threads/:threadId/runs/stream", async () => {
|
||||
const encoder = new TextEncoder();
|
||||
const sendSSE = (event: string, data: unknown) =>
|
||||
encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("metadata", {
|
||||
run_id: "1f03278a-1734-6518-80a4-3390db59f960",
|
||||
attempt: 1,
|
||||
}),
|
||||
);
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("values", {
|
||||
messages: [
|
||||
{
|
||||
content: "Hey",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {},
|
||||
type: "human",
|
||||
name: null,
|
||||
id: "2d8c0d9f-a614-4e44-b474-6a56e9471cf5",
|
||||
example: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("messages", [
|
||||
{
|
||||
content: "",
|
||||
additional_kwargs: {},
|
||||
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
||||
type: "AIMessageChunk",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
tool_call_chunks: [],
|
||||
},
|
||||
{ run_attempt: 1 },
|
||||
]),
|
||||
);
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("messages", [
|
||||
{
|
||||
content: "Hello",
|
||||
additional_kwargs: {},
|
||||
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
||||
type: "AIMessageChunk",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
tool_call_chunks: [],
|
||||
},
|
||||
{ run_attempt: 1 },
|
||||
]),
|
||||
);
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("messages", [
|
||||
{
|
||||
content: "! How can I assist you today?",
|
||||
additional_kwargs: {},
|
||||
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
||||
type: "AIMessageChunk",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
tool_call_chunks: [],
|
||||
},
|
||||
{ run_attempt: 1 },
|
||||
]),
|
||||
);
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("messages", [
|
||||
{
|
||||
content: "",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {
|
||||
stop_reason: "end_turn",
|
||||
stop_sequence: null,
|
||||
},
|
||||
type: "AIMessageChunk",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
tool_call_chunks: [],
|
||||
},
|
||||
{ run_attempt: 1 },
|
||||
]),
|
||||
);
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("values", {
|
||||
messages: [
|
||||
{
|
||||
content: "Hey",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {},
|
||||
type: "human",
|
||||
name: null,
|
||||
id: "2d8c0d9f-a614-4e44-b474-6a56e9471cf5",
|
||||
example: false,
|
||||
},
|
||||
{
|
||||
content: "Hello! How can I assist you today?",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {
|
||||
model_name: "claude-3-7-sonnet-latest",
|
||||
stop_reason: "end_turn",
|
||||
stop_sequence: null,
|
||||
},
|
||||
type: "ai",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
server.use(
|
||||
http.post("*/threads/:threadId/history", () => {
|
||||
return HttpResponse.json([
|
||||
{
|
||||
values: {
|
||||
messages: [
|
||||
{
|
||||
content: "Hey",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {},
|
||||
type: "human",
|
||||
name: null,
|
||||
id: "2d8c0d9f-a614-4e44-b474-6a56e9471cf5",
|
||||
example: false,
|
||||
},
|
||||
{
|
||||
content: "Hello! How can I assist you today?",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {
|
||||
model_name: "claude-3-7-sonnet-latest",
|
||||
stop_reason: "end_turn",
|
||||
stop_sequence: null,
|
||||
},
|
||||
type: "ai",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
example: false,
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
next: [],
|
||||
tasks: [],
|
||||
metadata: {
|
||||
run_attempt: 1,
|
||||
source: "loop",
|
||||
writes: {
|
||||
agent: {
|
||||
messages: [
|
||||
{
|
||||
content: "Hello! How can I assist you today?",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {
|
||||
model_name: "claude-3-7-sonnet-latest",
|
||||
stop_reason: "end_turn",
|
||||
stop_sequence: null,
|
||||
},
|
||||
type: "ai",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
example: false,
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
step: 1,
|
||||
parents: {},
|
||||
},
|
||||
created_at: "2025-05-16T17:10:16.987537+00:00",
|
||||
checkpoint: {
|
||||
checkpoint_id: "1f03278a-38cf-6c68-8001-22b77ac43ff6",
|
||||
thread_id: "b06fd92a-955c-446e-b233-7977716c4a9c",
|
||||
checkpoint_ns: "",
|
||||
},
|
||||
parent_checkpoint: {
|
||||
checkpoint_id: "1f03278a-206b-67c6-8000-ac34a0872e1a",
|
||||
thread_id: "b06fd92a-955c-446e-b233-7977716c4a9c",
|
||||
checkpoint_ns: "",
|
||||
},
|
||||
checkpoint_id: "1f03278a-38cf-6c68-8001-22b77ac43ff6",
|
||||
parent_checkpoint_id: "1f03278a-206b-67c6-8000-ac34a0872e1a",
|
||||
},
|
||||
{
|
||||
values: {
|
||||
messages: [
|
||||
{
|
||||
content: "Hey",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {},
|
||||
type: "human",
|
||||
name: null,
|
||||
id: "2d8c0d9f-a614-4e44-b474-6a56e9471cf5",
|
||||
example: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
next: ["agent"],
|
||||
tasks: [
|
||||
{
|
||||
id: "e1b7b52b-a78e-4b32-0c89-e06bf46405ed",
|
||||
name: "agent",
|
||||
path: ["__pregel_pull", "agent"],
|
||||
error: null,
|
||||
interrupts: [],
|
||||
checkpoint: null,
|
||||
state: null,
|
||||
result: {
|
||||
messages: [
|
||||
{
|
||||
content: "Hello! How can I assist you today?",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {
|
||||
model_name: "claude-3-7-sonnet-latest",
|
||||
stop_reason: "end_turn",
|
||||
stop_sequence: null,
|
||||
},
|
||||
type: "ai",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
example: false,
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
run_attempt: 1,
|
||||
},
|
||||
created_at: "2025-05-16T17:10:14.429889+00:00",
|
||||
checkpoint: {
|
||||
checkpoint_id: "1f03278a-206b-67c6-8000-ac34a0872e1a",
|
||||
thread_id: "b06fd92a-955c-446e-b233-7977716c4a9c",
|
||||
checkpoint_ns: "",
|
||||
},
|
||||
parent_checkpoint: {
|
||||
checkpoint_id: "1f03278a-2067-6590-bfff-3fb740466fc3",
|
||||
thread_id: "b06fd92a-955c-446e-b233-7977716c4a9c",
|
||||
checkpoint_ns: "",
|
||||
},
|
||||
checkpoint_id: "1f03278a-206b-67c6-8000-ac34a0872e1a",
|
||||
parent_checkpoint_id: "1f03278a-2067-6590-bfff-3fb740466fc3",
|
||||
},
|
||||
{
|
||||
values: {
|
||||
messages: [],
|
||||
},
|
||||
next: ["__start__"],
|
||||
tasks: [
|
||||
{
|
||||
id: "291af033-2ddc-3320-8bbc-28060057cae5",
|
||||
name: "__start__",
|
||||
path: ["__pregel_pull", "__start__"],
|
||||
error: null,
|
||||
interrupts: [],
|
||||
checkpoint: null,
|
||||
state: null,
|
||||
result: {
|
||||
messages: [
|
||||
{
|
||||
id: "2d8c0d9f-a614-4e44-b474-6a56e9471cf5",
|
||||
type: "human",
|
||||
content: "Hey",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
run_attempt: 1,
|
||||
source: "input",
|
||||
writes: {
|
||||
__start__: {
|
||||
messages: [
|
||||
{
|
||||
id: "2d8c0d9f-a614-4e44-b474-6a56e9471cf5",
|
||||
type: "human",
|
||||
content: "Hey",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
step: -1,
|
||||
parents: {},
|
||||
},
|
||||
created_at: "2025-05-16T17:10:14.428191+00:00",
|
||||
checkpoint: {
|
||||
checkpoint_id: "1f03278a-2067-6590-bfff-3fb740466fc3",
|
||||
thread_id: "b06fd92a-955c-446e-b233-7977716c4a9c",
|
||||
checkpoint_ns: "",
|
||||
},
|
||||
parent_checkpoint: null,
|
||||
checkpoint_id: "1f03278a-2067-6590-bfff-3fb740466fc3",
|
||||
parent_checkpoint_id: null,
|
||||
},
|
||||
]);
|
||||
}),
|
||||
);
|
||||
|
||||
return new HttpResponse(stream, {
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
server.use;
|
||||
|
||||
describe("useStream", () => {
|
||||
beforeEach(() => server.listen());
|
||||
|
||||
@@ -114,8 +417,10 @@ describe("useStream", () => {
|
||||
|
||||
// Wait for messages to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("message-0")).toHaveTextContent("Hello");
|
||||
expect(screen.getByTestId("message-1")).toHaveTextContent("Hey");
|
||||
expect(screen.getByTestId("message-0")).toHaveTextContent("Hey");
|
||||
expect(screen.getByTestId("message-1")).toHaveTextContent(
|
||||
"Hello! How can I assist you today?",
|
||||
);
|
||||
});
|
||||
|
||||
// Check final state
|
||||
@@ -135,316 +440,4 @@ describe("useStream", () => {
|
||||
expect(screen.getByTestId("loading")).toHaveTextContent("Not loading");
|
||||
});
|
||||
});
|
||||
|
||||
it("displays initial values immediately and clears them when submitting", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
function TestCachedComponent() {
|
||||
const { messages, values, submit } = useStream<{
|
||||
messages: Message[];
|
||||
}>({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
initialValues: {
|
||||
messages: [
|
||||
{ id: "cached-1", type: "human", content: "Cached user message" },
|
||||
{ id: "cached-2", type: "ai", content: "Cached AI response" },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="messages">
|
||||
{messages.map((msg, i) => (
|
||||
<div
|
||||
key={msg.id ?? i}
|
||||
data-testid={
|
||||
msg.id?.includes("cached")
|
||||
? `message-cached-${i}`
|
||||
: `message-${i}`
|
||||
}
|
||||
>
|
||||
{typeof msg.content === "string"
|
||||
? msg.content
|
||||
: JSON.stringify(msg.content)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div data-testid="values">{JSON.stringify(values)}</div>
|
||||
<button
|
||||
data-testid="submit"
|
||||
onClick={() =>
|
||||
submit({ messages: [{ content: "Hello", type: "human" }] })
|
||||
}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<TestCachedComponent />);
|
||||
|
||||
// Should immediately show cached messages
|
||||
expect(screen.getByTestId("message-cached-0")).toHaveTextContent(
|
||||
"Cached user message",
|
||||
);
|
||||
expect(screen.getByTestId("message-cached-1")).toHaveTextContent(
|
||||
"Cached AI response",
|
||||
);
|
||||
|
||||
// Values should include initial values
|
||||
expect(screen.getByTestId("values")).toHaveTextContent(
|
||||
"Cached user message",
|
||||
);
|
||||
|
||||
// Submitting should clear out the cached messages
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
|
||||
// Wait for messages to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("message-0")).toHaveTextContent("Hello");
|
||||
expect(screen.getByTestId("message-1")).toHaveTextContent("Hey");
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts newThreadId option without errors", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
const spy = vi.fn();
|
||||
const predeterminedThreadId = randomUUID();
|
||||
|
||||
// Test that newThreadId option can be passed without causing errors
|
||||
function TestNewThreadComponent() {
|
||||
const stream = useStream<{ messages: Message[] }>({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
threadId: null, // Start with no thread
|
||||
onThreadId: spy, // Mock callback
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="loading">
|
||||
{stream.isLoading ? "Loading..." : "Not loading"}
|
||||
</div>
|
||||
<div data-testid="thread-id">
|
||||
{stream.client ? "Client ready" : "No client"}
|
||||
</div>
|
||||
<button
|
||||
data-testid="submit"
|
||||
onClick={() =>
|
||||
stream.submit({}, { threadId: predeterminedThreadId })
|
||||
}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<TestNewThreadComponent />);
|
||||
|
||||
// Should render without errors
|
||||
expect(screen.getByTestId("loading")).toHaveTextContent("Not loading");
|
||||
expect(screen.getByTestId("thread-id")).toHaveTextContent("Client ready");
|
||||
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
expect(spy).toHaveBeenCalledWith(predeterminedThreadId);
|
||||
expect(await threads.get(predeterminedThreadId)).toEqual({
|
||||
thread_id: predeterminedThreadId,
|
||||
metadata: {
|
||||
graph_id: "agent",
|
||||
assistant_id: "agent",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("onStop callback is called when stop is called", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onStopCallback = vi.fn();
|
||||
|
||||
function TestComponent() {
|
||||
const { submit, stop } = useStream({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
onStop: onStopCallback,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button data-testid="submit" onClick={() => submit({})}>
|
||||
Send
|
||||
</button>
|
||||
<button data-testid="stop" onClick={stop}>
|
||||
Stop
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<TestComponent />);
|
||||
|
||||
// Start a stream and stop it
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
await user.click(screen.getByTestId("stop"));
|
||||
|
||||
// Verify onStop was called with mutate function
|
||||
expect(onStopCallback).toHaveBeenCalledTimes(1);
|
||||
expect(onStopCallback).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mutate: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("onStop mutate function updates stream values immediately", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
function TestComponent() {
|
||||
const [stopped, setStopped] = useState(false);
|
||||
const { submit, stop, messages } = useStream<{ messages: Message[] }>({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
onStop: ({ mutate }) => {
|
||||
setStopped(true);
|
||||
mutate((prev) => ({
|
||||
...prev,
|
||||
messages: [
|
||||
...(prev.messages ?? []),
|
||||
{ type: "ai", content: "Stream stopped" },
|
||||
],
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="stopped-status">
|
||||
{stopped ? "Stopped" : "Not stopped"}
|
||||
</div>
|
||||
<div data-testid="messages">
|
||||
{messages.map((msg, i) => (
|
||||
<div key={msg.id ?? i} data-testid={`message-${i}`}>
|
||||
{typeof msg.content === "string"
|
||||
? msg.content
|
||||
: JSON.stringify(msg.content)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button data-testid="submit" onClick={() => submit({})}>
|
||||
Send
|
||||
</button>
|
||||
<button data-testid="stop" onClick={stop}>
|
||||
Stop
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<TestComponent />);
|
||||
|
||||
// Initial state
|
||||
expect(screen.getByTestId("stopped-status")).toHaveTextContent(
|
||||
"Not stopped",
|
||||
);
|
||||
|
||||
// Start and stop stream
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
await user.click(screen.getByTestId("stop"));
|
||||
|
||||
// Verify state was updated immediately
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("stopped-status")).toHaveTextContent("Stopped");
|
||||
expect(screen.getByTestId("message-0")).toHaveTextContent(
|
||||
"Stream stopped",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("onStop handles functional updates correctly", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
function TestComponent() {
|
||||
const { submit, stop, values } = useStream({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
initialValues: {
|
||||
counter: 5,
|
||||
items: ["item1", "item2"],
|
||||
},
|
||||
onStop: ({ mutate }) => {
|
||||
mutate((prev: any) => ({
|
||||
...prev,
|
||||
counter: (prev.counter || 0) + 10,
|
||||
items: [...(prev.items || []), "stopped"],
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="counter">{(values as any).counter}</div>
|
||||
<div data-testid="items">{(values as any).items?.join(", ")}</div>
|
||||
<button data-testid="submit" onClick={() => submit({})}>
|
||||
Send
|
||||
</button>
|
||||
<button data-testid="stop" onClick={stop}>
|
||||
Stop
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<TestComponent />);
|
||||
|
||||
// Initial state
|
||||
expect(screen.getByTestId("counter")).toHaveTextContent("5");
|
||||
expect(screen.getByTestId("items")).toHaveTextContent("item1, item2");
|
||||
|
||||
// Start and stop stream
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
await user.click(screen.getByTestId("stop"));
|
||||
|
||||
// Verify functional update was applied correctly
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("counter")).toHaveTextContent("15");
|
||||
expect(screen.getByTestId("items")).toHaveTextContent(
|
||||
"item1, item2, stopped",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("onStop is not called when stream completes naturally", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
const onStopCallback = vi.fn();
|
||||
|
||||
function TestComponent() {
|
||||
const { submit } = useStream({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
onStop: onStopCallback,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button data-testid="submit" onClick={() => submit({})}>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<TestComponent />);
|
||||
|
||||
// Start a stream and let it complete naturally
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
|
||||
// Wait for stream to complete naturally
|
||||
await waitFor(() => {
|
||||
expect(onStopCallback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,22 +13,16 @@ type MessageContent = string | MessageContentComplex[];
|
||||
*/
|
||||
type MessageAdditionalKwargs = Record<string, unknown>;
|
||||
|
||||
type BaseMessage = {
|
||||
additional_kwargs?: MessageAdditionalKwargs | undefined;
|
||||
content: MessageContent;
|
||||
id?: string | undefined;
|
||||
name?: string | undefined;
|
||||
response_metadata?: Record<string, unknown> | undefined;
|
||||
};
|
||||
|
||||
export type HumanMessage = BaseMessage & {
|
||||
export type HumanMessage = {
|
||||
type: "human";
|
||||
example?: boolean | undefined;
|
||||
id?: string | undefined;
|
||||
content: MessageContent;
|
||||
};
|
||||
|
||||
export type AIMessage = BaseMessage & {
|
||||
export type AIMessage = {
|
||||
type: "ai";
|
||||
example?: boolean | undefined;
|
||||
id?: string | undefined;
|
||||
content: MessageContent;
|
||||
tool_calls?:
|
||||
| {
|
||||
name: string;
|
||||
@@ -63,12 +57,19 @@ export type AIMessage = BaseMessage & {
|
||||
| undefined;
|
||||
}
|
||||
| undefined;
|
||||
additional_kwargs?: MessageAdditionalKwargs | undefined;
|
||||
response_metadata?: Record<string, unknown> | undefined;
|
||||
};
|
||||
|
||||
export type ToolMessage = BaseMessage & {
|
||||
export type ToolMessage = {
|
||||
type: "tool";
|
||||
name?: string | undefined;
|
||||
id?: string | undefined;
|
||||
content: MessageContent;
|
||||
status?: "error" | "success" | undefined;
|
||||
tool_call_id: string;
|
||||
additional_kwargs?: MessageAdditionalKwargs | undefined;
|
||||
response_metadata?: Record<string, unknown> | undefined;
|
||||
/**
|
||||
* Artifact of the Tool execution which is not meant to be sent to the model.
|
||||
*
|
||||
@@ -80,16 +81,22 @@ export type ToolMessage = BaseMessage & {
|
||||
artifact?: any;
|
||||
};
|
||||
|
||||
export type SystemMessage = BaseMessage & {
|
||||
export type SystemMessage = {
|
||||
type: "system";
|
||||
id?: string | undefined;
|
||||
content: MessageContent;
|
||||
};
|
||||
|
||||
export type FunctionMessage = BaseMessage & {
|
||||
export type FunctionMessage = {
|
||||
type: "function";
|
||||
id?: string | undefined;
|
||||
content: MessageContent;
|
||||
};
|
||||
|
||||
export type RemoveMessage = BaseMessage & {
|
||||
export type RemoveMessage = {
|
||||
type: "remove";
|
||||
id: string;
|
||||
content: MessageContent;
|
||||
};
|
||||
|
||||
export type Message =
|
||||
|
||||
+132
-128
@@ -6,88 +6,90 @@ const SPACE = " ".charCodeAt(0);
|
||||
|
||||
const TRAILING_NEWLINE = [CR, LF];
|
||||
|
||||
export function BytesLineDecoder() {
|
||||
let buffer: Uint8Array[] = [];
|
||||
let trailingCr = false;
|
||||
export class BytesLineDecoder extends TransformStream<Uint8Array, Uint8Array> {
|
||||
constructor() {
|
||||
let buffer: Uint8Array[] = [];
|
||||
let trailingCr = false;
|
||||
|
||||
return new TransformStream<Uint8Array, Uint8Array>({
|
||||
start() {
|
||||
buffer = [];
|
||||
trailingCr = false;
|
||||
},
|
||||
|
||||
transform(chunk, controller) {
|
||||
// See https://docs.python.org/3/glossary.html#term-universal-newlines
|
||||
let text = chunk;
|
||||
|
||||
// Handle trailing CR from previous chunk
|
||||
if (trailingCr) {
|
||||
text = joinArrays([[CR], text]);
|
||||
trailingCr = false;
|
||||
}
|
||||
|
||||
// Check for trailing CR in current chunk
|
||||
if (text.length > 0 && text.at(-1) === CR) {
|
||||
trailingCr = true;
|
||||
text = text.subarray(0, -1);
|
||||
}
|
||||
|
||||
if (!text.length) return;
|
||||
const trailingNewline = TRAILING_NEWLINE.includes(text.at(-1)!);
|
||||
|
||||
const lastIdx = text.length - 1;
|
||||
const { lines } = text.reduce<{ lines: Uint8Array[]; from: number }>(
|
||||
(acc, cur, idx) => {
|
||||
if (acc.from > idx) return acc;
|
||||
|
||||
if (cur === CR || cur === LF) {
|
||||
acc.lines.push(text.subarray(acc.from, idx));
|
||||
if (cur === CR && text[idx + 1] === LF) {
|
||||
acc.from = idx + 2;
|
||||
} else {
|
||||
acc.from = idx + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx === lastIdx && acc.from <= lastIdx) {
|
||||
acc.lines.push(text.subarray(acc.from));
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ lines: [], from: 0 },
|
||||
);
|
||||
|
||||
if (lines.length === 1 && !trailingNewline) {
|
||||
buffer.push(lines[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.length) {
|
||||
// Include existing buffer in first line
|
||||
buffer.push(lines[0]);
|
||||
lines[0] = joinArrays(buffer);
|
||||
super({
|
||||
start() {
|
||||
buffer = [];
|
||||
}
|
||||
trailingCr = false;
|
||||
},
|
||||
|
||||
if (!trailingNewline) {
|
||||
// If the last segment is not newline terminated,
|
||||
// buffer it for the next chunk
|
||||
if (lines.length) buffer = [lines.pop()!];
|
||||
}
|
||||
transform(chunk, controller) {
|
||||
// See https://docs.python.org/3/glossary.html#term-universal-newlines
|
||||
let text = chunk;
|
||||
|
||||
// Enqueue complete lines
|
||||
for (const line of lines) {
|
||||
controller.enqueue(line);
|
||||
}
|
||||
},
|
||||
// Handle trailing CR from previous chunk
|
||||
if (trailingCr) {
|
||||
text = joinArrays([[CR], text]);
|
||||
trailingCr = false;
|
||||
}
|
||||
|
||||
flush(controller) {
|
||||
if (buffer.length) {
|
||||
controller.enqueue(joinArrays(buffer));
|
||||
}
|
||||
},
|
||||
});
|
||||
// Check for trailing CR in current chunk
|
||||
if (text.length > 0 && text.at(-1) === CR) {
|
||||
trailingCr = true;
|
||||
text = text.subarray(0, -1);
|
||||
}
|
||||
|
||||
if (!text.length) return;
|
||||
const trailingNewline = TRAILING_NEWLINE.includes(text.at(-1)!);
|
||||
|
||||
const lastIdx = text.length - 1;
|
||||
const { lines } = text.reduce<{ lines: Uint8Array[]; from: number }>(
|
||||
(acc, cur, idx) => {
|
||||
if (acc.from > idx) return acc;
|
||||
|
||||
if (cur === CR || cur === LF) {
|
||||
acc.lines.push(text.subarray(acc.from, idx));
|
||||
if (cur === CR && text[idx + 1] === LF) {
|
||||
acc.from = idx + 2;
|
||||
} else {
|
||||
acc.from = idx + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx === lastIdx && acc.from <= lastIdx) {
|
||||
acc.lines.push(text.subarray(acc.from));
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ lines: [], from: 0 },
|
||||
);
|
||||
|
||||
if (lines.length === 1 && !trailingNewline) {
|
||||
buffer.push(lines[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.length) {
|
||||
// Include existing buffer in first line
|
||||
buffer.push(lines[0]);
|
||||
lines[0] = joinArrays(buffer);
|
||||
buffer = [];
|
||||
}
|
||||
|
||||
if (!trailingNewline) {
|
||||
// If the last segment is not newline terminated,
|
||||
// buffer it for the next chunk
|
||||
if (lines.length) buffer = [lines.pop()!];
|
||||
}
|
||||
|
||||
// Enqueue complete lines
|
||||
for (const line of lines) {
|
||||
controller.enqueue(line);
|
||||
}
|
||||
},
|
||||
|
||||
flush(controller) {
|
||||
if (buffer.length) {
|
||||
controller.enqueue(joinArrays(buffer));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface StreamPart {
|
||||
@@ -96,67 +98,69 @@ interface StreamPart {
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
export function SSEDecoder() {
|
||||
let event = "";
|
||||
let data: Uint8Array[] = [];
|
||||
let lastEventId = "";
|
||||
let retry: number | null = null;
|
||||
export class SSEDecoder extends TransformStream<Uint8Array, StreamPart> {
|
||||
constructor() {
|
||||
let event = "";
|
||||
let data: Uint8Array[] = [];
|
||||
let lastEventId = "";
|
||||
let retry: number | null = null;
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
return new TransformStream<Uint8Array, StreamPart>({
|
||||
transform(chunk, controller) {
|
||||
// Handle empty line case
|
||||
if (!chunk.length) {
|
||||
if (!event && !data.length && !lastEventId && retry == null) return;
|
||||
super({
|
||||
transform(chunk, controller) {
|
||||
// Handle empty line case
|
||||
if (!chunk.length) {
|
||||
if (!event && !data.length && !lastEventId && retry == null) return;
|
||||
|
||||
const sse = {
|
||||
id: lastEventId || undefined,
|
||||
event,
|
||||
data: data.length ? decodeArraysToJson(decoder, data) : null,
|
||||
};
|
||||
const sse = {
|
||||
id: lastEventId || undefined,
|
||||
event,
|
||||
data: data.length ? decodeArraysToJson(decoder, data) : null,
|
||||
};
|
||||
|
||||
// NOTE: as per the SSE spec, do not reset lastEventId
|
||||
event = "";
|
||||
data = [];
|
||||
retry = null;
|
||||
// NOTE: as per the SSE spec, do not reset lastEventId
|
||||
event = "";
|
||||
data = [];
|
||||
retry = null;
|
||||
|
||||
controller.enqueue(sse);
|
||||
return;
|
||||
}
|
||||
controller.enqueue(sse);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore comments
|
||||
if (chunk[0] === COLON) return;
|
||||
// Ignore comments
|
||||
if (chunk[0] === COLON) return;
|
||||
|
||||
const sepIdx = chunk.indexOf(COLON);
|
||||
if (sepIdx === -1) return;
|
||||
const sepIdx = chunk.indexOf(COLON);
|
||||
if (sepIdx === -1) return;
|
||||
|
||||
const fieldName = decoder.decode(chunk.subarray(0, sepIdx));
|
||||
let value = chunk.subarray(sepIdx + 1);
|
||||
if (value[0] === SPACE) value = value.subarray(1);
|
||||
const fieldName = decoder.decode(chunk.subarray(0, sepIdx));
|
||||
let value = chunk.subarray(sepIdx + 1);
|
||||
if (value[0] === SPACE) value = value.subarray(1);
|
||||
|
||||
if (fieldName === "event") {
|
||||
event = decoder.decode(value);
|
||||
} else if (fieldName === "data") {
|
||||
data.push(value);
|
||||
} else if (fieldName === "id") {
|
||||
if (value.indexOf(NULL) === -1) lastEventId = decoder.decode(value);
|
||||
} else if (fieldName === "retry") {
|
||||
const retryNum = Number.parseInt(decoder.decode(value));
|
||||
if (!Number.isNaN(retryNum)) retry = retryNum;
|
||||
}
|
||||
},
|
||||
if (fieldName === "event") {
|
||||
event = decoder.decode(value);
|
||||
} else if (fieldName === "data") {
|
||||
data.push(value);
|
||||
} else if (fieldName === "id") {
|
||||
if (value.indexOf(NULL) === -1) lastEventId = decoder.decode(value);
|
||||
} else if (fieldName === "retry") {
|
||||
const retryNum = Number.parseInt(decoder.decode(value));
|
||||
if (!Number.isNaN(retryNum)) retry = retryNum;
|
||||
}
|
||||
},
|
||||
|
||||
flush(controller) {
|
||||
if (event) {
|
||||
controller.enqueue({
|
||||
id: lastEventId || undefined,
|
||||
event,
|
||||
data: data.length ? decodeArraysToJson(decoder, data) : null,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
flush(controller) {
|
||||
if (event) {
|
||||
controller.enqueue({
|
||||
id: lastEventId || undefined,
|
||||
event,
|
||||
data: data.length ? decodeArraysToJson(decoder, data) : null,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function joinArrays(data: ArrayLike<number>[]) {
|
||||
|
||||
+22
-1046
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user