From e3d1f2d43962126064bca05c8ad7c53b3572a690 Mon Sep 17 00:00:00 2001 From: Dimitri Tombroff Date: Fri, 24 Jul 2026 08:00:09 +0200 Subject: [PATCH 1/3] feat(hello_graph): surface the classify routing decision in the Thought panel classify_step now wraps its greeting/question decision in context.thinking("planning", ...) instead of relying on emit_status alone (never persisted or shown in the UI). Developers and testers can see why a message was routed to greet vs. answer without reading server logs. --- .../hello_graph/graph_steps.py | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 agents/fred_samples_agents/hello_graph/graph_steps.py diff --git a/agents/fred_samples_agents/hello_graph/graph_steps.py b/agents/fred_samples_agents/hello_graph/graph_steps.py new file mode 100644 index 0000000..048a0f6 --- /dev/null +++ b/agents/fred_samples_agents/hello_graph/graph_steps.py @@ -0,0 +1,191 @@ +# Copyright Thales 2026 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Business steps for the Hello Graph sample agent. + +Read this file to understand the smallest useful v2 graph agent: +- classify_step: one structured model call decides which branch to take + (intent_router_step) +- greet_step / answer_step: one more model call per branch produces the + user-facing text (model_text_step) +- finalize_step: the standard terminal node + +No MCP server and no human-in-the-loop gate — see bank_transfer or +postal_tracking for those, once this shape is familiar. +""" + +from __future__ import annotations + +from typing import Literal + +from fred_sdk import ( + GraphNodeContext, + GraphNodeResult, + StepResult, + intent_router_step, + model_text_step, + typed_node, +) +from fred_sdk import ( + finalize_step as _finalize_step, +) +from pydantic import BaseModel, Field + +from .graph_state import HelloGraphState + +# ── System prompts ───────────────────────────────────────────────────────────── + +_CLASSIFY_SYSTEM_PROMPT = """\ +Classify the user message as one of: +- "greeting": hellos, small talk, or asking what this agent can do +- "question": anything else the user wants an answer to +""" + +_GREET_SYSTEM_PROMPT = """\ +You are a friendly assistant saying hello for the first time. +Reply in one short, warm sentence and mention that you can answer questions. +""" + +_ANSWER_SYSTEM_PROMPT = """\ +You are a helpful, concise assistant. +Answer the user's question clearly in a few sentences. +""" + + +# ── Route model ─────────────────────────────────────────────────────────────── + + +class MessageKind(BaseModel): + """Structured classification produced by classify_step.""" + + kind: Literal["greeting", "question"] = Field( + description="'greeting' for hellos/small talk, 'question' for everything else." + ) + + +# ── Step: classify ───────────────────────────────────────────────────────────── + + +@typed_node(HelloGraphState) +async def classify_step( + state: HelloGraphState, + context: GraphNodeContext, +) -> StepResult: + """ + Classify the user message as a greeting or a question. + + Why this exists: + - shows the smallest routing pattern in a graph agent: one structured model + call decides which node runs next + - wraps the decision in a `context.thinking()` block so the routing choice + is visible in the frontend's "Thought" trace panel — `emit_status` alone + is a fire-and-forget signal that is never persisted or shown in the UI + + How to use: + - place as the entry node; declare "greeting" and "question" routes after it + """ + context.emit_status("classify", "Reading your message.") + async with context.thinking("planning", title="Classifying the message") as thought: + await thought.write(f"User said: {state.latest_user_text!r}") + result = await intent_router_step( + context, + operation="classify_message", + route_model=MessageKind, + system_prompt=_CLASSIFY_SYSTEM_PROMPT, + user_prompt=state.latest_user_text, + fallback_output={"kind": "question"}, + route_field="kind", + state_update_builder=lambda d: {"kind": d.kind}, + ) + next_step = "greet" if result.route_key == "greeting" else "answer" + await thought.conclude(f"Classified as '{result.route_key}' — routing to {next_step}.") + return result + + +# ── Step: greet ───────────────────────────────────────────────────────────────── + + +@typed_node(HelloGraphState) +async def greet_step( + state: HelloGraphState, + context: GraphNodeContext, +) -> StepResult: + """ + Answer a greeting with one short, friendly line. + + How to use: + - place on the "greeting" branch with a direct edge to "finalize" + """ + context.emit_status("greet", "Saying hello.") + response = await model_text_step( + context, + operation="greet", + system_prompt=_GREET_SYSTEM_PROMPT, + user_prompt=state.latest_user_text, + fallback_text="Hello! Ask me anything.", + ) + return StepResult(state_update={"final_text": response, "done_reason": "greeted"}) + + +# ── Step: answer ───────────────────────────────────────────────────────────────── + + +@typed_node(HelloGraphState) +async def answer_step( + state: HelloGraphState, + context: GraphNodeContext, +) -> StepResult: + """ + Answer the user's question with one model call. + + How to use: + - place on the "question" branch with a direct edge to "finalize" + """ + context.emit_status("answer", "Thinking.") + response = await model_text_step( + context, + operation="answer_question", + system_prompt=_ANSWER_SYSTEM_PROMPT, + user_prompt=state.latest_user_text, + fallback_text="I couldn't come up with an answer just now — try rephrasing?", + ) + return StepResult(state_update={"final_text": response, "done_reason": "answered"}) + + +# ── Step: finalize ──────────────────────────────────────────────────────────── + + +@typed_node(HelloGraphState) +async def finalize_step( + state: HelloGraphState, + context: GraphNodeContext, +) -> GraphNodeResult: + """ + Terminal step — keep existing final_text or set a generic fallback. + + How to use: + - register as the "finalize" node; both branches route here + """ + return _finalize_step( + final_text=state.final_text + or ( + f"An unexpected error occurred: {state.node_error}" + if state.node_error + else None + ), + fallback_text="Hi! Say hello or ask me a question.", + done_reason=state.done_reason + or ("infrastructure_error" if state.node_error else None), + ) From 482acd25d11f70ddf36cf23262be4d3d7dd1278f Mon Sep 17 00:00:00 2001 From: Dimitri Tombroff Date: Fri, 24 Jul 2026 08:00:20 +0200 Subject: [PATCH 2/3] feat(team_of_3): drop [ROUTED:*] text markers, rely on the coordinator's thought MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The router's coordinator now emits a "Choosing a specialist" thought (fred-sdk 3.4.0, AGENT-THINKING-API-RFC.md Amendment C) that names the chosen member — the [ROUTED:GRAPH]/[ROUTED:REACT_1]/[ROUTED:REACT_2] markers baked into each child's prompt/output were only ever there to let a human verify routing, and they leaked into the visible answer text. Remove them from all three children; the graph child's own deterministic classify_request_step also gets a thought block narrating its keyword decision. Update README_AGENT.md / README_CLI.md to check the thought conclusion instead of the marker. Requires fred-sdk>=3.4.0 (local editable install via `make dev-local` until fred-sdk 3.4.0 is published). --- .../team_of_3_agents_sample/README_AGENT.md | 19 ++++-- .../team_of_3_agents_sample/README_CLI.md | 18 +++--- .../graph_agent/graph_steps.py | 61 +++++++++---------- .../react_agent_one/react_agent.py | 4 +- .../react_agent_two/react_agent.py | 2 - 5 files changed, 55 insertions(+), 49 deletions(-) diff --git a/agents/fred_samples_agents/team_of_3_agents_sample/README_AGENT.md b/agents/fred_samples_agents/team_of_3_agents_sample/README_AGENT.md index fd939e2..7223fd3 100644 --- a/agents/fred_samples_agents/team_of_3_agents_sample/README_AGENT.md +++ b/agents/fred_samples_agents/team_of_3_agents_sample/README_AGENT.md @@ -58,29 +58,36 @@ Inside `fred-agents-cli`, select: ## 5. Exactly 3 routing tests +Each test is verified by the coordinator's own "Choosing a specialist" thought +(the "Thought…" panel above the reply in the chat UI, or the `thought`-channel +message in `/history`) rather than a text marker in the answer — the +coordinator's routing decision is now observable on its own, per +`AGENT-THINKING-API-RFC.md` Amendment C. The reply text itself is a clean +answer with no routing artifact. + ### Test 1 (should route to graph child) - Prompt: `Please approve this expense request for 120 EUR.` - Expected child: `fred.samples.team_of_3.graph_child` -- Expected marker in response: - `[ROUTED:GRAPH]` +- Expected thought conclusion: + `Routing to Graph Approval Specialist.` ### Test 2 (should route to ReAct agent 1) - Prompt: `Convert 2.5 km to meters and add 120.` - Expected child: `fred.samples.team_of_3.react_math` -- Expected marker in response: - `[ROUTED:REACT_1]` +- Expected thought conclusion: + `Routing to Math Conversion Specialist.` ### Test 3 (should route to ReAct agent 2) - Prompt: `Rewrite this sentence in plain English: The rollout was postponed due to environmental contingencies.` - Expected child: `fred.samples.team_of_3.react_writer` -- Expected marker in response: - `[ROUTED:REACT_2]` +- Expected thought conclusion: + `Routing to Writing Specialist.` ## 6. Routing analysis (verified from installed packages) diff --git a/agents/fred_samples_agents/team_of_3_agents_sample/README_CLI.md b/agents/fred_samples_agents/team_of_3_agents_sample/README_CLI.md index 4894343..f3453a2 100644 --- a/agents/fred_samples_agents/team_of_3_agents_sample/README_CLI.md +++ b/agents/fred_samples_agents/team_of_3_agents_sample/README_CLI.md @@ -40,33 +40,37 @@ Test A (graph): .venv/bin/fred-agents-cli \ --base-url http://127.0.0.1:8010/samples/agents/v1 \ --agent fred.samples.team_of_3.router \ + --verbose --stream \ "Please approve this expense request for 120 EUR." ``` -Expected marker: `[ROUTED:GRAPH]` +Expected thought conclusion: `Routing to Graph Approval Specialist.` Test B (react_1): ```bash .venv/bin/fred-agents-cli \ --base-url http://127.0.0.1:8010/samples/agents/v1 \ --agent fred.samples.team_of_3.router \ + --verbose --stream \ "Convert 2.5 km to meters and add 120." ``` -Expected marker: `[ROUTED:REACT_1]` +Expected thought conclusion: `Routing to Math Conversion Specialist.` Test C (react_2): ```bash .venv/bin/fred-agents-cli \ --base-url http://127.0.0.1:8010/samples/agents/v1 \ --agent fred.samples.team_of_3.router \ + --verbose --stream \ "Rewrite this sentence in plain English: The rollout was postponed due to environmental contingencies." ``` -Expected marker: `[ROUTED:REACT_2]` +Expected thought conclusion: `Routing to Writing Specialist.` ## 5. How to identify which child agent handled the request -Each child emits a stable marker: -- graph child: `[ROUTED:GRAPH]` -- react child 1: `[ROUTED:REACT_1]` -- react child 2: `[ROUTED:REACT_2]` +The coordinator's own routing decision is a `thought`-channel event +(`phase="planning"`, `title="Choosing a specialist"`) with a `conclusion` +field naming the chosen member — visible in the chat UI's "Thought…" panel +and, from the CLI, in `--verbose --stream` output. Child replies carry no +routing marker; see `AGENT-THINKING-API-RFC.md` Amendment C. ## 6. Capturing routing evidence/logs Use chat client verbose/stream flags: diff --git a/agents/fred_samples_agents/team_of_3_agents_sample/graph_agent/graph_steps.py b/agents/fred_samples_agents/team_of_3_agents_sample/graph_agent/graph_steps.py index 30bc309..34f3677 100644 --- a/agents/fred_samples_agents/team_of_3_agents_sample/graph_agent/graph_steps.py +++ b/agents/fred_samples_agents/team_of_3_agents_sample/graph_agent/graph_steps.py @@ -2,8 +2,6 @@ from .graph_state import Team3GraphState -ROUTED_MARKER = "[ROUTED:GRAPH]" - def _normalize(text: str) -> str: return text.lower().strip() @@ -17,32 +15,38 @@ async def classify_request_step( context.emit_status("classify", detail="deterministic keyword classifier") text = _normalize(state.latest_user_text) - if any(token in text for token in ("approve", "approved", "allow", "accept")): - return StepResult( - state_update={ - "decision": "approved", - "reason": "approval keyword found", - }, - route_key="approved", - ) + async with context.thinking("planning", title="Classifying the request") as thought: + await thought.write(f"Message: {state.latest_user_text!r}") + + if any(token in text for token in ("approve", "approved", "allow", "accept")): + await thought.conclude("Approval keyword found — decision: APPROVED.") + return StepResult( + state_update={ + "decision": "approved", + "reason": "approval keyword found", + }, + route_key="approved", + ) + + if any(token in text for token in ("reject", "rejected", "deny", "decline")): + await thought.conclude("Rejection keyword found — decision: REJECTED.") + return StepResult( + state_update={ + "decision": "rejected", + "reason": "rejection keyword found", + }, + route_key="rejected", + ) - if any(token in text for token in ("reject", "rejected", "deny", "decline")): + await thought.conclude("No approval/rejection keyword — decision: NEEDS_REVIEW.") return StepResult( state_update={ - "decision": "rejected", - "reason": "rejection keyword found", + "decision": "needs_review", + "reason": "no approval/rejection keyword found", }, - route_key="rejected", + route_key="needs_review", ) - return StepResult( - state_update={ - "decision": "needs_review", - "reason": "no approval/rejection keyword found", - }, - route_key="needs_review", - ) - @typed_node(Team3GraphState) async def approved_step(state: Team3GraphState, context: GraphNodeContext) -> StepResult: @@ -50,9 +54,7 @@ async def approved_step(state: Team3GraphState, context: GraphNodeContext) -> St return StepResult( state_update={ "final_text": ( - f"{ROUTED_MARKER} Decision: APPROVED.\n" - f"Reason: {state.reason}.\n" - "This path is deterministic." + f"Decision: APPROVED.\nReason: {state.reason}.\nThis path is deterministic." ) } ) @@ -64,9 +66,7 @@ async def rejected_step(state: Team3GraphState, context: GraphNodeContext) -> St return StepResult( state_update={ "final_text": ( - f"{ROUTED_MARKER} Decision: REJECTED.\n" - f"Reason: {state.reason}.\n" - "This path is deterministic." + f"Decision: REJECTED.\nReason: {state.reason}.\nThis path is deterministic." ) } ) @@ -80,7 +80,7 @@ async def needs_review_step( return StepResult( state_update={ "final_text": ( - f"{ROUTED_MARKER} Decision: NEEDS_REVIEW.\n" + f"Decision: NEEDS_REVIEW.\n" f"Reason: {state.reason}.\n" "Include an explicit approve/reject keyword to choose a branch." ) @@ -95,6 +95,5 @@ async def finalize_graph_step( del context return finalize_step( final_text=state.final_text, - fallback_text=f"{ROUTED_MARKER} No result available.", + fallback_text="No result available.", ) - diff --git a/agents/fred_samples_agents/team_of_3_agents_sample/react_agent_one/react_agent.py b/agents/fred_samples_agents/team_of_3_agents_sample/react_agent_one/react_agent.py index 4345c17..b0c2dc4 100644 --- a/agents/fred_samples_agents/team_of_3_agents_sample/react_agent_one/react_agent.py +++ b/agents/fred_samples_agents/team_of_3_agents_sample/react_agent_one/react_agent.py @@ -12,14 +12,12 @@ class Team3ReActMathAgent(ReActAgent): tags: tuple[str, ...] = ("sample", "team", "react", "math", "conversion") system_prompt_template: str = """\ You are a precise arithmetic and unit-conversion helper. -Always start your answer with this exact marker on its own line: -[ROUTED:REACT_1] Scope: - arithmetic (add, subtract, multiply, divide, percentages) - simple metric conversions (mm/cm/m/km and g/kg) -If a request is outside this scope, say it briefly and still include the marker. +If a request is outside this scope, say so briefly. """ diff --git a/agents/fred_samples_agents/team_of_3_agents_sample/react_agent_two/react_agent.py b/agents/fred_samples_agents/team_of_3_agents_sample/react_agent_two/react_agent.py index e7e8a1e..3bcf8e4 100644 --- a/agents/fred_samples_agents/team_of_3_agents_sample/react_agent_two/react_agent.py +++ b/agents/fred_samples_agents/team_of_3_agents_sample/react_agent_two/react_agent.py @@ -12,8 +12,6 @@ class Team3ReActWriterAgent(ReActAgent): tags: tuple[str, ...] = ("sample", "team", "react", "writing", "summary") system_prompt_template: str = """\ You are a concise writing helper for rewrite/summarize/glossary tasks. -Always start your answer with this exact marker on its own line: -[ROUTED:REACT_2] Scope: - rewrite text in a clearer style From 8b1e89d798df6507a7439643964292a863f9e03b Mon Sep 17 00:00:00 2001 From: Dimitri Tombroff Date: Fri, 24 Jul 2026 08:19:01 +0200 Subject: [PATCH 3/3] improve graph agents code --- README.md | 25 +++ agents/config/configuration.yaml | 10 +- agents/config/configuration_prod.yaml | 66 ++++++ .../hello_graph/__init__.py | 13 ++ .../hello_graph/graph_agent.py | 94 +++++++++ .../hello_graph/graph_state.py | 66 ++++++ agents/fred_samples_agents/registry.py | 2 + agents/pyproject.toml | 4 +- agents/uv.lock | 191 ++++++++++++------ 9 files changed, 402 insertions(+), 69 deletions(-) create mode 100644 agents/config/configuration_prod.yaml create mode 100644 agents/fred_samples_agents/hello_graph/__init__.py create mode 100644 agents/fred_samples_agents/hello_graph/graph_agent.py create mode 100644 agents/fred_samples_agents/hello_graph/graph_state.py diff --git a/README.md b/README.md index 2422e8a..c9b6b54 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,30 @@ Requires: nothing — just a model API key --- +### Hello Graph — minimal graph agent + +The smallest possible v2 graph agent: one classify step, one answer step, one +finalize step. No MCP server, no HITL gate. Read this before Bank Transfer or +Postal Tracking to see the shape of a graph agent with nothing else in the way. + +``` +Agent ID: fred.samples.hello_graph +Requires: nothing — just a model API key +``` + +**Workflow:** +1. `classify` reads your message and decides "greeting" or "question". +2. `greet` or `answer` produces one model reply on the matching branch. +3. `finalize` returns it. + +**Try it:** +``` +Hi! +What's the capital of France? +``` + +--- + ### Bank Transfer — HITL demo A workflow agent that executes a fund transfer through two mandatory human @@ -166,6 +190,7 @@ Current agent: assistant Switch to a sample agent: ``` +/agent fred.samples.hello_graph /agent fred.samples.bank_transfer.graph /agent fred.samples.postal_tracking.graph /agent fred.samples.team_of_3.router diff --git a/agents/config/configuration.yaml b/agents/config/configuration.yaml index 3d80f1f..68ab41c 100644 --- a/agents/config/configuration.yaml +++ b/agents/config/configuration.yaml @@ -3,8 +3,6 @@ app: base_url: "/samples/agents/v1" port: 8010 log_level: "debug" - metrics_address: "127.0.0.1" - metrics_port: 9115 security: m2m: @@ -25,7 +23,13 @@ ai: observability: tracer: logging # null | logging | langfuse - metrics: prometheus # null | logging | prometheus + kpi: + log: + enabled: true + prometheus: + enabled: false + opensearch: + enabled: false storage: postgres: diff --git a/agents/config/configuration_prod.yaml b/agents/config/configuration_prod.yaml new file mode 100644 index 0000000..afb663c --- /dev/null +++ b/agents/config/configuration_prod.yaml @@ -0,0 +1,66 @@ +app: + name: "Fred Sample Agents" + base_url: "/samples/agents/v1" + host: "0.0.0.0" + port: 8010 + log_level: "info" + +security: + m2m: + enabled: true + client_id: "agentic" + realm_url: "http://app-keycloak:8080/realms/app" + secret_env_var: KEYCLOAK_AGENTIC_CLIENT_SECRET # pragma: allowlist secret (env var name only) + user: + enabled: true + client_id: "app" + # Must be browser-reachable: tokens validated here are issued client-side + # by keycloak-js against this same URL. "app-keycloak" only resolves + # inside the docker-compose network. security.m2m.realm_url above stays + # docker-internal (backend-to-Keycloak only). Mirrors the split applied + # in fred/apps/control-plane-backend and fred/apps/knowledge-flow-backend. + realm_url: "http://localhost:8080/realms/app" + authorized_origins: + - "http://localhost:5173" + rebac: + enabled: true + type: openfga + api_url: "http://localhost:9080" + +ai: + knowledge_flow_url: "http://localhost:8111/knowledge-flow/v1" + timeout: + connect: 5 + read: 30 + +observability: + tracer: logging # null | logging | langfuse — no Langfuse creds in this pod's .env yet + kpi: + log: + enabled: true + prometheus: + enabled: true + port: 9010 # app.port + 1000, same convention as the other pods; 9000/9111/9222 already taken + opensearch: + enabled: true + +storage: + postgres: + host: localhost + port: 5432 + database: fred + username: fred + opensearch: + host: "https://localhost:9200" + username: "admin" + secure: true + verify_certs: false + log_store: + type: opensearch + index: app-logs-index + +scheduler: + enabled: false + +platform: + control_plane_url: "http://localhost:8222/control-plane/v1" diff --git a/agents/fred_samples_agents/hello_graph/__init__.py b/agents/fred_samples_agents/hello_graph/__init__.py new file mode 100644 index 0000000..0e7d3ec --- /dev/null +++ b/agents/fred_samples_agents/hello_graph/__init__.py @@ -0,0 +1,13 @@ +# Copyright Thales 2026 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/agents/fred_samples_agents/hello_graph/graph_agent.py b/agents/fred_samples_agents/hello_graph/graph_agent.py new file mode 100644 index 0000000..96acd2a --- /dev/null +++ b/agents/fred_samples_agents/hello_graph/graph_agent.py @@ -0,0 +1,94 @@ +# Copyright Thales 2026 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Graph definition for the Hello Graph sample agent. + +Purpose: +- the minimal v2 graph agent: no MCP server, no human-in-the-loop gate, no + external dependency of any kind beyond a configured chat model +- read this first, before bank_transfer or postal_tracking, to see the shape + of a graph agent with nothing else in the way + +Workflow overview: + classify + ├─ greeting ──► greet ──► finalize + └─ question ──► answer ──► finalize + +No MCP servers required — just a configured model. +""" + +from __future__ import annotations + +from fred_sdk import GraphAgent, GraphWorkflow + +from .graph_state import HelloGraphInput, HelloGraphState +from .graph_steps import ( + answer_step, + classify_step, + finalize_step, + greet_step, +) + + +class HelloGraphAgent(GraphAgent): + """ + Sample v2 graph agent with the smallest possible shape: classify, then + answer on one of two branches. + + Use this agent as a reference when building any workflow agent, before + adding MCP tools or HITL gates. Once this shape is clear, look at + bank_transfer (MCP + two HITL gates) or postal_tracking (MCP + map + + one HITL gate) for a fuller example. + + Change graph_agent.py when the workflow shape changes. + Change graph_state.py when the data model changes. + Change graph_steps.py when step behaviour changes. + """ + + agent_id: str = "fred.samples.hello_graph" + role: str = "Hello Graph" + description: str = ( + "Minimal sample graph agent: classifies a message as a greeting or a " + "question, then answers on the matching branch. No MCP server, no HITL." + ) + tags: tuple[str, ...] = ("sample", "graph", "hello_world", "minimal", "v2") + + input_schema = HelloGraphInput + state_schema = HelloGraphState + input_to_state = {"message": "latest_user_text"} + output_state_field = "final_text" + + workflow = GraphWorkflow( + entry="classify", + nodes={ + "classify": classify_step, + "greet": greet_step, + "answer": answer_step, + "finalize": finalize_step, + }, + edges={ + "greet": "finalize", + "answer": "finalize", + }, + routes={ + "classify": { + "greeting": "greet", + "question": "answer", + }, + }, + ) + + +HELLO_GRAPH_AGENT = HelloGraphAgent() diff --git a/agents/fred_samples_agents/hello_graph/graph_state.py b/agents/fred_samples_agents/hello_graph/graph_state.py new file mode 100644 index 0000000..24b3b5d --- /dev/null +++ b/agents/fred_samples_agents/hello_graph/graph_state.py @@ -0,0 +1,66 @@ +# Copyright Thales 2026 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Input and state models for the Hello Graph sample agent. + +This is the smallest state shape a v2 graph agent needs: one field for what the +user said, one field for the routing decision, one field for the answer. +""" + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel, Field + + +class HelloGraphInput(BaseModel): + """ + User message that starts one hello-graph turn. + + Example: + ```python + request = HelloGraphInput(message="Hi there!") + ``` + """ + + message: str = Field(..., min_length=1) + + +class HelloGraphState(BaseModel): + """ + Workflow state for the hello-graph sample. + + Example: + ```python + state = HelloGraphState( + latest_user_text="Hi there!", + kind="greeting", + final_text="Hello! Ask me anything.", + ) + ``` + """ + + latest_user_text: str + + # Set by classify_step; drives the routes= branch in the workflow. + kind: Literal["greeting", "question"] | None = None + + # Terminal output written by greet_step or answer_step. + final_text: str | None = None + done_reason: str | None = None + + # Set by the runtime when a node raises and on_error routing fires. + node_error: str = "" diff --git a/agents/fred_samples_agents/registry.py b/agents/fred_samples_agents/registry.py index a6038f7..ec6484f 100644 --- a/agents/fred_samples_agents/registry.py +++ b/agents/fred_samples_agents/registry.py @@ -16,6 +16,7 @@ from fred_samples_agents.bank_transfer.graph_agent import BANK_TRANSFER_AGENT from fred_samples_agents.postal_tracking.graph_agent import POSTAL_TRACKING_AGENT from fred_samples_agents.general_assistant import GENERAL_ASSISTANT_AGENT +from fred_samples_agents.hello_graph.graph_agent import HELLO_GRAPH_AGENT from fred_samples_agents.team_of_3_agents_sample import ( TEAM3_GRAPH_AGENT, TEAM3_REACT_AGENT_ONE, @@ -26,6 +27,7 @@ def build_registry() -> dict[str, ReActAgentDefinition | GraphAgentDefinition]: return { GENERAL_ASSISTANT_AGENT.agent_id: GENERAL_ASSISTANT_AGENT, + HELLO_GRAPH_AGENT.agent_id: HELLO_GRAPH_AGENT, BANK_TRANSFER_AGENT.agent_id: BANK_TRANSFER_AGENT, POSTAL_TRACKING_AGENT.agent_id: POSTAL_TRACKING_AGENT, TEAM3_GRAPH_AGENT.agent_id: TEAM3_GRAPH_AGENT, diff --git a/agents/pyproject.toml b/agents/pyproject.toml index 23e5aef..4739ec8 100644 --- a/agents/pyproject.toml +++ b/agents/pyproject.toml @@ -6,8 +6,8 @@ readme = "README.md" requires-python = ">=3.12,<3.13" license = { text = "Apache-2.0" } dependencies = [ - "fred-sdk>=2.0.5", - "fred-runtime>=2.0.4", + "fred-sdk>=3.3.5", + "fred-runtime>=3.3.7", "greenlet>=3.4.0", "uvicorn>=0.35", ] diff --git a/agents/uv.lock b/agents/uv.lock index c3883ee..3917ee1 100644 --- a/agents/uv.lock +++ b/agents/uv.lock @@ -76,6 +76,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" }, ] +[[package]] +name = "alembic" +version = "1.18.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/cc/ac0bed8e562e7407fe55c3ba85a4dce86e6dbd8730887bd1e406a6c5c18a/alembic-1.18.5.tar.gz", hash = "sha256:1554982221dd17e9a749b53902407578eb305e453f71999e8c7f0a48389fff8e", size = 2060480, upload-time = "2026-06-25T15:20:54.888Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/78/5fe6dc3a3a5b2f5a2a4faef8bfe336d5fa049a38884ab3172e0098160c01/alembic-1.18.5-py3-none-any.whl", hash = "sha256:06d8ba9d04558022f5395e9317de03d270f3dced49cee01f89fe7a13c26f14bc", size = 264664, upload-time = "2026-06-25T15:20:56.673Z" }, +] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -383,7 +397,7 @@ wheels = [ [[package]] name = "deepagents" -version = "0.5.6" +version = "0.6.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain" }, @@ -393,9 +407,9 @@ dependencies = [ { name = "langsmith" }, { name = "wcmatch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/b6/8bff546e96d33fa16b3f20038db4329f0c4b7c6e3e510855f477bc55e9a8/deepagents-0.5.6.tar.gz", hash = "sha256:9906b3695affb0175742eb681377158d197c5cfbb70ab31cc6633da0deb080d9", size = 163816, upload-time = "2026-05-01T15:24:36.111Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/db/a6acdc72a9e90c3f07ed10de35c951734a02d4facb693bb59684ad368801/deepagents-0.6.12.tar.gz", hash = "sha256:1f281c0bc5a63132f62e2ee345c1dc593b23188da6e23016401f6879fbe54b5f", size = 211364, upload-time = "2026-06-25T17:26:52.775Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/32/d21ef2cd5f17e98bc3fb83cd7303661caf3dab00b7f622b415ab8ef0d900/deepagents-0.5.6-py3-none-any.whl", hash = "sha256:3dc2f2ac4f0a0eb2735dedb62c9e00ac066312fcaedd9ef8d6c11c0fb7c153f5", size = 186304, upload-time = "2026-05-01T15:24:34.729Z" }, + { url = "https://files.pythonhosted.org/packages/98/49/af7219b3c13520fee047bb807cfaefba17f8e4584c551d946773589a4f08/deepagents-0.6.12-py3-none-any.whl", hash = "sha256:28b8fa0119ca0a689e3e18e288c4634e4046062acfc87a1cb34289d3af3a1c88", size = 236120, upload-time = "2026-06-25T17:26:51.736Z" }, ] [[package]] @@ -438,16 +452,18 @@ wheels = [ [[package]] name = "fastapi" -version = "0.116.1" +version = "0.139.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "annotated-doc" }, { name = "pydantic" }, { name = "starlette" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/af/a5f50ccfa659ec1802cb4ca842c23f06d906a8cc9aef6016a2caeea3d4ed/fastapi-0.139.0.tar.gz", hash = "sha256:99ab7b2d92223c76d6cf10757ab3f89d45b38267fc20b2a136cf02f6beac3145", size = 423016, upload-time = "2026-07-01T16:35:33.436Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7c/8e3c6ad324ea5cb36604fc3f968554887891c316d9dfde57761611d907ad/fastapi-0.139.0-py3-none-any.whl", hash = "sha256:cf15e1e9e667ddb0ad63811e60bd11390d1aac838ca4a7a23f421807b2308189", size = 130339, upload-time = "2026-07-01T16:35:32.19Z" }, ] [[package]] @@ -470,7 +486,7 @@ wheels = [ [[package]] name = "fred-core" -version = "2.0.1" +version = "3.4.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiosqlite" }, @@ -478,7 +494,10 @@ dependencies = [ { name = "authzed" }, { name = "azure-identity" }, { name = "fastapi" }, + { name = "google-cloud-storage" }, + { name = "greenlet" }, { name = "httpx" }, + { name = "langchain-anthropic" }, { name = "langchain-core" }, { name = "langchain-google-genai" }, { name = "langchain-google-vertexai" }, @@ -497,19 +516,21 @@ dependencies = [ { name = "python-keycloak" }, { name = "sqlalchemy" }, { name = "sqlparse" }, + { name = "starlette" }, { name = "temporalio" }, { name = "uv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/8f/218a8dd195fbba83e3146724c6629b809ca9fbe29347f105220a13a5a5f1/fred_core-2.0.1.tar.gz", hash = "sha256:c263b127d95eb02e99b4fc7514971ab3066e395b4ff077a6d9971fb979ea4c71", size = 132807, upload-time = "2026-05-05T07:26:29.996Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/5c/64f9908f1ff5a5468dab81ea126293e5c8882421d0ee584b074c776cf466/fred_core-3.4.7.tar.gz", hash = "sha256:ca1ff7e316695d78b78baa05486509a6c589797d4da2416f4caa8aad4b0fb87b", size = 241415, upload-time = "2026-07-23T05:09:56.434Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/e4/04ece2609885476560d1e504f4bc855035cb643d9bddbb1c397d8a9d0bc3/fred_core-2.0.1-py3-none-any.whl", hash = "sha256:69b7633cd28d4aa3fe664baadbbd17e40c30982fb156098b05e8532242a72abe", size = 190643, upload-time = "2026-05-05T07:26:31.864Z" }, + { url = "https://files.pythonhosted.org/packages/e1/93/9172c328991161c50276e70d76981efc32e1cb0bb4809d78ca9860d401a8/fred_core-3.4.7-py3-none-any.whl", hash = "sha256:a0d461f116aaf101420b9de9485054a6bf1c240ec6988a15a00ca87b4de17678", size = 356183, upload-time = "2026-07-23T05:09:54.944Z" }, ] [[package]] name = "fred-runtime" -version = "2.0.3" +version = "3.3.7" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "alembic" }, { name = "deepagents" }, { name = "fred-core" }, { name = "fred-sdk" }, @@ -519,12 +540,13 @@ dependencies = [ { name = "langchain-mcp-adapters" }, { name = "langfuse" }, { name = "langgraph" }, + { name = "python-multipart" }, { name = "pyyaml" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/82/a743ca7d490eb687badd0e7d9526c9467ba4c6cc3aaed0a862546f43d505/fred_runtime-2.0.3.tar.gz", hash = "sha256:0b7832510a3ff82eb93c34117ae9646669e5e7a7f71ac2803a5cf978ebb040b8", size = 210537, upload-time = "2026-05-05T11:03:52.163Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/93/f94f400e5412fd3aeef6e45aa7d4e0eae8bfbdc2a1e5e8d15bfecf84f905/fred_runtime-3.3.7.tar.gz", hash = "sha256:16cfaaa5ab175994f07c0183955f7dbef8f0406e4739666b9b59ff11aa49b8a3", size = 402181, upload-time = "2026-07-23T05:10:20.719Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/ab/49a0e29ad217617e51eb110cdfc6f4e18c1ab0217ca1d6593a46405926c8/fred_runtime-2.0.3-py3-none-any.whl", hash = "sha256:7ed7af375f4ed50d80b3a9d2bf31ca4c4b4f5d7b671d32ebbfe95c008913bb31", size = 220878, upload-time = "2026-05-05T11:03:50.144Z" }, + { url = "https://files.pythonhosted.org/packages/72/b4/bb6d1b843690763329e80ec582a1f02aa32efb50a4f03e2fcd2f3cf45ea7/fred_runtime-3.3.7-py3-none-any.whl", hash = "sha256:89029dcc50e9f4e8ea2cfc871a740f1544d9043b858586cc378c90571c1b501a", size = 347824, upload-time = "2026-07-23T05:10:22.113Z" }, ] [[package]] @@ -552,8 +574,8 @@ dev = [ [package.metadata] requires-dist = [ - { name = "fred-runtime", specifier = ">=0.1.19" }, - { name = "fred-sdk", specifier = ">=0.1.11" }, + { name = "fred-runtime", specifier = ">=3.3.7" }, + { name = "fred-sdk", specifier = ">=3.3.5" }, { name = "greenlet", specifier = ">=3.4.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.4.2" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.12.5" }, @@ -569,7 +591,7 @@ dev = [ [[package]] name = "fred-sdk" -version = "2.0.2" +version = "3.3.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fred-core" }, @@ -579,9 +601,9 @@ dependencies = [ { name = "langgraph" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/22/f4f3a90467058ccbbe8d41dd6323f6def118222a3618b23807e842c1eac6/fred_sdk-2.0.2.tar.gz", hash = "sha256:3fb27e3da5fa9f520e247f8a80d0de54b716740e8df0b5eec9a72000b49cc7d2", size = 93887, upload-time = "2026-05-05T11:02:46.144Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/d8/a7f736033ec891c56baed53950a93b3dcb49ebcebdebb18ee764e28ee512/fred_sdk-3.3.5.tar.gz", hash = "sha256:211a44fa5b8aa31c2f7b1eef915e7f8f2402f1b69834d1a4adb66649c717407a", size = 137245, upload-time = "2026-07-23T05:10:07.266Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/bb/c39f9d4444deb6deb587f1f1d6f0bbabcd5a7ebee502afce232bfeb53de0/fred_sdk-2.0.2-py3-none-any.whl", hash = "sha256:9d7c709c5556f6766afc77eed50290fbce3bdbb6952442f0f68bb8af51998bcb", size = 104891, upload-time = "2026-05-05T11:02:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/03/14/1dfa255f6c242b4cecd83425ab98238fe214f66ad412e7d6cae66ef37afc/fred_sdk-3.3.5-py3-none-any.whl", hash = "sha256:c5b43fd1310c8d49ef02d449996dd27a5354e38addc211f5142e94cb2d75d6e8", size = 141818, upload-time = "2026-07-23T05:10:05.907Z" }, ] [[package]] @@ -1105,35 +1127,35 @@ wheels = [ [[package]] name = "langchain" -version = "1.2.17" +version = "1.3.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/35/322d13339acb61d7a733d03a73a9ade968c64ac0eb982f497d24e22a998f/langchain-1.2.17.tar.gz", hash = "sha256:c30b578c0eebbde8bec9247dbbbae1a791128557b99b65c8be1e007040975d09", size = 577779, upload-time = "2026-04-30T20:25:34.626Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/68/a6dbad9c22df4087a0f9e79ddd46226c442b30128bfeee538d5889492a73/langchain-1.3.14.tar.gz", hash = "sha256:1b6696c72ba3bbbce54d745e0180742c9f6ece8bbc59ed5a46c3e20b9a435929", size = 645181, upload-time = "2026-07-16T13:28:18.29Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/cf/b183dba8667f7b6d1be546fb8089a3bc3bc12b514f551f5317ae03815770/langchain-1.2.17-py3-none-any.whl", hash = "sha256:ff881cdfbe90e0b6afac42eea7999657c282cc73db059c910d803f4e9f8ff305", size = 113131, upload-time = "2026-04-30T20:25:32.895Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ec/0f942e78a621f8e3162ff1ed24284f469aaf51fb4607ee5831c626f2b2bc/langchain-1.3.14-py3-none-any.whl", hash = "sha256:4d10dbe91005952cddd56d0dc77aa108964da6bae90ab20063653957e901f782", size = 139560, upload-time = "2026-07-16T13:28:16.498Z" }, ] [[package]] name = "langchain-anthropic" -version = "1.4.3" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anthropic" }, { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fa/e3/d2f9dec95602524b1cfb4be2747ba5bc38d32501b2a56cb4bcb76e80bb45/langchain_anthropic-1.4.3.tar.gz", hash = "sha256:f8a2442463c0629b1b3110eaeaa56fdbdc87df2a802f8c7f5ecf611eb4874ec8", size = 685219, upload-time = "2026-05-03T17:33:27.118Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/99/b0fc215bb552a9e94af78f583334ad16a561bca3c771dbd6cbaa67f92a77/langchain_anthropic-1.5.0.tar.gz", hash = "sha256:c36f195fd73455d820f4e7cf7220d652858d707b67bce70f3fe0f57227ec6c81", size = 711155, upload-time = "2026-07-21T18:17:10.143Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/55/482a1968c95275e8be6d8c1e53b54f0f7be0b8b155ce1608c947a95cf543/langchain_anthropic-1.4.3-py3-none-any.whl", hash = "sha256:65466e0f2f95909a009708f2958e917dfdbfab79c612b4484a30866a85e1f291", size = 50389, upload-time = "2026-05-03T17:33:25.671Z" }, + { url = "https://files.pythonhosted.org/packages/0b/bd/a612fbafa1def5ff41a72080e430a310bc08f6fce34d4fc803fb9c00f632/langchain_anthropic-1.5.0-py3-none-any.whl", hash = "sha256:b341c0fa197e7d55555a228377e66214af8cb77631ca633f8bd58655d10103ac", size = 53083, upload-time = "2026-07-21T18:17:08.955Z" }, ] [[package]] name = "langchain-core" -version = "1.3.2" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -1146,14 +1168,14 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/03/7219502e8ca728d65eb44d7a3eb60239230742a70dbfc9241b9bfd61c4ab/langchain_core-1.3.2.tar.gz", hash = "sha256:fd7a50b2f28ba561fd9d7f5d2760bc9e06cf00cdf820a3ccafe88a94ffa8d5b7", size = 911813, upload-time = "2026-04-24T15:49:23.699Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/05/986c4bb148285791eb59994e0b28947bed96cac7f24467079e4274952a37/langchain_core-1.5.0.tar.gz", hash = "sha256:e1fa09d55b354192c8f60dade06a55bd6add2318c822a684555b8d4a30a16143", size = 967401, upload-time = "2026-07-21T03:37:26.48Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/d5/8fa4431007cbb7cfed7590f4d6a5dea3ad724f4174d248f6642ef5ce7d05/langchain_core-1.3.2-py3-none-any.whl", hash = "sha256:d44a66127f9f8db735bdfd0ab9661bccb47a97113cfd3f2d89c74864422b7274", size = 542390, upload-time = "2026-04-24T15:49:21.991Z" }, + { url = "https://files.pythonhosted.org/packages/29/56/5ef7ba14bac95b0344da18c6e8ec108dce0baf5fc054d1117702f92af29d/langchain_core-1.5.0-py3-none-any.whl", hash = "sha256:f122efee35446632b38687119fca33711abbf3b6b555e31156762298fbe78a65", size = 558510, upload-time = "2026-07-21T03:37:24.423Z" }, ] [[package]] name = "langchain-google-genai" -version = "4.2.2" +version = "4.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filetype" }, @@ -1161,9 +1183,9 @@ dependencies = [ { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/78/dfe068937338727b0dee637d971d59fe2fa275f9d0f0edee3fa80e811846/langchain_google_genai-4.2.2.tar.gz", hash = "sha256:5fc774bf41d1dc1c1a5ba8d7b9f2017dfa77e30653c9b44d2dfbaf0e877e7388", size = 267457, upload-time = "2026-04-15T15:08:32.18Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/b7/793ef6c8894f6c6e6946fcf95c59644341e0307f21490b33645fec0ac56d/langchain_google_genai-4.3.1.tar.gz", hash = "sha256:623cd4329f6a3e4e0397ccacc6fa355728362572688ff9a8e522bf21519e9964", size = 285426, upload-time = "2026-07-21T22:14:43.165Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/5c/adf81d68ab89b4cf505e690f8c1956d11b5969c831c951c7b4b1b1818080/langchain_google_genai-4.2.2-py3-none-any.whl", hash = "sha256:c8d09aac0304d26f1c2483e41a350f15587af1fbe034c39a304e1e17a3b743f3", size = 67605, upload-time = "2026-04-15T15:08:31.346Z" }, + { url = "https://files.pythonhosted.org/packages/30/cf/d6c81ba35c27c24af754f36943100b78ddc922594d23d82a9bd8d1334bad/langchain_google_genai-4.3.1-py3-none-any.whl", hash = "sha256:0ebec25bfa62e75a65561b92fde5bdf8d87618bdda9371afe57d6b86bbb9cda8", size = 72170, upload-time = "2026-07-21T22:14:42.032Z" }, ] [[package]] @@ -1248,14 +1270,14 @@ wheels = [ [[package]] name = "langchain-protocol" -version = "0.0.15" +version = "0.0.18" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4f/24/9777489d6fbbee64af0c8f96d4f840239c408cf694f3394672807dafc490/langchain_protocol-0.0.15.tar.gz", hash = "sha256:9ab2d11ee73944754f10e037e717098d3a6796f0e58afa9cadda6154e7655ade", size = 5862, upload-time = "2026-05-01T22:30:04.748Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/59/b5959aea96faa9146e2e49a7a22882b3528c62efafe9a6a95beab30c2305/langchain_protocol-0.0.18.tar.gz", hash = "sha256:ec3e11782f1ed0c9db38e5a9ed01b0e7a0d3fba406faa8aef6594b73c56a63e6", size = 6150, upload-time = "2026-06-18T17:08:26.959Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/7a/9c97a7b9cbe4c5dc6a44cdb1545450c28f0c8ce89b9c1f0ee7fbad896263/langchain_protocol-0.0.15-py3-none-any.whl", hash = "sha256:461eb794358f83d5e42635a5797799ffec7b4702314e34edf73ac21e75d3ef79", size = 6982, upload-time = "2026-05-01T22:30:03.877Z" }, + { url = "https://files.pythonhosted.org/packages/99/2e/d82db9eec13ad0f72e7aaad5c4bc730ab111934fdc83c85523206eb9b0a0/langchain_protocol-0.0.18-py3-none-any.whl", hash = "sha256:70b53a86fbf9cedc863555effe44da192ab02d556ddbf2cf95b8873adcf41b5a", size = 7221, upload-time = "2026-06-18T17:08:25.996Z" }, ] [[package]] @@ -1301,7 +1323,7 @@ wheels = [ [[package]] name = "langgraph" -version = "1.1.10" +version = "1.2.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, @@ -1311,68 +1333,88 @@ dependencies = [ { name = "pydantic" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/b3/7dec224369c7938eb3227ff69542a0d0f517862a0d27945b8c395f2a781f/langgraph-1.1.10.tar.gz", hash = "sha256:3115beb58203283c98d8752a90c034f3432177d2979a1fe205f76e5f1b744500", size = 560685, upload-time = "2026-04-27T17:19:10.426Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/4b/0d1130e26b41a99dcc88353bbe7162a1f255c4db746bd94024268e6af27b/langgraph-1.2.9.tar.gz", hash = "sha256:385f87bc1802c35af7e0aa479278ecba8582d103515eb48256cb2ddcd42d0bd4", size = 722869, upload-time = "2026-07-10T01:30:14.985Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/07/057dc1aa7991115fca53f1fa6573a7cc0dd296c05360c672cc67fdb6245b/langgraph-1.1.10-py3-none-any.whl", hash = "sha256:8a4f163f72f4401648d0c11b48ee906947d938ba8cf1f474540fe591534f0d17", size = 173750, upload-time = "2026-04-27T17:19:09.073Z" }, + { url = "https://files.pythonhosted.org/packages/41/16/0b8dc48823f1326f3e0c8012a3c07a40da6f194299e2ec080df236287baf/langgraph-1.2.9-py3-none-any.whl", hash = "sha256:c2d98ad94333937922ba04148641c1da2bfe45b5b8e55d7b6dcb0bb2df809e76", size = 247473, upload-time = "2026-07-10T01:30:13.733Z" }, ] [[package]] name = "langgraph-checkpoint" -version = "4.0.3" +version = "4.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "ormsgpack" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/e1/885e49cdafceb4c74dae4573bc5dd6054c6c640382ee73104532f33dca46/langgraph_checkpoint-4.0.3.tar.gz", hash = "sha256:a7b5e2ca18fb79b55edf19396d4ee446f8a53dcb7a4ec62ce6f1c7e00bb5af7f", size = 174009, upload-time = "2026-04-27T14:34:02.777Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/47/886af6f886f0bff2273164a45f008694e48a96ff3cd25ff0228f2aa9480e/langgraph_checkpoint-4.1.1.tar.gz", hash = "sha256:6c2bdb530c91f91d7d9c1bd100925d0fc4f498d418c17f3587d1526279482a25", size = 184020, upload-time = "2026-05-22T16:57:38.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/ee/ecd3fa2e893746dde3b768daca2a4935208bc77d09445437ccfffb4a8c9b/langgraph_checkpoint-4.0.3-py3-none-any.whl", hash = "sha256:b91b765712a2311a5b198760f714b7ab9b376d01c047ed78d9b9a3e80df802a3", size = 51682, upload-time = "2026-04-27T14:34:01.51Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b4/71425e3e38be92611300b9cc5e46a5bf98ab23f5ea8a75b73d02a2f1413c/langgraph_checkpoint-4.1.1-py3-none-any.whl", hash = "sha256:25d29144b082827218e7bc3f1e9b0566a4bb007895cd6cc26f66a8428739f56e", size = 56212, upload-time = "2026-05-22T16:57:37.203Z" }, ] [[package]] name = "langgraph-prebuilt" -version = "1.0.13" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph-checkpoint" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/a4/f8ac75fa7c503103f0cf7680944e28bbaaef74c19a8d163d7346869cc369/langgraph_prebuilt-1.0.13.tar.gz", hash = "sha256:ad219782a80e1718e7e7794de49e0ae307111d45cbcffab9a52725a66a609456", size = 172913, upload-time = "2026-04-30T01:48:15.742Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/66/ed9b93f56bc17ef22d551892f0ac2b225a97fe0fcf23a511b857f70d590b/langgraph_prebuilt-1.1.0.tar.gz", hash = "sha256:3c579cf6eed2d17f9c157c2d0fcaddcd8688524e7022d3b22b37a3bf4589d528", size = 178833, upload-time = "2026-05-12T03:37:49.332Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/ef/5ada0bef4013ef5ae53a0ca1de5736517f1076a54d313f156ca545ec65d5/langgraph_prebuilt-1.0.13-py3-none-any.whl", hash = "sha256:7055e9fad41fbd3593800aed0aea0a6e974b17f33ed51b80d3d3a031212dd7c0", size = 37214, upload-time = "2026-04-30T01:48:14.507Z" }, + { url = "https://files.pythonhosted.org/packages/e9/43/3fe1a700b8490ed02679cdbbc8c915eb23a092faf496c9c1118abcd10be3/langgraph_prebuilt-1.1.0-py3-none-any.whl", hash = "sha256:51e311747d755b751d5c6b39b0c1446124d3a7643d2515017e6714b323508fc9", size = 41043, upload-time = "2026-05-12T03:37:48.007Z" }, ] [[package]] name = "langgraph-sdk" -version = "0.3.13" +version = "0.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, + { name = "langchain-core" }, + { name = "langchain-protocol" }, { name = "orjson" }, + { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/db/77a45127dddcfea5e4256ba916182903e4c31dc4cfca305b8c386f0a9e53/langgraph_sdk-0.3.13.tar.gz", hash = "sha256:419ca5663eec3cec192ad194ac0647c0c826866b446073eb40f384f950986cd5", size = 196360, upload-time = "2026-04-07T20:34:18.766Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/2b/bd8ac26d4e97f6df88ef05ce5b6a38945a3903e1025d926f4752aa88aa97/langgraph_sdk-0.4.2.tar.gz", hash = "sha256:b88f0f5f6328ac0680d6790614a905b2bcfa257f2276dba4e38f0e86db0aa738", size = 348327, upload-time = "2026-06-01T17:51:19.856Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ef/64d64e9f8eea47ce7b939aa6da6863b674c8d418647813c20111645fcc62/langgraph_sdk-0.3.13-py3-none-any.whl", hash = "sha256:aee09e345c90775f6de9d6f4c7b847cfc652e49055c27a2aed0d981af2af3bd0", size = 96668, upload-time = "2026-04-07T20:34:17.866Z" }, + { url = "https://files.pythonhosted.org/packages/a0/05/aac507337cceae773c2cc9ab91eb6301963af7aeeb55b4217a00e15aff17/langgraph_sdk-0.4.2-py3-none-any.whl", hash = "sha256:75fa5096c1177ce39c847096a8fe3745ffd480ddb412995f836e9f5f884c43dd", size = 160521, upload-time = "2026-06-01T17:51:18.849Z" }, ] [[package]] name = "langsmith" -version = "0.8.0" +version = "0.10.10" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "anyio" }, + { name = "distro" }, { name = "httpx" }, { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, { name = "packaging" }, { name = "pydantic" }, { name = "requests" }, { name = "requests-toolbelt" }, + { name = "sniffio" }, + { name = "typing-extensions" }, { name = "uuid-utils" }, + { name = "websockets" }, { name = "xxhash" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/64/95f1f013531395f4e8ed73caeee780f65c7c58fe028cb543f8937b45611b/langsmith-0.8.0.tar.gz", hash = "sha256:59fe5b2a56bbbe14a08aa76691f84b49e8675dd21e11b57d80c6db8c08bac2e3", size = 4432996, upload-time = "2026-04-30T22:13:07.341Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/38/c709ff119172e490a8e8bccd10deeda8da239f15f11521009a58c7b904ac/langsmith-0.10.10.tar.gz", hash = "sha256:e0e175e9dd8de6d96dbb55244482e20590a2691ec7c5e087afc1f302e33d98fe", size = 4739726, upload-time = "2026-07-23T04:18:56.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/e1/a4be2e696c9473bb53298df398237da5674704d781d4b748ed35aeef592a/langsmith-0.8.0-py3-none-any.whl", hash = "sha256:12cc4bc5622b835a6d841964d6034df3617bdb912dae0c1381fd0a68a9b3a3ef", size = 393268, upload-time = "2026-04-30T22:13:05.56Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/0b2348bea502e07cb3c2a6e5109423debd82aef9fcef6f807c159ceb5297/langsmith-0.10.10-py3-none-any.whl", hash = "sha256:eb5e9da635f5853fc657cddd1c27f40a8e8b2f00c38051555d608c8e57eb605d", size = 675232, upload-time = "2026-07-23T04:18:54.399Z" }, +] + +[[package]] +name = "mako" +version = "1.3.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/62/791b31e69ae182791ec67f04850f2f062716bbd205483d63a215f3e062d3/mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a", size = 400219, upload-time = "2026-04-28T19:01:08.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/b1/a0ec7a5a9db730a08daef1fdfb8090435b82465abbf758a596f0ea88727e/mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9", size = 78521, upload-time = "2026-04-28T19:01:10.393Z" }, ] [[package]] @@ -1387,6 +1429,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, +] + [[package]] name = "mcp" version = "1.27.0" @@ -2379,15 +2440,15 @@ wheels = [ [[package]] name = "starlette" -version = "0.47.3" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/b9/cc3017f9a9c9b6e27c5106cc10cc7904653c3eec0729793aec10479dd669/starlette-0.47.3.tar.gz", hash = "sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9", size = 2584144, upload-time = "2025-08-24T13:36:42.122Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/fd/901cfa59aaa5b30a99e16876f11abe38b59a1a2c51ffb3d7142bb6089069/starlette-0.47.3-py3-none-any.whl", hash = "sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51", size = 72991, upload-time = "2025-08-24T13:36:40.887Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, ] [[package]] @@ -2648,20 +2709,22 @@ wheels = [ [[package]] name = "websockets" -version = "16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, - { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, - { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, - { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, - { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, - { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, - { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, - { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, - { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] [[package]]