From e9b121773cb93cc1273c15c9a9f091f677aac36e Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Wed, 26 Nov 2025 11:41:24 +0100 Subject: [PATCH 1/6] Restate runner --- openai-agents/tour-of-agents/app/chat.py | 31 +- .../tour-of-agents/app/durable_agent.py | 37 +-- .../tour-of-agents/app/utils/middleware.py | 219 +++++++++++--- openai-agents/tour-of-agents/pyproject.toml | 5 +- openai-agents/tour-of-agents/uv.lock | 276 +++++++++++------- vercel-ai/tour-of-agents/src/mcp/agent.ts | 35 +++ 6 files changed, 421 insertions(+), 182 deletions(-) create mode 100644 vercel-ai/tour-of-agents/src/mcp/agent.ts diff --git a/openai-agents/tour-of-agents/app/chat.py b/openai-agents/tour-of-agents/app/chat.py index 50dbac6..5e86210 100644 --- a/openai-agents/tour-of-agents/app/chat.py +++ b/openai-agents/tour-of-agents/app/chat.py @@ -1,32 +1,29 @@ -from agents import Agent, RunConfig, Runner, ModelSettings +from agents import Agent, Runner, WebSearchTool from restate import VirtualObject, ObjectContext, ObjectSharedContext -from app.utils.middleware import DurableModelCalls, RestateSession +from app.utils.middleware import Runner, RestateSession from app.utils.models import ChatMessage chat = VirtualObject("Chat") @chat.handler() -async def message(restate_context: ObjectContext, chat_message: ChatMessage) -> dict: - - restate_session = await RestateSession.create( - session_id=restate_context.key(), ctx=restate_context - ) - +async def message(_ctx: ObjectContext, chat_message: ChatMessage) -> dict: result = await Runner.run( - Agent(name="Assistant", instructions="You are a helpful assistant."), - input=chat_message.message, - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context), - model_settings=ModelSettings(parallel_tool_calls=False), + Agent( + name="Assistant", + instructions="You are a helpful assistant.", + tools=[ + WebSearchTool() + ] ), - session=restate_session, + input=chat_message.message, + session=RestateSession(), ) return result.final_output @chat.handler(kind="shared") -async def get_history(ctx: ObjectSharedContext): - return await ctx.get("items") or [] +async def get_history(_ctx: ObjectSharedContext): + session = RestateSession() + return session.get_items() diff --git a/openai-agents/tour-of-agents/app/durable_agent.py b/openai-agents/tour-of-agents/app/durable_agent.py index 04bddfd..6707d42 100644 --- a/openai-agents/tour-of-agents/app/durable_agent.py +++ b/openai-agents/tour-of-agents/app/durable_agent.py @@ -3,51 +3,30 @@ from agents import ( Agent, RunConfig, - Runner, - function_tool, RunContextWrapper, ModelSettings, ) +from restate import TerminalError -from app.utils.middleware import DurableModelCalls, raise_restate_errors -from app.utils.models import WeatherPrompt, WeatherRequest, WeatherResponse +from app.utils.middleware import Runner, function_tool +from app.utils.models import WeatherPrompt from app.utils.utils import fetch_weather - -@function_tool(failure_error_function=raise_restate_errors) -async def get_weather( - wrapper: RunContextWrapper[restate.Context], req: WeatherRequest -) -> WeatherResponse: +@function_tool +async def get_weather(city: str) -> str: """Get the current weather for a given city.""" - # Do durable steps using the Restate context - restate_context = wrapper.context - return await restate_context.run_typed("Get weather", fetch_weather, city=req.city) + return (await fetch_weather(city)).model_dump_json() -weather_agent = Agent[restate.Context]( +weather_agent = Agent( name="WeatherAgent", instructions="You are a helpful agent that provides weather updates.", tools=[get_weather], ) - agent_service = restate.Service("WeatherAgent") - @agent_service.handler() async def run(restate_context: restate.Context, prompt: WeatherPrompt) -> str: - - result = await Runner.run( - weather_agent, - input=prompt.message, - # Pass the Restate context to tools to make tool execution steps durable - context=restate_context, - # Choose any model and let Restate persist your calls - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context), - model_settings=ModelSettings(parallel_tool_calls=False), - ), - ) - + result = await Runner.run(weather_agent, input=prompt.message) return result.final_output diff --git a/openai-agents/tour-of-agents/app/utils/middleware.py b/openai-agents/tour-of-agents/app/utils/middleware.py index d6954ea..5eef9e4 100644 --- a/openai-agents/tour-of-agents/app/utils/middleware.py +++ b/openai-agents/tour-of-agents/app/utils/middleware.py @@ -1,4 +1,8 @@ import asyncio +import dataclasses +import typing + +import agents import restate from agents import ( @@ -7,15 +11,25 @@ default_tool_error_function, RunContextWrapper, AgentsException, + Runner as OpenAIRunner, + RunConfig, + TContext, + RunResult, Agent, AgentBase, ) +from agents.function_schema import DocstringStyle from agents.models.multi_provider import MultiProvider from agents.items import TResponseStreamEvent, TResponseOutputItem, ModelResponse from agents.memory.session import SessionABC from agents.items import TResponseInputItem -from typing import List, Any +from typing import List, Any, overload, Callable from typing import AsyncIterator +from agents.tool import ToolFunction, ToolErrorFunction, FunctionTool +from agents.tool_context import ToolContext +from agents.util._types import MaybeAwaitable from pydantic import BaseModel +from restate import ObjectContext, TerminalError +from restate.extensions import current_context # The OpenAI ModelResponse class is a dataclass with Pydantic fields. @@ -43,14 +57,13 @@ class DurableModelCalls(MultiProvider): A Restate model provider that wraps the OpenAI SDK's default MultiProvider. """ - def __init__(self, ctx: restate.Context, max_retries: int | None = 3): + def __init__(self, max_retries: int | None = 3): super().__init__() - self.ctx = ctx self.max_retries = max_retries def get_model(self, model_name: str | None) -> Model: return RestateModelWrapper( - self.ctx, super().get_model(model_name or None), self.max_retries + super().get_model(model_name or None), self.max_retries ) @@ -59,8 +72,7 @@ class RestateModelWrapper(Model): A wrapper around the OpenAI SDK's Model that persists LLM calls in the Restate journal. """ - def __init__(self, ctx: restate.Context, model: Model, max_retries: int | None = 3): - self.ctx = ctx + def __init__(self, model: Model, max_retries: int | None = 3): self.model = model self.model_name = f"RestateModelWrapper" self.max_retries = max_retries @@ -75,7 +87,7 @@ async def call_llm() -> RestateModelResponse: response_id=resp.response_id, ) - result = await self.ctx.run_typed( + result = await current_context().run_typed( "call LLM", call_llm, restate.RunOptions(max_attempts=self.max_retries) ) # convert back to original ModelResponse @@ -94,48 +106,34 @@ def stream_response(self, *args, **kwargs) -> AsyncIterator[TResponseStreamEvent class RestateSession(SessionABC): """Restate session implementation following the Session protocol.""" - def __init__( - self, - session_id: str, - ctx: restate.ObjectContext | restate.WorkflowContext, - current_items: List[TResponseInputItem], - ): - self.session_id = session_id - self.ctx = ctx - self.current_items = current_items - - @classmethod - async def create( - cls, - session_id: str, - ctx: restate.ObjectContext | restate.WorkflowContext, - ): - current_items = await ctx.get("items", type_hint=List[TResponseInputItem]) or [] - return cls(session_id, ctx, current_items) + def _ctx(self) -> restate.ObjectContext: + return typing.cast(restate.ObjectContext, current_context()) async def get_items(self, limit: int | None = None) -> List[TResponseInputItem]: """Retrieve conversation history for this session.""" + current_items = await self._ctx().get("items", type_hint=List[TResponseInputItem]) or [] if limit is not None: - return self.current_items[-limit:] - return self.current_items + return current_items[-limit:] + return current_items async def add_items(self, items: List[TResponseInputItem]) -> None: """Store new items for this session.""" # Your implementation here - self.ctx.set("items", self.current_items + items) + current_items = await self.get_items() or [] + self._ctx().set("items", current_items + items) async def pop_item(self) -> TResponseInputItem | None: """Remove and return the most recent item from this session.""" - if self.current_items: - item = self.current_items.pop() - self.ctx.set("items", self.current_items) + current_items = await self.get_items() or [] + if current_items: + item = current_items.pop() + self._ctx().set("items", current_items) return item return None async def clear_session(self) -> None: """Clear all items for this session.""" - self.current_items = [] - self.ctx.clear("items") + self._ctx().clear("items") class AgentsTerminalException(AgentsException, restate.TerminalError): @@ -166,3 +164,158 @@ def raise_restate_errors(context: RunContextWrapper[Any], error: Exception) -> s # Feed all other errors back to the agent return default_tool_error_function(context, error) + + +@overload +def function_tool( + func: ToolFunction[...], + *, + name_override: str | None = None, + description_override: str | None = None, + docstring_style: DocstringStyle | None = None, + use_docstring_info: bool = True, + failure_error_function: ToolErrorFunction | None = None, + strict_mode: bool = True, + is_enabled: ( + bool | Callable[[RunContextWrapper[Any], AgentBase], MaybeAwaitable[bool]] + ) = True, +) -> FunctionTool: + """Overload for usage as @function_tool (no parentheses).""" + ... + + +@overload +def function_tool( + *, + name_override: str | None = None, + description_override: str | None = None, + docstring_style: DocstringStyle | None = None, + use_docstring_info: bool = True, + failure_error_function: ToolErrorFunction | None = None, + strict_mode: bool = True, + is_enabled: ( + bool | Callable[[RunContextWrapper[Any], AgentBase], MaybeAwaitable[bool]] + ) = True, +) -> Callable[[ToolFunction[...]], FunctionTool]: + """Overload for usage as @function_tool(...).""" + ... + + +def function_tool( + func: ToolFunction[...] | None = None, + *, + name_override: str | None = None, + description_override: str | None = None, + docstring_style: DocstringStyle | None = None, + use_docstring_info: bool = True, + failure_error_function: ToolErrorFunction | None = default_tool_error_function, + strict_mode: bool = True, + is_enabled: ( + bool | Callable[[RunContextWrapper[Any], AgentBase], MaybeAwaitable[bool]] + ) = True, +) -> FunctionTool | Callable[[ToolFunction[...]], FunctionTool]: + def _raise_suspensions(context: RunContextWrapper[Any], error: Exception) -> str: + """A custom function to provide a user-friendly error message.""" + # Raise terminal errors and cancellations + if isinstance(error, restate.TerminalError): + # For the agent SDK it needs to be an AgentsException, for restate it needs to be a TerminalError + # so we create a new exception that inherits from both + raise AgentsTerminalException(error.message) + + # Next Python SDK release will use CancelledError for suspensions + if isinstance(error, asyncio.CancelledError): + raise AgentsAsyncioSuspension(error) + + # Feed all other errors back to the agent + return failure_error_function(context, error) + + return agents.function_tool( + func=func, + name_override=name_override, + description_override=description_override, + docstring_style=docstring_style, + use_docstring_info=use_docstring_info, + failure_error_function=_raise_suspensions, + strict_mode=strict_mode, + is_enabled=is_enabled, + ) + + +class Runner: + """ + A wrapper around Runner.run that automatically configures RunConfig for Restate contexts. + + This class automatically sets up the appropriate model provider (DurableModelCalls) and + model settings, taking over any model and model_settings configuration provided in the + original RunConfig. + """ + + @staticmethod + async def run( + starting_agent: Agent[TContext], + disable_tool_autowrapping: bool = False, + *args: object, + run_config: RunConfig | None = None, + **kwargs, + ) -> RunResult: + """ + Run an agent with automatic Restate configuration. + + Returns: + The result from Runner.run + """ + + current_run_config = run_config or RunConfig() + new_run_config = dataclasses.replace( + current_run_config, + # TODO allow wrapping an existing model provider? + model_provider=DurableModelCalls(), + ) + restate_agent = starting_agent if disable_tool_autowrapping else wrap_tools(starting_agent) + return await OpenAIRunner.run( + restate_agent, *args, run_config=new_run_config, **kwargs + ) + + +def wrap_tools( + agent: Agent[TContext], +) -> Agent[TContext]: + """ + Wrap the tools of an agent to use the Restate error handling. + + Returns: + A new agent with wrapped tools. + """ + + # Restate does not allow parallel tool calls, so we use a lock to ensure sequential execution. + sequential_tools_lock = asyncio.Lock() + wrapped_tools = [] + for tool in agent.tools: + if isinstance(tool, FunctionTool): + async def on_invoke_tool_wrapper( + tool_context: ToolContext[Any], tool_input: str + ) -> Any: + await sequential_tools_lock.acquire() + async def invoke(): + return await tool.on_invoke_tool(tool_context, tool_input) + try: + return await current_context().run_typed(tool.name, invoke) + except TerminalError as e: + raise AgentsTerminalException(e.message) + finally: + sequential_tools_lock.release() + + wrapped_tools.append( + dataclasses.replace(tool, on_invoke_tool=on_invoke_tool_wrapper) + ) + else: + wrapped_tools.append(tool) + + handoffs_with_wrapped_tools = [] + for handoff in agent.handoffs: + handoffs_with_wrapped_tools.append(wrap_tools(handoff)) + + return agent.clone( + tools=wrapped_tools, + handoffs=handoffs_with_wrapped_tools, + ) diff --git a/openai-agents/tour-of-agents/pyproject.toml b/openai-agents/tour-of-agents/pyproject.toml index c560bee..7c37a10 100644 --- a/openai-agents/tour-of-agents/pyproject.toml +++ b/openai-agents/tour-of-agents/pyproject.toml @@ -9,12 +9,15 @@ dependencies = [ "hypercorn", "restate-sdk[serde]>=0.11.0", "pydantic>=2.11.9", - "openai-agents>=0.3.2", + "openai-agents>=0.6.1", ] [tool.hatch.build.targets.wheel] packages = ["app"] +[tool.uv.sources] +restate-sdk = { path = "../../../sdk-python/dist/restate_sdk-0.12.0-cp313-cp313-linux_x86_64.whl" } + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" diff --git a/openai-agents/tour-of-agents/uv.lock b/openai-agents/tour-of-agents/uv.lock index 6a5a08f..622d100 100644 --- a/openai-agents/tour-of-agents/uv.lock +++ b/openai-agents/tour-of-agents/uv.lock @@ -358,6 +358,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/11/d334fbb7c2aeddd2e762b86d7a619acffae012643a5738e698f975a2a9e2/mcp-1.14.1-py3-none-any.whl", hash = "sha256:3b7a479e8e5cbf5361bdc1da8bc6d500d795dc3aff44b44077a363a7f7e945a4", size = 163809 }, ] +[[package]] +name = "msgspec" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/9c/bfbd12955a49180cbd234c5d29ec6f74fe641698f0cd9df154a854fc8a15/msgspec-0.20.0.tar.gz", hash = "sha256:692349e588fde322875f8d3025ac01689fead5901e7fb18d6870a44519d62a29", size = 317862 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/59/fdcb3af72f750a8de2bcf39d62ada70b5eb17b06d7f63860e0a679cb656b/msgspec-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:09e0efbf1ac641fedb1d5496c59507c2f0dc62a052189ee62c763e0aae217520", size = 193345 }, + { url = "https://files.pythonhosted.org/packages/5a/15/3c225610da9f02505d37d69a77f4a2e7daae2a125f99d638df211ba84e59/msgspec-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23ee3787142e48f5ee746b2909ce1b76e2949fbe0f97f9f6e70879f06c218b54", size = 186867 }, + { url = "https://files.pythonhosted.org/packages/81/36/13ab0c547e283bf172f45491edfdea0e2cecb26ae61e3a7b1ae6058b326d/msgspec-0.20.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:81f4ac6f0363407ac0465eff5c7d4d18f26870e00674f8fcb336d898a1e36854", size = 215351 }, + { url = "https://files.pythonhosted.org/packages/6b/96/5c095b940de3aa6b43a71ec76275ac3537b21bd45c7499b5a17a429110fa/msgspec-0.20.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb4d873f24ae18cd1334f4e37a178ed46c9d186437733351267e0a269bdf7e53", size = 219896 }, + { url = "https://files.pythonhosted.org/packages/98/7a/81a7b5f01af300761087b114dafa20fb97aed7184d33aab64d48874eb187/msgspec-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b92b8334427b8393b520c24ff53b70f326f79acf5f74adb94fd361bcff8a1d4e", size = 220389 }, + { url = "https://files.pythonhosted.org/packages/70/c0/3d0cce27db9a9912421273d49eab79ce01ecd2fed1a2f1b74af9b445f33c/msgspec-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:562c44b047c05cc0384e006fae7a5e715740215c799429e0d7e3e5adf324285a", size = 223348 }, + { url = "https://files.pythonhosted.org/packages/89/5e/406b7d578926b68790e390d83a1165a9bfc2d95612a1a9c1c4d5c72ea815/msgspec-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:d1dcc93a3ce3d3195985bfff18a48274d0b5ffbc96fa1c5b89da6f0d9af81b29", size = 188713 }, + { url = "https://files.pythonhosted.org/packages/47/87/14fe2316624ceedf76a9e94d714d194cbcb699720b210ff189f89ca4efd7/msgspec-0.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:aa387aa330d2e4bd69995f66ea8fdc87099ddeedf6fdb232993c6a67711e7520", size = 174229 }, + { url = "https://files.pythonhosted.org/packages/d9/6f/1e25eee957e58e3afb2a44b94fa95e06cebc4c236193ed0de3012fff1e19/msgspec-0.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2aba22e2e302e9231e85edc24f27ba1f524d43c223ef5765bd8624c7df9ec0a5", size = 196391 }, + { url = "https://files.pythonhosted.org/packages/7f/ee/af51d090ada641d4b264992a486435ba3ef5b5634bc27e6eb002f71cef7d/msgspec-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:716284f898ab2547fedd72a93bb940375de9fbfe77538f05779632dc34afdfde", size = 188644 }, + { url = "https://files.pythonhosted.org/packages/49/d6/9709ee093b7742362c2934bfb1bbe791a1e09bed3ea5d8a18ce552fbfd73/msgspec-0.20.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:558ed73315efa51b1538fa8f1d3b22c8c5ff6d9a2a62eff87d25829b94fc5054", size = 218852 }, + { url = "https://files.pythonhosted.org/packages/5c/a2/488517a43ccf5a4b6b6eca6dd4ede0bd82b043d1539dd6bb908a19f8efd3/msgspec-0.20.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:509ac1362a1d53aa66798c9b9fd76872d7faa30fcf89b2fba3bcbfd559d56eb0", size = 224937 }, + { url = "https://files.pythonhosted.org/packages/d5/e8/49b832808aa23b85d4f090d1d2e48a4e3834871415031ed7c5fe48723156/msgspec-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1353c2c93423602e7dea1aa4c92f3391fdfc25ff40e0bacf81d34dbc68adb870", size = 222858 }, + { url = "https://files.pythonhosted.org/packages/9f/56/1dc2fa53685dca9c3f243a6cbecd34e856858354e455b77f47ebd76cf5bf/msgspec-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb33b5eb5adb3c33d749684471c6a165468395d7aa02d8867c15103b81e1da3e", size = 227248 }, + { url = "https://files.pythonhosted.org/packages/5a/51/aba940212c23b32eedce752896205912c2668472ed5b205fc33da28a6509/msgspec-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:fb1d934e435dd3a2b8cf4bbf47a8757100b4a1cfdc2afdf227541199885cdacb", size = 190024 }, + { url = "https://files.pythonhosted.org/packages/41/ad/3b9f259d94f183daa9764fef33fdc7010f7ecffc29af977044fa47440a83/msgspec-0.20.0-cp312-cp312-win_arm64.whl", hash = "sha256:00648b1e19cf01b2be45444ba9dc961bd4c056ffb15706651e64e5d6ec6197b7", size = 175390 }, + { url = "https://files.pythonhosted.org/packages/8a/d1/b902d38b6e5ba3bdddbec469bba388d647f960aeed7b5b3623a8debe8a76/msgspec-0.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c1ff8db03be7598b50dd4b4a478d6fe93faae3bd54f4f17aa004d0e46c14c46", size = 196463 }, + { url = "https://files.pythonhosted.org/packages/57/b6/eff0305961a1d9447ec2b02f8c73c8946f22564d302a504185b730c9a761/msgspec-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f6532369ece217fd37c5ebcfd7e981f2615628c21121b7b2df9d3adcf2fd69b8", size = 188650 }, + { url = "https://files.pythonhosted.org/packages/99/93/f2ec1ae1de51d3fdee998a1ede6b2c089453a2ee82b5c1b361ed9095064a/msgspec-0.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9a1697da2f85a751ac3cc6a97fceb8e937fc670947183fb2268edaf4016d1ee", size = 218834 }, + { url = "https://files.pythonhosted.org/packages/28/83/36557b04cfdc317ed8a525c4993b23e43a8fbcddaddd78619112ca07138c/msgspec-0.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fac7e9c92eddcd24c19d9e5f6249760941485dff97802461ae7c995a2450111", size = 224917 }, + { url = "https://files.pythonhosted.org/packages/8f/56/362037a1ed5be0b88aced59272442c4b40065c659700f4b195a7f4d0ac88/msgspec-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f953a66f2a3eb8d5ea64768445e2bb301d97609db052628c3e1bcb7d87192a9f", size = 222821 }, + { url = "https://files.pythonhosted.org/packages/92/75/fa2370ec341cedf663731ab7042e177b3742645c5dd4f64dc96bd9f18a6b/msgspec-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:247af0313ae64a066d3aea7ba98840f6681ccbf5c90ba9c7d17f3e39dbba679c", size = 227227 }, + { url = "https://files.pythonhosted.org/packages/f1/25/5e8080fe0117f799b1b68008dc29a65862077296b92550632de015128579/msgspec-0.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:67d5e4dfad52832017018d30a462604c80561aa62a9d548fc2bd4e430b66a352", size = 189966 }, + { url = "https://files.pythonhosted.org/packages/79/b6/63363422153937d40e1cb349c5081338401f8529a5a4e216865decd981bf/msgspec-0.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:91a52578226708b63a9a13de287b1ec3ed1123e4a088b198143860c087770458", size = 175378 }, + { url = "https://files.pythonhosted.org/packages/bb/18/62dc13ab0260c7d741dda8dc7f481495b93ac9168cd887dda5929880eef8/msgspec-0.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:eead16538db1b3f7ec6e3ed1f6f7c5dec67e90f76e76b610e1ffb5671815633a", size = 196407 }, + { url = "https://files.pythonhosted.org/packages/dd/1d/b9949e4ad6953e9f9a142c7997b2f7390c81e03e93570c7c33caf65d27e1/msgspec-0.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:703c3bb47bf47801627fb1438f106adbfa2998fe586696d1324586a375fca238", size = 188889 }, + { url = "https://files.pythonhosted.org/packages/1e/19/f8bb2dc0f1bfe46cc7d2b6b61c5e9b5a46c62298e8f4d03bbe499c926180/msgspec-0.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6cdb227dc585fb109305cee0fd304c2896f02af93ecf50a9c84ee54ee67dbb42", size = 219691 }, + { url = "https://files.pythonhosted.org/packages/b8/8e/6b17e43f6eb9369d9858ee32c97959fcd515628a1df376af96c11606cf70/msgspec-0.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27d35044dd8818ac1bd0fedb2feb4fbdff4e3508dd7c5d14316a12a2d96a0de0", size = 224918 }, + { url = "https://files.pythonhosted.org/packages/1c/db/0e833a177db1a4484797adba7f429d4242585980b90882cc38709e1b62df/msgspec-0.20.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b4296393a29ee42dd25947981c65506fd4ad39beaf816f614146fa0c5a6c91ae", size = 223436 }, + { url = "https://files.pythonhosted.org/packages/c3/30/d2ee787f4c918fd2b123441d49a7707ae9015e0e8e1ab51aa7967a97b90e/msgspec-0.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:205fbdadd0d8d861d71c8f3399fe1a82a2caf4467bc8ff9a626df34c12176980", size = 227190 }, + { url = "https://files.pythonhosted.org/packages/ff/37/9c4b58ff11d890d788e700b827db2366f4d11b3313bf136780da7017278b/msgspec-0.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:7dfebc94fe7d3feec6bc6c9df4f7e9eccc1160bb5b811fbf3e3a56899e398a6b", size = 193950 }, + { url = "https://files.pythonhosted.org/packages/e9/4e/cab707bf2fa57408e2934e5197fc3560079db34a1e3cd2675ff2e47e07de/msgspec-0.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:2ad6ae36e4a602b24b4bf4eaf8ab5a441fec03e1f1b5931beca8ebda68f53fc0", size = 179018 }, + { url = "https://files.pythonhosted.org/packages/4c/06/3da3fc9aaa55618a8f43eb9052453cfe01f82930bca3af8cea63a89f3a11/msgspec-0.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f84703e0e6ef025663dd1de828ca028774797b8155e070e795c548f76dde65d5", size = 200389 }, + { url = "https://files.pythonhosted.org/packages/83/3b/cc4270a5ceab40dfe1d1745856951b0a24fd16ac8539a66ed3004a60c91e/msgspec-0.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7c83fc24dd09cf1275934ff300e3951b3adc5573f0657a643515cc16c7dee131", size = 193198 }, + { url = "https://files.pythonhosted.org/packages/cd/ae/4c7905ac53830c8e3c06fdd60e3cdcfedc0bbc993872d1549b84ea21a1bd/msgspec-0.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f13ccb1c335a124e80c4562573b9b90f01ea9521a1a87f7576c2e281d547f56", size = 225973 }, + { url = "https://files.pythonhosted.org/packages/d9/da/032abac1de4d0678d99eaeadb1323bd9d247f4711c012404ba77ed6f15ca/msgspec-0.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17c2b5ca19f19306fc83c96d85e606d2cc107e0caeea85066b5389f664e04846", size = 229509 }, + { url = "https://files.pythonhosted.org/packages/69/52/fdc7bdb7057a166f309e0b44929e584319e625aaba4771b60912a9321ccd/msgspec-0.20.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d931709355edabf66c2dd1a756b2d658593e79882bc81aae5964969d5a291b63", size = 230434 }, + { url = "https://files.pythonhosted.org/packages/cb/fe/1dfd5f512b26b53043884e4f34710c73e294e7cc54278c3fe28380e42c37/msgspec-0.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:565f915d2e540e8a0c93a01ff67f50aebe1f7e22798c6a25873f9fda8d1325f8", size = 231758 }, + { url = "https://files.pythonhosted.org/packages/97/f6/9ba7121b8e0c4e0beee49575d1dbc804e2e72467692f0428cf39ceba1ea5/msgspec-0.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:726f3e6c3c323f283f6021ebb6c8ccf58d7cd7baa67b93d73bfbe9a15c34ab8d", size = 206540 }, + { url = "https://files.pythonhosted.org/packages/c8/3e/c5187de84bb2c2ca334ab163fcacf19a23ebb1d876c837f81a1b324a15bf/msgspec-0.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:93f23528edc51d9f686808a361728e903d6f2be55c901d6f5c92e44c6d546bfc", size = 183011 }, +] + [[package]] name = "mypy" version = "1.18.2" @@ -407,7 +455,7 @@ wheels = [ [[package]] name = "openai" -version = "1.109.1" +version = "2.8.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -419,14 +467,14 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/a1/a303104dc55fc546a3f6914c842d3da471c64eec92043aef8f652eb6c524/openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869", size = 564133 } +sdist = { url = "https://files.pythonhosted.org/packages/d5/e4/42591e356f1d53c568418dc7e30dcda7be31dd5a4d570bca22acb0525862/openai-2.8.1.tar.gz", hash = "sha256:cb1b79eef6e809f6da326a7ef6038719e35aa944c42d081807bfa1be8060f15f", size = 602490 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/2a/7dd3d207ec669cacc1f186fd856a0f61dbc255d24f6fdc1a6715d6051b0f/openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315", size = 948627 }, + { url = "https://files.pythonhosted.org/packages/55/4f/dbc0c124c40cb390508a82770fb9f6e3ed162560181a85089191a851c59a/openai-2.8.1-py3-none-any.whl", hash = "sha256:c6c3b5a04994734386e8dad3c00a393f56d3b68a27cd2e8acae91a59e4122463", size = 1022688 }, ] [[package]] name = "openai-agents" -version = "0.3.2" +version = "0.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -437,9 +485,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/9f/dafa9f80653778179822e1abf77c7f0d9da5a16806c96b5bb9e0e46bd747/openai_agents-0.3.2.tar.gz", hash = "sha256:b71ac04ee9f502f1bc0f4d142407df4ec69db4442db86c4da252b4558fa90cd5", size = 1727988 } +sdist = { url = "https://files.pythonhosted.org/packages/88/dc/7323c8d96211f252a2ebb288782a8f40bef6b472878eeebb59b3097a5708/openai_agents-0.6.1.tar.gz", hash = "sha256:067d2b66669c390c840effeb02d80939b4ac4a4db53e9735b74895a6d916b840", size = 2011463 } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/7e/6a8437f9f40937bb473ceb120a65e1b37bc87bcee6da67be4c05b25c6a89/openai_agents-0.3.2-py3-none-any.whl", hash = "sha256:55e02c57f2aaf3170ff0aa0ab7c337c28fd06b43b3bb9edc28b77ffd8142b425", size = 194221 }, + { url = "https://files.pythonhosted.org/packages/11/53/d8076306f324992c79e9b2ee597f2ce863f0ac5d1fd24e6ad88f2a4dcbc0/openai_agents-0.6.1-py3-none-any.whl", hash = "sha256:7bde01c8d2fd723b0c72c9b207dcfeb12a8d211078f5d259945fb163a6f52b89", size = 237609 }, ] [[package]] @@ -462,7 +510,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.11.9" +version = "2.12.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -470,74 +518,106 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495 } +sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855 }, + { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400 }, ] [[package]] name = "pydantic-core" -version = "2.33.2" +version = "2.41.5" 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 } -wheels = [ - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538 }, - { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909 }, - { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 }, - { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 }, - { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873 }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826 }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869 }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890 }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740 }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021 }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378 }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761 }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303 }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355 }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875 }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549 }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305 }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902 }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990 }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003 }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200 }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578 }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504 }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816 }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366 }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698 }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603 }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591 }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068 }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908 }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145 }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179 }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403 }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206 }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307 }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258 }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917 }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186 }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164 }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146 }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788 }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133 }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852 }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679 }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766 }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005 }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622 }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725 }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040 }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691 }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897 }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302 }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877 }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680 }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960 }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102 }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039 }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126 }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489 }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288 }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255 }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760 }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092 }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385 }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832 }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585 }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078 }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914 }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560 }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244 }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955 }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906 }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607 }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769 }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441 }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291 }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632 }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905 }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495 }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388 }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879 }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017 }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980 }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865 }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256 }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762 }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141 }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317 }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992 }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302 }, ] [[package]] @@ -622,45 +702,37 @@ wheels = [ [[package]] name = "restate-sdk" -version = "0.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/5c/4dbce55d3d1a55f42636a539068adb3b3040a3bb1f8ebeeb66ff26ec2c8b/restate_sdk-0.11.0.tar.gz", hash = "sha256:2be22cd6223ada6eab7d7000a7112ad3565b858fb9a7ea5d003c30db553b2ea5", size = 70529 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/aa/08ea3f0d4ca70273a6093f475adfe27dfb93b1549b98840e13f715b27609/restate_sdk-0.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e191f8e5662c210a477c4ef285f246d71899869ea502b5d3500e5176db1fd8cf", size = 1816401 }, - { url = "https://files.pythonhosted.org/packages/f4/c7/68b04ae76c759f2448316d33a518d51efbc51c780b0a3cc4da05589f00b2/restate_sdk-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c40005e244800a17cb089ab7266cee21b8d7970143406855c3290fad2f2fba64", size = 1751760 }, - { url = "https://files.pythonhosted.org/packages/ec/21/a402f70f7b18662106d3ea6a5920f78857d89e70f5e5f5fd6d87427aa29b/restate_sdk-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbf34609bfda306658eb44e4f583164a36f5c8e97ad007b2c00499941ff82df", size = 1946519 }, - { url = "https://files.pythonhosted.org/packages/1c/c5/c86a490e70e39694680b044678ec80f28d1d9935c9fbaa6b56c3fe370bef/restate_sdk-0.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:1dfd2f03a494f48988b208fc5009f31707a47183059e90c7406dba95a1e8ddfc", size = 1976565 }, - { url = "https://files.pythonhosted.org/packages/3b/5c/b91e1b170e2bab94cb06351b2948b7e6da21db370db3382219494047b08c/restate_sdk-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:70c224f54cc42ed63320015d4b7abe0d905157112cb9d46b61a5f2126468fe08", size = 2169119 }, - { url = "https://files.pythonhosted.org/packages/28/af/b545b23c1e9646e1b760e5ade5e4d94427a15cc16d8801fee71f9793e89c/restate_sdk-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:08565d40ef623a6e2ba49b652ba3e42f80fec91e4f8fc73753d37fe2f49e1b40", size = 2183831 }, - { url = "https://files.pythonhosted.org/packages/3c/3f/5e32ab47a8ba7385f9f1f0afe5490e00e5be5db00c8868d1ef4fa939a696/restate_sdk-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f223e5c9cb4825ca0a6118d71a82ca41bc2146fe00999a05bedd8f5e013261e5", size = 1812958 }, - { url = "https://files.pythonhosted.org/packages/85/48/6bf225211159bfd18795de536803bc4a0b65464eeee101b500d390d298f0/restate_sdk-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e8afd86d044fa2cd8008bfdd4e8a8679f6455ef0a8bb71f762689527c14dca68", size = 1750270 }, - { url = "https://files.pythonhosted.org/packages/17/cd/cfbd33c199330477d0eea8c3c6d3cc520e70ec385c69295f3cf56039197a/restate_sdk-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1956346f58356fec8f827765b7b571657197f8481d3fe5d15bcd1888c5db86e6", size = 1947020 }, - { url = "https://files.pythonhosted.org/packages/b7/05/1f35b0e8a17983ab303cd40b45e30292c83afac8fcef93c84b482a93c9c5/restate_sdk-0.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:56b6ba5aac96600e2aeadda3deb0015486d94f27232963cb6064539289efb541", size = 1975985 }, - { url = "https://files.pythonhosted.org/packages/64/ad/f26ec899599fa2c3501b1166aebd8e66e9e13211bb2f02d67f19efdc900e/restate_sdk-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ed121ffad08c3e0542a4af435b6b3a5dd748bffeec527fc762af41381d4bcc9b", size = 2168992 }, - { url = "https://files.pythonhosted.org/packages/fd/27/b7bb6c21ae28c408bebf41a9221c52cc38737b1a3a6dfd18ae63fd763276/restate_sdk-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:851c3e37879f869c0b54d6f5f3e9d21da43dc1909a22cfb2a7482fa978e5dab9", size = 2184377 }, - { url = "https://files.pythonhosted.org/packages/10/94/1c63bce3efd4d8fb362e4b45234eaa650d56890747880e5c00ce8d327762/restate_sdk-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7a8d7c16afc0ad51b7cee0533385f35ab78654e49f7b0f08a151f406603388e9", size = 1813111 }, - { url = "https://files.pythonhosted.org/packages/57/a7/ce77d605e8d3bd9c69b0b7d3e79ef0521d69f6e58b99c0327fc466c3ac1c/restate_sdk-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e2ebe19e30bbe9ec9d7bce09935253c520c8e61f8f060f2c8120e2653e77bde", size = 1750136 }, - { url = "https://files.pythonhosted.org/packages/a8/0f/c4a6433792c3618069b5c7f5a6fbef488cd5e1fea16dff27352eb4d175e2/restate_sdk-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d732506f19fcc89fd4bfbf82737cd84a52e949c8535ee2b26fa1675bb9af2fba", size = 1947384 }, - { url = "https://files.pythonhosted.org/packages/42/6a/f4cb83e4fc1b6ff6627e3aa7678c2eb94b587460b12b67ddea9f9c0c3280/restate_sdk-0.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:29f44b18ad265e5fa16ef211cedea76ce47f6ff86290b5e6efbf7a16f419c37e", size = 1976482 }, - { url = "https://files.pythonhosted.org/packages/0f/b0/fadd219731204e9267f62f5cc95edef2784f7a5857219d491089ab5b3672/restate_sdk-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9e4a8ce67e46859269ebad611e1724d6d75a0eedc4bcb52b5bb7a45872caf9b6", size = 2169167 }, - { url = "https://files.pythonhosted.org/packages/c6/2b/359406be892a97022699b707a78291f38c4088f9da1c47e1dc91e97ff213/restate_sdk-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bc0f8ec6b003cd6e46581f453d9576a3fb59297942add88cf7e5ed37bc4193cc", size = 2184643 }, - { url = "https://files.pythonhosted.org/packages/ae/d1/f12807216f5c766074997dffb3dd87001ad6dbd4ec1e541a7a19df7f2322/restate_sdk-0.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e89543854ea99571267f1365730923082f7b7128139b261218f604dc1858bf54", size = 1976025 }, - { url = "https://files.pythonhosted.org/packages/1e/39/8ca25b27b49bec1fc6ead85d77ce6a497ce552ce7af1bb5618e9909fb59f/restate_sdk-0.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:93ea3f41c7f6455bfef43abfcccdedd8be030d59605ca4242b9171a30187b771", size = 2168800 }, - { url = "https://files.pythonhosted.org/packages/b4/4d/091078490999dddf52bdbe227c49b82eaaa7a43467cbc1c9719d83114cb6/restate_sdk-0.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ec3bdd69fe73b7f2de41752347482a940bdb48d6ba5523934870a38820c30457", size = 2183941 }, - { url = "https://files.pythonhosted.org/packages/cd/c2/e42a8935a008c6eb9bf75dc2735c51f3dc7bbd4745c98899ccafec56e945/restate_sdk-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5767bc1a37bdaded2cb3c327c1a88f7e6ebda8d18dbb30f948389a0f29b650ec", size = 1749389 }, - { url = "https://files.pythonhosted.org/packages/08/ca/4cf69c8eb46bd20cb5b98a1058d2f81ed0d0ffced135d2b8aea94ce7b078/restate_sdk-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bdb570e7bde2bc8c1587a60d082b2b1f5ac1d079b673bba95e21f03970c457d", size = 1947002 }, - { url = "https://files.pythonhosted.org/packages/d8/12/9e0f3adb8974c0a9671ed0503c971ffbd5a2d508ad0b4241cab171d1e621/restate_sdk-0.11.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729b92df5eb6eb0c2388cf24f6eeadd48ddb79801016e0f64ab2767d640165e", size = 1944944 }, - { url = "https://files.pythonhosted.org/packages/bc/7d/f79b0d28285e5a81d64460bee42d3de2a923aabf60ae015e6c266bd8434f/restate_sdk-0.11.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4a54f44d4f8eae619d8d42f91714cc27fff9feaf6d5d89dce7d7bdc599fa4cf9", size = 1976031 }, - { url = "https://files.pythonhosted.org/packages/eb/9a/ea8055fc21597239c2094988a59437c39b4961f0c4fa2217f690758128c1/restate_sdk-0.11.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:cd0200ae6624423033469ceb07d0249017e32ca8aa36f72c8ced212e4fb1d466", size = 2168776 }, - { url = "https://files.pythonhosted.org/packages/ff/23/9081883ffcd73e308e7094f620f255e3d2a108f85e050872fd7bee10e9a9/restate_sdk-0.11.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:fa5ba62e317c0a8eade34837855c9af066162a4753e703b78dd485857b812fa9", size = 2184795 }, +version = "0.12.0" +source = { path = "../../../sdk-python/dist/restate_sdk-0.12.0-cp313-cp313-linux_x86_64.whl" } +wheels = [ + { filename = "restate_sdk-0.12.0-cp313-cp313-linux_x86_64.whl", hash = "sha256:b23692f78d147a3bb031741670e39c86763ac3fbc5870cde79adba9d07c2ab21" }, ] [package.optional-dependencies] serde = [ { name = "dacite" }, + { name = "msgspec" }, { name = "pydantic" }, ] +[package.metadata] +requires-dist = [ + { name = "anyio", marker = "extra == 'test'" }, + { name = "dacite", marker = "extra == 'serde'" }, + { name = "httpx", marker = "extra == 'harness'" }, + { name = "httpx", extras = ["http2"], marker = "extra == 'client'" }, + { name = "hypercorn", marker = "extra == 'harness'" }, + { name = "hypercorn", marker = "extra == 'test'" }, + { name = "msgspec", marker = "extra == 'serde'" }, + { name = "mypy", marker = "extra == 'lint'", specifier = ">=1.11.2" }, + { name = "pydantic", marker = "extra == 'serde'" }, + { name = "pyright", marker = "extra == 'lint'", specifier = ">=1.1.390" }, + { name = "pytest", marker = "extra == 'test'" }, + { name = "ruff", marker = "extra == 'lint'", specifier = ">=0.6.9" }, + { name = "testcontainers", marker = "extra == 'harness'" }, +] +provides-extras = ["test", "lint", "harness", "serde", "client"] + [[package]] name = "rpds-py" version = "0.27.1" @@ -824,9 +896,9 @@ dev = [ requires-dist = [ { name = "httpx" }, { name = "hypercorn" }, - { name = "openai-agents", specifier = ">=0.3.2" }, + { name = "openai-agents", specifier = ">=0.6.1" }, { name = "pydantic", specifier = ">=2.11.9" }, - { name = "restate-sdk", extras = ["serde"], specifier = ">=0.11.0" }, + { name = "restate-sdk", extras = ["serde"], path = "../../../sdk-python/dist/restate_sdk-0.12.0-cp313-cp313-linux_x86_64.whl" }, ] [package.metadata.requires-dev] @@ -867,14 +939,14 @@ wheels = [ [[package]] name = "typing-inspection" -version = "0.4.1" +version = "0.4.2" 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 } +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949 } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611 }, ] [[package]] diff --git a/vercel-ai/tour-of-agents/src/mcp/agent.ts b/vercel-ai/tour-of-agents/src/mcp/agent.ts new file mode 100644 index 0000000..5674aae --- /dev/null +++ b/vercel-ai/tour-of-agents/src/mcp/agent.ts @@ -0,0 +1,35 @@ +import * as restate from "@restatedev/restate-sdk"; +import { durableCalls, superJson } from "@restatedev/vercel-ai-middleware"; + +import { openai } from "@ai-sdk/openai"; +import { generateText, ModelMessage, wrapLanguageModel } from "ai"; +import { handlers } from "@restatedev/restate-sdk"; +import shared = handlers.object.shared; + +export default restate.object({ + name: "Chat", + handlers: { + message: async (ctx: restate.ObjectContext, req: { message: string }) => { + const model = wrapLanguageModel({ + model: openai("gpt-4o"), + middleware: durableCalls(ctx, { maxRetryAttempts: 3 }), + }); + + const messages = + (await ctx.get("messages", superJson)) ?? []; + messages.push({ role: "user", content: req.message }); + + const res = await generateText({ + model, + system: "You are a helpful assistant.", + messages, + }); + + ctx.set("messages", [...messages, ...res.response.messages], superJson); + return { answer: res.text }; + }, + getHistory: shared(async (ctx: restate.ObjectSharedContext) => + ctx.get("messages", superJson), + ), + }, +}); From e94ea4347e826660c3474ecdc1babadbcba4d367 Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Thu, 27 Nov 2025 15:47:28 +0100 Subject: [PATCH 2/6] Improve runner abstraction --- openai-agents/tour-of-agents/__main__.py | 8 +- .../app/advanced/manual_loop_agent.py | 14 ++- .../app/advanced/rollback_agent.py | 27 +++-- openai-agents/tour-of-agents/app/chat.py | 8 +- .../tour-of-agents/app/durable_agent.py | 9 +- .../tour-of-agents/app/error_handling.py | 20 +--- .../app/human_approval_agent.py | 15 +-- .../app/human_approval_agent_with_timeout.py | 17 +-- .../tour-of-agents/app/multi_agent.py | 21 +--- .../tour-of-agents/app/multi_agent_remote.py | 20 +--- .../tour-of-agents/app/parallel_agents.py | 12 +- .../app/parallel_tools_agent.py | 18 +-- .../tour-of-agents/app/sub_workflow_agent.py | 14 +-- .../tour-of-agents/app/utils/middleware.py | 110 +++++++++++------- .../tour-of-agents/app/utils/models.py | 7 +- .../tour-of-agents/app/utils/utils.py | 58 +++++---- 16 files changed, 173 insertions(+), 205 deletions(-) diff --git a/openai-agents/tour-of-agents/__main__.py b/openai-agents/tour-of-agents/__main__.py index 1fd3861..ac93c19 100644 --- a/openai-agents/tour-of-agents/__main__.py +++ b/openai-agents/tour-of-agents/__main__.py @@ -20,7 +20,11 @@ from app.parallel_agents import agent_service as parallel_agent_claim_approval from app.parallel_tools_agent import agent_service as parallel_tool_claim_agent -from app.utils.utils import fraud_agent_service, rate_comparison_agent_service, eligibility_agent_service +from app.utils.utils import ( + fraud_agent_service, + rate_comparison_agent_service, + eligibility_agent_service, +) # Create Restate app with all tour services app = restate.app( @@ -47,7 +51,7 @@ # Utils fraud_agent_service, eligibility_agent_service, - rate_comparison_agent_service + rate_comparison_agent_service, ] ) diff --git a/openai-agents/tour-of-agents/app/advanced/manual_loop_agent.py b/openai-agents/tour-of-agents/app/advanced/manual_loop_agent.py index 9485bb9..db742bb 100644 --- a/openai-agents/tour-of-agents/app/advanced/manual_loop_agent.py +++ b/openai-agents/tour-of-agents/app/advanced/manual_loop_agent.py @@ -1,6 +1,11 @@ import restate -from openai.types.chat import ChatCompletion, ChatCompletionMessageParam, ChatCompletionMessageFunctionToolCall, \ - ChatCompletionToolMessageParam, ChatCompletionUserMessageParam +from openai.types.chat import ( + ChatCompletion, + ChatCompletionMessageParam, + ChatCompletionMessageFunctionToolCall, + ChatCompletionToolMessageParam, + ChatCompletionUserMessageParam, +) from pydantic import BaseModel from restate import Context from openai import OpenAI, pydantic_function_tool @@ -61,7 +66,10 @@ def llm_call() -> ChatCompletion: # Check if we need to call tools for tool_call in assistant_message.tool_calls: - if isinstance(tool_call, ChatCompletionMessageFunctionToolCall) and tool_call.function.name == "get_weather": + if ( + isinstance(tool_call, ChatCompletionMessageFunctionToolCall) + and tool_call.function.name == "get_weather" + ): req = WeatherRequest.model_validate_json(tool_call.function.arguments) tool_output = await ctx.run_typed( "Get weather", fetch_weather, city=req.city diff --git a/openai-agents/tour-of-agents/app/advanced/rollback_agent.py b/openai-agents/tour-of-agents/app/advanced/rollback_agent.py index 4e2af7d..ceca721 100644 --- a/openai-agents/tour-of-agents/app/advanced/rollback_agent.py +++ b/openai-agents/tour-of-agents/app/advanced/rollback_agent.py @@ -1,11 +1,11 @@ from typing import Callable import restate -from agents import Agent, RunConfig, Runner, function_tool, RunContextWrapper +from agents import Agent, RunConfig, Runner, RunContextWrapper from pydantic import Field, BaseModel, ConfigDict from restate import TerminalError -from app.utils.middleware import DurableModelCalls, raise_restate_errors +from app.utils.middleware import Runner, function_tool from app.utils.models import HotelBooking, FlightBooking, BookingPrompt, BookingResult from app.utils.utils import ( reserve_hotel, @@ -25,7 +25,7 @@ class BookingContext(BaseModel): # Functions raise terminal errors instead of feeding them back to the agent -@function_tool(failure_error_function=raise_restate_errors) +@function_tool async def book_hotel( wrapper: RunContextWrapper[BookingContext], booking: HotelBooking ) -> BookingResult: @@ -41,11 +41,14 @@ async def book_hotel( # Execute the workflow step return await booking_context.restate_context.run_typed( - "Book hotel", reserve_hotel, booking_id=booking_context.booking_id, booking=booking + "Book hotel", + reserve_hotel, + booking_id=booking_context.booking_id, + booking=booking, ) -@function_tool(failure_error_function=raise_restate_errors) +@function_tool async def book_flight( wrapper: RunContextWrapper[BookingContext], booking: FlightBooking ) -> BookingResult: @@ -58,7 +61,10 @@ async def book_flight( ) ) return await booking_context.restate_context.run_typed( - "Book flight", reserve_flight, booking_id=booking_context.booking_id, booking=booking + "Book flight", + reserve_flight, + booking_id=booking_context.booking_id, + booking=booking, ) @@ -83,14 +89,7 @@ async def book(restate_context: restate.Context, prompt: BookingPrompt) -> str: ) try: - result = await Runner.run( - booking_agent, - input=prompt.message, - context=booking_context, - run_config=RunConfig( - model="gpt-4o", model_provider=DurableModelCalls(restate_context) - ), - ) + result = await Runner.run(booking_agent, input=prompt.message) except TerminalError as e: # Run all the rollback actions on terminal errors for compensation in reversed(booking_context.on_rollback): diff --git a/openai-agents/tour-of-agents/app/chat.py b/openai-agents/tour-of-agents/app/chat.py index 5e86210..49d3564 100644 --- a/openai-agents/tour-of-agents/app/chat.py +++ b/openai-agents/tour-of-agents/app/chat.py @@ -10,13 +10,7 @@ @chat.handler() async def message(_ctx: ObjectContext, chat_message: ChatMessage) -> dict: result = await Runner.run( - Agent( - name="Assistant", - instructions="You are a helpful assistant.", - tools=[ - WebSearchTool() - ] - ), + Agent(name="Assistant", instructions="You are a helpful assistant."), input=chat_message.message, session=RestateSession(), ) diff --git a/openai-agents/tour-of-agents/app/durable_agent.py b/openai-agents/tour-of-agents/app/durable_agent.py index 6707d42..d9a1a46 100644 --- a/openai-agents/tour-of-agents/app/durable_agent.py +++ b/openai-agents/tour-of-agents/app/durable_agent.py @@ -6,16 +6,16 @@ RunContextWrapper, ModelSettings, ) -from restate import TerminalError from app.utils.middleware import Runner, function_tool -from app.utils.models import WeatherPrompt +from app.utils.models import WeatherPrompt, WeatherRequest, WeatherResponse from app.utils.utils import fetch_weather + @function_tool -async def get_weather(city: str) -> str: +async def get_weather(city: WeatherRequest) -> WeatherResponse: """Get the current weather for a given city.""" - return (await fetch_weather(city)).model_dump_json() + return await fetch_weather(city) weather_agent = Agent( @@ -26,6 +26,7 @@ async def get_weather(city: str) -> str: agent_service = restate.Service("WeatherAgent") + @agent_service.handler() async def run(restate_context: restate.Context, prompt: WeatherPrompt) -> str: result = await Runner.run(weather_agent, input=prompt.message) diff --git a/openai-agents/tour-of-agents/app/error_handling.py b/openai-agents/tour-of-agents/app/error_handling.py index 8b7e5c9..97afb5f 100644 --- a/openai-agents/tour-of-agents/app/error_handling.py +++ b/openai-agents/tour-of-agents/app/error_handling.py @@ -2,26 +2,21 @@ from agents import ( Agent, - RunConfig, - Runner, - function_tool, RunContextWrapper, - ModelSettings, ) -from app.utils.middleware import DurableModelCalls, raise_restate_errors +from app.utils.middleware import Runner, function_tool from app.utils.models import WeatherPrompt, WeatherRequest, WeatherResponse from app.utils.utils import fetch_weather # -@function_tool(failure_error_function=raise_restate_errors) +@function_tool async def get_weather( wrapper: RunContextWrapper[restate.Context], req: WeatherRequest ) -> WeatherResponse: """Get the current weather for a given city.""" - restate_context = wrapper.context - return await restate_context.run_typed("Get weather", fetch_weather, city=req.city) + return await fetch_weather(req.city) # @@ -42,14 +37,7 @@ async def run(restate_context: restate.Context, prompt: WeatherPrompt) -> str: # try: result = await Runner.run( - weather_agent, - input=prompt.message, - context=restate_context, - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context, max_retries=2), - model_settings=ModelSettings(parallel_tool_calls=False), - ), + weather_agent, input=prompt.message, context=restate_context ) except restate.TerminalError as e: # Handle terminal errors gracefully diff --git a/openai-agents/tour-of-agents/app/human_approval_agent.py b/openai-agents/tour-of-agents/app/human_approval_agent.py index ef6e29e..13bbfaa 100644 --- a/openai-agents/tour-of-agents/app/human_approval_agent.py +++ b/openai-agents/tour-of-agents/app/human_approval_agent.py @@ -1,14 +1,10 @@ import restate from agents import ( Agent, - RunConfig, - Runner, - function_tool, RunContextWrapper, - ModelSettings, ) -from app.utils.middleware import DurableModelCalls, raise_restate_errors +from app.utils.middleware import Runner, function_tool from app.utils.models import ClaimPrompt from app.utils.utils import ( InsuranceClaim, @@ -17,7 +13,7 @@ # -@function_tool(failure_error_function=raise_restate_errors) +@function_tool async def human_approval( wrapper: RunContextWrapper[restate.Context], claim: InsuranceClaim ) -> str: @@ -56,12 +52,7 @@ async def run(restate_context: restate.Context, prompt: ClaimPrompt) -> str: result = await Runner.run( claim_approval_agent, input=prompt.message, + disable_tool_autowrapping=True, context=restate_context, - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context), - model_settings=ModelSettings(parallel_tool_calls=False), - ), ) - return result.final_output diff --git a/openai-agents/tour-of-agents/app/human_approval_agent_with_timeout.py b/openai-agents/tour-of-agents/app/human_approval_agent_with_timeout.py index 7302204..b4ffd0c 100644 --- a/openai-agents/tour-of-agents/app/human_approval_agent_with_timeout.py +++ b/openai-agents/tour-of-agents/app/human_approval_agent_with_timeout.py @@ -3,14 +3,10 @@ import restate from agents import ( Agent, - RunConfig, - Runner, - function_tool, RunContextWrapper, - ModelSettings, ) -from app.utils.middleware import DurableModelCalls, raise_restate_errors +from app.utils.middleware import Runner, function_tool from app.utils.models import ClaimPrompt from app.utils.utils import ( InsuranceClaim, @@ -18,7 +14,7 @@ ) -@function_tool(failure_error_function=raise_restate_errors) +@function_tool async def human_approval( wrapper: RunContextWrapper[restate.Context], claim: InsuranceClaim ) -> str: @@ -63,12 +59,7 @@ async def run(restate_context: restate.Context, prompt: ClaimPrompt) -> str: result = await Runner.run( claim_approval_agent, input=prompt.message, - context=restate_context, - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context), - model_settings=ModelSettings(parallel_tool_calls=False), - ), + disable_tool_autowrapping=True, + context=restate_context ) - return result.final_output diff --git a/openai-agents/tour-of-agents/app/multi_agent.py b/openai-agents/tour-of-agents/app/multi_agent.py index b068cc3..5b07226 100644 --- a/openai-agents/tour-of-agents/app/multi_agent.py +++ b/openai-agents/tour-of-agents/app/multi_agent.py @@ -1,21 +1,21 @@ import restate -from agents import Agent, RunConfig, Runner, ModelSettings +from agents import Agent -from app.utils.middleware import DurableModelCalls, RestateSession +from app.utils.middleware import Runner, RestateSession from app.utils.utils import InsuranceClaim -intake_agent = Agent[restate.ObjectContext]( +intake_agent = Agent( name="IntakeAgent", instructions="Route insurance claims to the appropriate specialist: medical, auto, or property.", ) -medical_specialist = Agent[restate.ObjectContext]( +medical_specialist = Agent( name="MedicalSpecialist", handoff_description="I handle medical insurance claims from intake to final decision.", instructions="Review medical claims for coverage and necessity. Approve/deny up to $50,000.", ) -auto_specialist = Agent[restate.ObjectContext]( +auto_specialist = Agent( name="AutoSpecialist", handoff_description="I handle auto insurance claims from intake to final decision.", instructions="Assess auto claims for liability and damage. Approve/deny up to $25,000.", @@ -42,19 +42,10 @@ async def run(restate_context: restate.ObjectContext, claim: InsuranceClaim) -> ) last_agent = agent_dict.get(last_agent_name, intake_agent) - restate_session = await RestateSession.create( - session_id=restate_context.key(), ctx=restate_context - ) result = await Runner.run( last_agent, input=f"Claim: {claim.model_dump_json()}", - context=restate_context, - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context), - model_settings=ModelSettings(parallel_tool_calls=False), - ), - session=restate_session, + session=RestateSession(), ) restate_context.set("last_agent_name", result.last_agent.name) diff --git a/openai-agents/tour-of-agents/app/multi_agent_remote.py b/openai-agents/tour-of-agents/app/multi_agent_remote.py index 3f55fec..07ff1c2 100644 --- a/openai-agents/tour-of-agents/app/multi_agent_remote.py +++ b/openai-agents/tour-of-agents/app/multi_agent_remote.py @@ -1,19 +1,15 @@ import restate from agents import ( Agent, - RunConfig, - Runner, - ModelSettings, RunContextWrapper, - function_tool, ) -from app.utils.middleware import DurableModelCalls, raise_restate_errors +from app.utils.middleware import Runner, function_tool from app.utils.utils import InsuranceClaim, run_eligibility_agent, run_fraud_agent # Durable service call to the eligibility agent; persisted and retried by Restate -@function_tool(failure_error_function=raise_restate_errors) +@function_tool async def check_eligibility( wrapper: RunContextWrapper[restate.Context], claim: InsuranceClaim ) -> str: @@ -24,7 +20,7 @@ async def check_eligibility( # # Durable service call to the fraud agent; persisted and retried by Restate -@function_tool(failure_error_function=raise_restate_errors) +@function_tool async def check_fraud( wrapper: RunContextWrapper[restate.Context], claim: InsuranceClaim ) -> str: @@ -36,7 +32,7 @@ async def check_fraud( claim_approval_coordinator = Agent[restate.Context]( name="ClaimApprovalCoordinator", instructions="You are a claim approval engine. Analyze the claim and use your tools to decide whether to approve it.", - tools=[check_fraud, check_eligibility], + tools=[check_eligibility, check_fraud], ) agent_service = restate.Service("RemoteMultiAgentClaimApproval") @@ -47,14 +43,10 @@ async def run(restate_context: restate.Context, claim: InsuranceClaim) -> str: result = await Runner.run( claim_approval_coordinator, input=f"Claim: {claim.model_dump_json()}", + disable_tool_autowrapping=True, context=restate_context, - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context), - model_settings=ModelSettings(parallel_tool_calls=False), - ), ) return result.final_output -# \ No newline at end of file +# diff --git a/openai-agents/tour-of-agents/app/parallel_agents.py b/openai-agents/tour-of-agents/app/parallel_agents.py index 307e7d2..f97462d 100644 --- a/openai-agents/tour-of-agents/app/parallel_agents.py +++ b/openai-agents/tour-of-agents/app/parallel_agents.py @@ -1,12 +1,12 @@ import restate -from agents import Agent, RunConfig, Runner, ModelSettings +from agents import Agent, Runner -from app.utils.middleware import DurableModelCalls +from app.utils.middleware import Runner from app.utils.utils import ( InsuranceClaim, run_eligibility_agent, run_fraud_agent, - run_rate_comparison_agent + run_rate_comparison_agent, ) agent_service = restate.Service("ParallelAgentClaimApproval") @@ -32,14 +32,8 @@ async def run(restate_context: restate.Context, claim: InsuranceClaim) -> str: input=f"Decide about claim: {claim.model_dump_json()}. " "Base your decision on the following analyses:" f"Eligibility: {await eligibility} Cost {await cost} Fraud: {await fraud}", - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context), - model_settings=ModelSettings(parallel_tool_calls=False), - ), ) return result.final_output # - diff --git a/openai-agents/tour-of-agents/app/parallel_tools_agent.py b/openai-agents/tour-of-agents/app/parallel_tools_agent.py index de03719..d754ec9 100644 --- a/openai-agents/tour-of-agents/app/parallel_tools_agent.py +++ b/openai-agents/tour-of-agents/app/parallel_tools_agent.py @@ -1,14 +1,10 @@ import restate from agents import ( Agent, - RunConfig, - Runner, - function_tool, RunContextWrapper, - ModelSettings, ) -from app.utils.middleware import DurableModelCalls, raise_restate_errors +from app.utils.middleware import Runner, function_tool from app.utils.utils import ( InsuranceClaim, check_eligibility, @@ -18,7 +14,7 @@ # -@function_tool(failure_error_function=raise_restate_errors) +@function_tool async def calculate_metrics( wrapper: RunContextWrapper[restate.Context], claim: InsuranceClaim ) -> list[str]: @@ -51,13 +47,9 @@ async def calculate_metrics( async def run(restate_context: restate.Context, claim: InsuranceClaim) -> str: result = await Runner.run( parallel_tools_agent, - input=f"Analyze the claim {claim.model_dump_json()}." - "Use your tools to calculate key metrics and decide whether to approve.", + input=f"Analyze the claim {claim.model_dump_json()}." + "Use your tools to calculate key metrics and decide whether to approve.", + disable_tool_autowrapping=True, context=restate_context, - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context), - model_settings=ModelSettings(parallel_tool_calls=False), - ), ) return result.final_output diff --git a/openai-agents/tour-of-agents/app/sub_workflow_agent.py b/openai-agents/tour-of-agents/app/sub_workflow_agent.py index 00a819e..d2188aa 100644 --- a/openai-agents/tour-of-agents/app/sub_workflow_agent.py +++ b/openai-agents/tour-of-agents/app/sub_workflow_agent.py @@ -1,14 +1,10 @@ import restate from agents import ( Agent, - RunConfig, - Runner, - function_tool, RunContextWrapper, - ModelSettings, ) -from app.utils.middleware import DurableModelCalls, raise_restate_errors +from app.utils.middleware import Runner, function_tool from app.utils.models import ClaimPrompt from app.utils.utils import ( InsuranceClaim, @@ -42,7 +38,7 @@ async def request_approval( # -@function_tool(failure_error_function=raise_restate_errors) +@function_tool async def human_approval( wrapper: RunContextWrapper[restate.Context], claim: InsuranceClaim ) -> str: @@ -73,12 +69,8 @@ async def run(restate_context: restate.Context, prompt: ClaimPrompt) -> str: result = await Runner.run( sub_workflow_agent, input=prompt.message, + disable_tool_autowrapping=True, context=restate_context, - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context), - model_settings=ModelSettings(parallel_tool_calls=False), - ), ) return result.final_output diff --git a/openai-agents/tour-of-agents/app/utils/middleware.py b/openai-agents/tour-of-agents/app/utils/middleware.py index 5eef9e4..25b5df9 100644 --- a/openai-agents/tour-of-agents/app/utils/middleware.py +++ b/openai-agents/tour-of-agents/app/utils/middleware.py @@ -14,7 +14,11 @@ Runner as OpenAIRunner, RunConfig, TContext, - RunResult, Agent, AgentBase, + RunResult, + Agent, + AgentBase, + ModelBehaviorError, + UserError, ) from agents.function_schema import DocstringStyle from agents.models.multi_provider import MultiProvider @@ -28,7 +32,7 @@ from agents.tool_context import ToolContext from agents.util._types import MaybeAwaitable from pydantic import BaseModel -from restate import ObjectContext, TerminalError +from restate import TerminalError from restate.extensions import current_context @@ -111,7 +115,9 @@ def _ctx(self) -> restate.ObjectContext: async def get_items(self, limit: int | None = None) -> List[TResponseInputItem]: """Retrieve conversation history for this session.""" - current_items = await self._ctx().get("items", type_hint=List[TResponseInputItem]) or [] + current_items = ( + await self._ctx().get("items", type_hint=List[TResponseInputItem]) or [] + ) if limit is not None: return current_items[-limit:] return current_items @@ -150,7 +156,7 @@ def __init__(self, *args: object) -> None: super().__init__(*args) -def raise_restate_errors(context: RunContextWrapper[Any], error: Exception) -> str: +def raise_terminal_errors(context: RunContextWrapper[Any], error: Exception) -> str: """A custom function to provide a user-friendly error message.""" # Raise terminal errors and cancellations if isinstance(error, restate.TerminalError): @@ -158,12 +164,31 @@ def raise_restate_errors(context: RunContextWrapper[Any], error: Exception) -> s # so we create a new exception that inherits from both raise AgentsTerminalException(error.message) - # Raise suspensions - if isinstance(error, asyncio.CancelledError): - raise AgentsAsyncioSuspension(error) + if isinstance(error, ModelBehaviorError): + return f"An error occurred while calling the tool: {str(error)}" + + raise error + + +def continue_on_terminal_errors( + context: RunContextWrapper[Any], error: Exception +) -> str: + """A custom function to provide a user-friendly error message.""" + # Raise terminal errors and cancellations + if isinstance(error, restate.TerminalError): + # For the agent SDK it needs to be an AgentsException, for restate it needs to be a TerminalError + # so we create a new exception that inherits from both + return f"An error occurred while running the tool: {str(error)}" + + if isinstance(error, ModelBehaviorError): + return f"An error occurred while calling the tool: {str(error)}" + + raise error + - # Feed all other errors back to the agent - return default_tool_error_function(context, error) +def continue_on_all_errors(context: RunContextWrapper[Any], error: Exception) -> str: + """A custom function to provide a user-friendly error message.""" + return f"An error occurred while calling the tool: {str(error)}" @overload @@ -174,7 +199,7 @@ def function_tool( description_override: str | None = None, docstring_style: DocstringStyle | None = None, use_docstring_info: bool = True, - failure_error_function: ToolErrorFunction | None = None, + failure_error_function: ToolErrorFunction | None = raise_terminal_errors, strict_mode: bool = True, is_enabled: ( bool | Callable[[RunContextWrapper[Any], AgentBase], MaybeAwaitable[bool]] @@ -191,7 +216,7 @@ def function_tool( description_override: str | None = None, docstring_style: DocstringStyle | None = None, use_docstring_info: bool = True, - failure_error_function: ToolErrorFunction | None = None, + failure_error_function: ToolErrorFunction | None = raise_terminal_errors, strict_mode: bool = True, is_enabled: ( bool | Callable[[RunContextWrapper[Any], AgentBase], MaybeAwaitable[bool]] @@ -208,25 +233,16 @@ def function_tool( description_override: str | None = None, docstring_style: DocstringStyle | None = None, use_docstring_info: bool = True, - failure_error_function: ToolErrorFunction | None = default_tool_error_function, + failure_error_function: ToolErrorFunction | None = raise_terminal_errors, strict_mode: bool = True, is_enabled: ( bool | Callable[[RunContextWrapper[Any], AgentBase], MaybeAwaitable[bool]] ) = True, ) -> FunctionTool | Callable[[ToolFunction[...]], FunctionTool]: def _raise_suspensions(context: RunContextWrapper[Any], error: Exception) -> str: - """A custom function to provide a user-friendly error message.""" - # Raise terminal errors and cancellations - if isinstance(error, restate.TerminalError): - # For the agent SDK it needs to be an AgentsException, for restate it needs to be a TerminalError - # so we create a new exception that inherits from both - raise AgentsTerminalException(error.message) - - # Next Python SDK release will use CancelledError for suspensions + # Raise suspensions if isinstance(error, asyncio.CancelledError): raise AgentsAsyncioSuspension(error) - - # Feed all other errors back to the agent return failure_error_function(context, error) return agents.function_tool( @@ -268,17 +284,17 @@ async def run( current_run_config = run_config or RunConfig() new_run_config = dataclasses.replace( current_run_config, - # TODO allow wrapping an existing model provider? model_provider=DurableModelCalls(), ) - restate_agent = starting_agent if disable_tool_autowrapping else wrap_tools(starting_agent) + restate_agent = sequentialize_and_wrap_tools(starting_agent, disable_tool_autowrapping) return await OpenAIRunner.run( restate_agent, *args, run_config=new_run_config, **kwargs ) -def wrap_tools( +def sequentialize_and_wrap_tools( agent: Agent[TContext], + disable_tool_autowrapping: bool, ) -> Agent[TContext]: """ Wrap the tools of an agent to use the Restate error handling. @@ -288,32 +304,48 @@ def wrap_tools( """ # Restate does not allow parallel tool calls, so we use a lock to ensure sequential execution. + # This lock only affects tools for this agent; handoff agents are wrapped recursively. sequential_tools_lock = asyncio.Lock() wrapped_tools = [] for tool in agent.tools: if isinstance(tool, FunctionTool): - async def on_invoke_tool_wrapper( - tool_context: ToolContext[Any], tool_input: str - ) -> Any: - await sequential_tools_lock.acquire() - async def invoke(): - return await tool.on_invoke_tool(tool_context, tool_input) - try: - return await current_context().run_typed(tool.name, invoke) - except TerminalError as e: - raise AgentsTerminalException(e.message) - finally: - sequential_tools_lock.release() + + def create_wrapper(captured_tool): + async def on_invoke_tool_wrapper( + tool_context: ToolContext[Any], tool_input: Any + ) -> Any: + await sequential_tools_lock.acquire() + + async def invoke(): + result = await captured_tool.on_invoke_tool(tool_context, tool_input) + # Ensure Pydantic objects are serialized to dict for LLM compatibility + if hasattr(result, 'model_dump'): + return result.model_dump() + elif hasattr(result, 'dict'): + return result.dict() + return result + try: + if disable_tool_autowrapping: + return await invoke() + + return await current_context().run_typed(captured_tool.name, invoke) + finally: + sequential_tools_lock.release() + return on_invoke_tool_wrapper wrapped_tools.append( - dataclasses.replace(tool, on_invoke_tool=on_invoke_tool_wrapper) + dataclasses.replace(tool, on_invoke_tool=create_wrapper(tool)) ) else: wrapped_tools.append(tool) handoffs_with_wrapped_tools = [] for handoff in agent.handoffs: - handoffs_with_wrapped_tools.append(wrap_tools(handoff)) + # recursively wrap tools in handoff agents + handoffs_with_wrapped_tools.append(sequentialize_and_wrap_tools(handoff, disable_tool_autowrapping)) + + for t in wrapped_tools: + print(f"Wrapped tool: {t.name}\n Description: {t.description}\n") return agent.clone( tools=wrapped_tools, diff --git a/openai-agents/tour-of-agents/app/utils/models.py b/openai-agents/tour-of-agents/app/utils/models.py index ff15a28..9c94cfc 100644 --- a/openai-agents/tour-of-agents/app/utils/models.py +++ b/openai-agents/tour-of-agents/app/utils/models.py @@ -33,12 +33,13 @@ class InsuranceClaim(BaseModel): class WeatherRequest(BaseModel): """Request to get the weather for a city.""" - model_config = ConfigDict(extra='forbid') + model_config = ConfigDict(extra="forbid") city: str class WeatherResponse(BaseModel): """Request to get the weather for a city.""" + temperature: float description: str @@ -67,7 +68,9 @@ class BookingPrompt(BaseModel): """Booking request data structure.""" booking_id: str = "booking_123" - message: str = "I need to book a business trip to San Francisco from March 15-17. Flying from JFK, need a hotel downtown for 1 guest." + message: str = ( + "I need to book a business trip to San Francisco from March 15-17. Flying from JFK, need a hotel downtown for 1 guest." + ) class BookingResult(BaseModel): diff --git a/openai-agents/tour-of-agents/app/utils/utils.py b/openai-agents/tour-of-agents/app/utils/utils.py index 1f64ba1..0f35d18 100644 --- a/openai-agents/tour-of-agents/app/utils/utils.py +++ b/openai-agents/tour-of-agents/app/utils/utils.py @@ -1,6 +1,7 @@ import httpx import restate -from agents import Runner, Agent, RunConfig, ModelSettings +from app.utils.middleware import Runner, function_tool +from agents import Agent from openai.types.chat import ChatCompletionMessage, ChatCompletionAssistantMessageParam from restate import TerminalError @@ -10,14 +11,14 @@ InsuranceClaim, BookingResult, FlightBooking, - HotelBooking, + HotelBooking, WeatherRequest, ) # -async def fetch_weather(city: str) -> WeatherResponse: - fail_on_denver(city) - weather_data = await call_weather_api(city) +async def fetch_weather(req: WeatherRequest) -> WeatherResponse: + fail_on_denver(req.city) + weather_data = await call_weather_api(req.city) return parse_weather_data(weather_data) @@ -82,6 +83,7 @@ async def check_fraud(claim: InsuranceClaim) -> str: # Simple fraud detection based on claim characteristics return "low risk" + async def reserve_hotel(booking_id: str, booking: HotelBooking) -> BookingResult: """Reserve a hotel (simulated).""" print(f"🏨 Reserving hotel in {booking.name} for {booking.guests} guests") @@ -96,12 +98,15 @@ async def reserve_flight(booking_id: str, booking: FlightBooking) -> BookingResu print(f"✈️ Reserving flight from {booking.origin} to {booking.destination}") if booking.destination == "San Francisco" or booking.destination == "SFO": print(f"[👻 SIMULATED] Flight booking failed: No flights to SFO available...") - raise TerminalError(f"[👻 SIMULATED] Flight booking failed: No flights to SFO available...") + raise TerminalError( + f"[👻 SIMULATED] Flight booking failed: No flights to SFO available..." + ) return BookingResult( id=booking_id, - confirmation=f"Flight from {booking.origin} to {booking.destination} on {booking.date} for {booking.passengers} passengers" + confirmation=f"Flight from {booking.origin} to {booking.destination} on {booking.date} for {booking.passengers} passengers", ) + async def cancel_hotel(booking_id: str) -> None: """Cancel hotel booking.""" print(f"❌ Cancelling hotel booking {booking_id}") @@ -114,6 +119,7 @@ async def cancel_flight(booking_id: str) -> None: eligibility_agent_service = restate.Service("EligibilityAgent") + @eligibility_agent_service.handler() async def run_eligibility_agent( restate_context: restate.Context, claim: InsuranceClaim @@ -125,16 +131,13 @@ async def run_eligibility_agent( "Respond with eligible if it's a medical claim, and not eligible otherwise.", ), input=claim.model_dump_json(), - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context), - model_settings=ModelSettings(parallel_tool_calls=False), - ), ) return result.final_output + rate_comparison_agent_service = restate.Service("RateComparisonAgent") + @rate_comparison_agent_service.handler() async def run_rate_comparison_agent( restate_context: restate.Context, claim: InsuranceClaim @@ -142,20 +145,17 @@ async def run_rate_comparison_agent( result = await Runner.run( Agent( name="RateComparisonAgent", - instructions="Decide whether the cost of the claim is reasonable given the treatment." + - "Respond with reasonable or not reasonable.", + instructions="Decide whether the cost of the claim is reasonable given the treatment." + + "Respond with reasonable or not reasonable.", ), input=claim.model_dump_json(), - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context), - model_settings=ModelSettings(parallel_tool_calls=False), - ), ) return result.final_output + fraud_agent_service = restate.Service("FraudAgent") + @fraud_agent_service.handler() async def run_fraud_agent( restate_context: restate.Context, claim: InsuranceClaim @@ -167,21 +167,17 @@ async def run_fraud_agent( "Always respond with low risk, medium risk, or high risk.", ), input=claim.model_dump_json(), - run_config=RunConfig( - model="gpt-4o", - model_provider=DurableModelCalls(restate_context), - model_settings=ModelSettings(parallel_tool_calls=False), - ), ) return result.final_output def as_chat_completion_param(msg: ChatCompletionMessage): return ChatCompletionAssistantMessageParam( - role="assistant", - content=msg.content, - tool_calls=[ - {"id": tc.id, "type": tc.type, "function": tc.function} # type: ignore - for tc in (msg.tool_calls or []) - ] or None, - ) \ No newline at end of file + role="assistant", + content=msg.content, + tool_calls=[ + {"id": tc.id, "type": tc.type, "function": tc.function} # type: ignore + for tc in (msg.tool_calls or []) + ] + or None, + ) From 79ed32aa3e1f815788aff630f1439d8eaeeb3398 Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Thu, 27 Nov 2025 16:16:32 +0100 Subject: [PATCH 3/6] Add mcp and websearch examples --- openai-agents/tour-of-agents/__main__.py | 2 + openai-agents/tour-of-agents/app/chat.py | 8 ++-- .../tour-of-agents/app/durable_agent.py | 1 - openai-agents/tour-of-agents/app/mcp.py | 40 +++++++++++++++++++ .../tour-of-agents/app/utils/middleware.py | 3 -- 5 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 openai-agents/tour-of-agents/app/mcp.py diff --git a/openai-agents/tour-of-agents/__main__.py b/openai-agents/tour-of-agents/__main__.py index ac93c19..65811cb 100644 --- a/openai-agents/tour-of-agents/__main__.py +++ b/openai-agents/tour-of-agents/__main__.py @@ -17,6 +17,7 @@ ) from app.advanced.rollback_agent import agent_service as booking_with_rollback_agent from app.advanced.manual_loop_agent import manual_loop_agent +from app.mcp import chat as mcp_chat from app.parallel_agents import agent_service as parallel_agent_claim_approval from app.parallel_tools_agent import agent_service as parallel_tool_claim_agent @@ -44,6 +45,7 @@ # Advanced patterns booking_with_rollback_agent, manual_loop_agent, + mcp_chat, # Error handling # Parallel processing parallel_agent_claim_approval, diff --git a/openai-agents/tour-of-agents/app/chat.py b/openai-agents/tour-of-agents/app/chat.py index 49d3564..6c6fccb 100644 --- a/openai-agents/tour-of-agents/app/chat.py +++ b/openai-agents/tour-of-agents/app/chat.py @@ -1,4 +1,6 @@ -from agents import Agent, Runner, WebSearchTool +from typing import List + +from agents import Agent, Runner, TResponseInputItem from restate import VirtualObject, ObjectContext, ObjectSharedContext from app.utils.middleware import Runner, RestateSession @@ -18,6 +20,6 @@ async def message(_ctx: ObjectContext, chat_message: ChatMessage) -> dict: @chat.handler(kind="shared") -async def get_history(_ctx: ObjectSharedContext): +async def get_history(_ctx: ObjectSharedContext) -> List[TResponseInputItem]: session = RestateSession() - return session.get_items() + return await session.get_items() diff --git a/openai-agents/tour-of-agents/app/durable_agent.py b/openai-agents/tour-of-agents/app/durable_agent.py index d9a1a46..0b36f98 100644 --- a/openai-agents/tour-of-agents/app/durable_agent.py +++ b/openai-agents/tour-of-agents/app/durable_agent.py @@ -6,7 +6,6 @@ RunContextWrapper, ModelSettings, ) - from app.utils.middleware import Runner, function_tool from app.utils.models import WeatherPrompt, WeatherRequest, WeatherResponse from app.utils.utils import fetch_weather diff --git a/openai-agents/tour-of-agents/app/mcp.py b/openai-agents/tour-of-agents/app/mcp.py new file mode 100644 index 0000000..94e358b --- /dev/null +++ b/openai-agents/tour-of-agents/app/mcp.py @@ -0,0 +1,40 @@ +from typing import List + +from agents import Agent, Runner, WebSearchTool, HostedMCPTool, TResponseInputItem +from openai.types.responses.tool_param import Mcp +from restate import VirtualObject, ObjectContext, ObjectSharedContext + +from app.utils.middleware import Runner, RestateSession +from app.utils.models import ChatMessage + +chat = VirtualObject("McpChat") + + +@chat.handler() +async def message(_ctx: ObjectContext, chat_message: ChatMessage) -> dict: + result = await Runner.run( + Agent( + name="Assistant", + instructions="You are a helpful assistant.", + tools = [ + HostedMCPTool( + tool_config=Mcp( + type="mcp", + server_label="restate_docs", + server_description="A knowledge base about Restate's documentation.", + server_url="https://docs.restate.dev/mcp", + ) + ), + WebSearchTool() + ], + ), + input=chat_message.message, + session=RestateSession(), + ) + return result.final_output + + +@chat.handler(kind="shared") +async def get_history(_ctx: ObjectSharedContext) -> List[TResponseInputItem]: + session = RestateSession() + return await session.get_items() diff --git a/openai-agents/tour-of-agents/app/utils/middleware.py b/openai-agents/tour-of-agents/app/utils/middleware.py index 25b5df9..cdb7e65 100644 --- a/openai-agents/tour-of-agents/app/utils/middleware.py +++ b/openai-agents/tour-of-agents/app/utils/middleware.py @@ -344,9 +344,6 @@ async def invoke(): # recursively wrap tools in handoff agents handoffs_with_wrapped_tools.append(sequentialize_and_wrap_tools(handoff, disable_tool_autowrapping)) - for t in wrapped_tools: - print(f"Wrapped tool: {t.name}\n Description: {t.description}\n") - return agent.clone( tools=wrapped_tools, handoffs=handoffs_with_wrapped_tools, From aa5a68dd050f5ab1959e1edd604d2e7c646d4399 Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Fri, 28 Nov 2025 14:05:21 +0100 Subject: [PATCH 4/6] Add mcp examples --- .../tour-of-agents/app/{ => advanced}/mcp.py | 18 ++++-- .../app/advanced/mcp_with_approval.py | 61 +++++++++++++++++++ .../tour-of-agents/app/advanced/websearch.py | 37 +++++++++++ .../app/human_approval_agent_with_timeout.py | 2 +- .../tour-of-agents/app/utils/middleware.py | 23 ++++--- .../tour-of-agents/app/utils/utils.py | 12 +++- 6 files changed, 138 insertions(+), 15 deletions(-) rename openai-agents/tour-of-agents/app/{ => advanced}/mcp.py (84%) create mode 100644 openai-agents/tour-of-agents/app/advanced/mcp_with_approval.py create mode 100644 openai-agents/tour-of-agents/app/advanced/websearch.py diff --git a/openai-agents/tour-of-agents/app/mcp.py b/openai-agents/tour-of-agents/app/advanced/mcp.py similarity index 84% rename from openai-agents/tour-of-agents/app/mcp.py rename to openai-agents/tour-of-agents/app/advanced/mcp.py index 94e358b..8363864 100644 --- a/openai-agents/tour-of-agents/app/mcp.py +++ b/openai-agents/tour-of-agents/app/advanced/mcp.py @@ -1,6 +1,11 @@ from typing import List -from agents import Agent, Runner, WebSearchTool, HostedMCPTool, TResponseInputItem +from agents import ( + Agent, + Runner, + HostedMCPTool, + TResponseInputItem, +) from openai.types.responses.tool_param import Mcp from restate import VirtualObject, ObjectContext, ObjectSharedContext @@ -11,21 +16,22 @@ @chat.handler() -async def message(_ctx: ObjectContext, chat_message: ChatMessage) -> dict: +async def message(_ctx: ObjectContext, chat_message: ChatMessage) -> str: + result = await Runner.run( Agent( name="Assistant", instructions="You are a helpful assistant.", - tools = [ + tools=[ HostedMCPTool( tool_config=Mcp( type="mcp", server_label="restate_docs", server_description="A knowledge base about Restate's documentation.", server_url="https://docs.restate.dev/mcp", - ) - ), - WebSearchTool() + require_approval="never", + ), + ) ], ), input=chat_message.message, diff --git a/openai-agents/tour-of-agents/app/advanced/mcp_with_approval.py b/openai-agents/tour-of-agents/app/advanced/mcp_with_approval.py new file mode 100644 index 0000000..05f14f2 --- /dev/null +++ b/openai-agents/tour-of-agents/app/advanced/mcp_with_approval.py @@ -0,0 +1,61 @@ +from typing import List + +from agents import Agent, Runner, HostedMCPTool, TResponseInputItem, MCPToolApprovalRequest, \ + MCPToolApprovalFunctionResult +from openai.types.responses.tool_param import Mcp +from restate import VirtualObject, ObjectContext, ObjectSharedContext + +from app.utils.middleware import Runner, RestateSession +from app.utils.models import ChatMessage +from app.utils.utils import request_human_review, request_mcp_approval + +chat = VirtualObject("McpChat") + +async def approve_func(req: MCPToolApprovalRequest) -> MCPToolApprovalFunctionResult: + restate_context = req.ctx_wrapper.context + + # Request human review + approval_id, approval_promise = restate_context.awakeable(type_hint=bool) + await restate_context.run_typed( + "Approve MCP tool", request_mcp_approval, mcp_tool_name=req.data.name, awakeable_id=approval_id + ) + # Wait for human approval + approved = await approval_promise + if not approved: + return {"approve": approved, "reason": "User denied"} + return {"approve": approved} + + + +@chat.handler() +async def message(ctx: ObjectContext, chat_message: ChatMessage) -> str: + + result = await Runner.run( + Agent( + name="Assistant", + instructions="You are a helpful assistant.", + tools = [ + HostedMCPTool( + tool_config=Mcp( + type="mcp", + server_label="restate_docs", + server_description="A knowledge base about Restate's documentation.", + server_url="https://docs.restate.dev/mcp" + ), + on_approval_request=approve_func + # or use require_approval="never" in the tool_config to disable approvals + ) + ], + ), + input=chat_message.message, + session=RestateSession(), + context=ctx + ) + + return result.final_output + + +@chat.handler(kind="shared") +async def get_history(_ctx: ObjectSharedContext) -> List[TResponseInputItem]: + session = RestateSession() + return await session.get_items() diff --git a/openai-agents/tour-of-agents/app/advanced/websearch.py b/openai-agents/tour-of-agents/app/advanced/websearch.py new file mode 100644 index 0000000..854710d --- /dev/null +++ b/openai-agents/tour-of-agents/app/advanced/websearch.py @@ -0,0 +1,37 @@ +from typing import List + +from agents import ( + Agent, + Runner, + WebSearchTool, + TResponseInputItem, +) +from restate import VirtualObject, ObjectContext, ObjectSharedContext + +from app.utils.middleware import Runner, RestateSession +from app.utils.models import ChatMessage + +chat = VirtualObject("WebsearchChat") + + +@chat.handler() +async def message(restate_context: ObjectContext, chat_message: ChatMessage) -> str: + + result = await Runner.run( + Agent( + name="Assistant", + instructions="You are a helpful assistant.", + tools=[ + WebSearchTool() + ], + ), + input=chat_message.message, + session=RestateSession(), + ) + return result.final_output + + +@chat.handler(kind="shared") +async def get_history(_ctx: ObjectSharedContext) -> List[TResponseInputItem]: + session = RestateSession() + return await session.get_items() diff --git a/openai-agents/tour-of-agents/app/human_approval_agent_with_timeout.py b/openai-agents/tour-of-agents/app/human_approval_agent_with_timeout.py index b4ffd0c..bc5f386 100644 --- a/openai-agents/tour-of-agents/app/human_approval_agent_with_timeout.py +++ b/openai-agents/tour-of-agents/app/human_approval_agent_with_timeout.py @@ -60,6 +60,6 @@ async def run(restate_context: restate.Context, prompt: ClaimPrompt) -> str: claim_approval_agent, input=prompt.message, disable_tool_autowrapping=True, - context=restate_context + context=restate_context, ) return result.final_output diff --git a/openai-agents/tour-of-agents/app/utils/middleware.py b/openai-agents/tour-of-agents/app/utils/middleware.py index cdb7e65..c9d37d7 100644 --- a/openai-agents/tour-of-agents/app/utils/middleware.py +++ b/openai-agents/tour-of-agents/app/utils/middleware.py @@ -32,7 +32,6 @@ from agents.tool_context import ToolContext from agents.util._types import MaybeAwaitable from pydantic import BaseModel -from restate import TerminalError from restate.extensions import current_context @@ -286,7 +285,9 @@ async def run( current_run_config, model_provider=DurableModelCalls(), ) - restate_agent = sequentialize_and_wrap_tools(starting_agent, disable_tool_autowrapping) + restate_agent = sequentialize_and_wrap_tools( + starting_agent, disable_tool_autowrapping + ) return await OpenAIRunner.run( restate_agent, *args, run_config=new_run_config, **kwargs ) @@ -317,20 +318,26 @@ async def on_invoke_tool_wrapper( await sequential_tools_lock.acquire() async def invoke(): - result = await captured_tool.on_invoke_tool(tool_context, tool_input) + result = await captured_tool.on_invoke_tool( + tool_context, tool_input + ) # Ensure Pydantic objects are serialized to dict for LLM compatibility - if hasattr(result, 'model_dump'): + if hasattr(result, "model_dump"): return result.model_dump() - elif hasattr(result, 'dict'): + elif hasattr(result, "dict"): return result.dict() return result + try: if disable_tool_autowrapping: return await invoke() - return await current_context().run_typed(captured_tool.name, invoke) + return await current_context().run_typed( + captured_tool.name, invoke + ) finally: sequential_tools_lock.release() + return on_invoke_tool_wrapper wrapped_tools.append( @@ -342,7 +349,9 @@ async def invoke(): handoffs_with_wrapped_tools = [] for handoff in agent.handoffs: # recursively wrap tools in handoff agents - handoffs_with_wrapped_tools.append(sequentialize_and_wrap_tools(handoff, disable_tool_autowrapping)) + handoffs_with_wrapped_tools.append( + sequentialize_and_wrap_tools(handoff, disable_tool_autowrapping) + ) return agent.clone( tools=wrapped_tools, diff --git a/openai-agents/tour-of-agents/app/utils/utils.py b/openai-agents/tour-of-agents/app/utils/utils.py index 0f35d18..02067cb 100644 --- a/openai-agents/tour-of-agents/app/utils/utils.py +++ b/openai-agents/tour-of-agents/app/utils/utils.py @@ -11,7 +11,8 @@ InsuranceClaim, BookingResult, FlightBooking, - HotelBooking, WeatherRequest, + HotelBooking, + WeatherRequest, ) @@ -67,6 +68,15 @@ async def request_human_review(claim: InsuranceClaim, awakeable_id: str) -> None ) +async def request_mcp_approval(mcp_tool_name: str, awakeable_id: str) -> None: + """Simulate requesting human review.""" + print(f"🔔 Human review requested: {mcp_tool_name}") + print(f" Submit your mcp tool approval via: \n ") + print( + f" curl localhost:8080/restate/awakeables/{awakeable_id}/resolve --json true" + ) + + # Additional utility functions for parallel processing async def check_eligibility(claim: InsuranceClaim) -> str: """Check claim eligibility (simplified version).""" From cbd0c14bbf724f28bebe95e2b34a55b92f95fd55 Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Fri, 28 Nov 2025 14:07:25 +0100 Subject: [PATCH 5/6] bind websearch and mcp approval agents --- openai-agents/tour-of-agents/__main__.py | 6 +++++- .../tour-of-agents/app/advanced/mcp_with_approval.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openai-agents/tour-of-agents/__main__.py b/openai-agents/tour-of-agents/__main__.py index 65811cb..869fbba 100644 --- a/openai-agents/tour-of-agents/__main__.py +++ b/openai-agents/tour-of-agents/__main__.py @@ -17,7 +17,9 @@ ) from app.advanced.rollback_agent import agent_service as booking_with_rollback_agent from app.advanced.manual_loop_agent import manual_loop_agent -from app.mcp import chat as mcp_chat +from app.advanced.mcp import chat as mcp_chat +from app.advanced.mcp_with_approval import chat as mcp_with_approvals_chat +from app.advanced.websearch import chat as websearch_chat from app.parallel_agents import agent_service as parallel_agent_claim_approval from app.parallel_tools_agent import agent_service as parallel_tool_claim_agent @@ -46,6 +48,8 @@ booking_with_rollback_agent, manual_loop_agent, mcp_chat, + mcp_with_approvals_chat, + websearch_chat, # Error handling # Parallel processing parallel_agent_claim_approval, diff --git a/openai-agents/tour-of-agents/app/advanced/mcp_with_approval.py b/openai-agents/tour-of-agents/app/advanced/mcp_with_approval.py index 05f14f2..53fed9a 100644 --- a/openai-agents/tour-of-agents/app/advanced/mcp_with_approval.py +++ b/openai-agents/tour-of-agents/app/advanced/mcp_with_approval.py @@ -9,7 +9,7 @@ from app.utils.models import ChatMessage from app.utils.utils import request_human_review, request_mcp_approval -chat = VirtualObject("McpChat") +chat = VirtualObject("McpWithApprovalsChat") async def approve_func(req: MCPToolApprovalRequest) -> MCPToolApprovalFunctionResult: restate_context = req.ctx_wrapper.context From 0f15a5b73ed5a7b0137d6acf4a919b9eb279c902 Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Fri, 28 Nov 2025 14:12:59 +0100 Subject: [PATCH 6/6] Cleanup --- openai-agents/tour-of-agents/app/utils/middleware.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openai-agents/tour-of-agents/app/utils/middleware.py b/openai-agents/tour-of-agents/app/utils/middleware.py index c9d37d7..2a43afc 100644 --- a/openai-agents/tour-of-agents/app/utils/middleware.py +++ b/openai-agents/tour-of-agents/app/utils/middleware.py @@ -8,7 +8,6 @@ from agents import ( Usage, Model, - default_tool_error_function, RunContextWrapper, AgentsException, Runner as OpenAIRunner, @@ -18,7 +17,6 @@ Agent, AgentBase, ModelBehaviorError, - UserError, ) from agents.function_schema import DocstringStyle from agents.models.multi_provider import MultiProvider