diff --git a/README.md b/README.md index ceaf7a3..41dd0f0 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Misc extension to [akd-core](https://github.com/NASA-IMPACT/accelerated-discovery/). +## Documentation + +- [Creating Agents](docs/development/creating_agents.md) — guide for building new agents on `OpenAIBaseAgent` or `PydanticAIBaseAgent`, including config, schemas, tools, capabilities, tests, and reference examples. + ## Installation ### Using uv (recommended) diff --git a/akd_ext/agents/_base/__init__.py b/akd_ext/agents/_base/__init__.py new file mode 100644 index 0000000..cc20aa2 --- /dev/null +++ b/akd_ext/agents/_base/__init__.py @@ -0,0 +1,17 @@ +"""Base agent classes for akd_ext. + +Two families live here: + +- :class:`OpenAIBaseAgent` — built on the OpenAI Agents SDK (``openai``). +- :class:`PydanticAIBaseAgent` — built on :class:`pydantic_ai.Agent`. +""" + +from akd_ext.agents._base.openai import OpenAIBaseAgent, OpenAIBaseAgentConfig +from akd_ext.agents._base.pydantic_ai import PydanticAIBaseAgent, PydanticAIBaseAgentConfig + +__all__ = [ + "OpenAIBaseAgent", + "OpenAIBaseAgentConfig", + "PydanticAIBaseAgent", + "PydanticAIBaseAgentConfig", +] diff --git a/akd_ext/agents/_base.py b/akd_ext/agents/_base/openai.py similarity index 100% rename from akd_ext/agents/_base.py rename to akd_ext/agents/_base/openai.py diff --git a/akd_ext/agents/_base/pydantic_ai/__init__.py b/akd_ext/agents/_base/pydantic_ai/__init__.py new file mode 100644 index 0000000..5214fe1 --- /dev/null +++ b/akd_ext/agents/_base/pydantic_ai/__init__.py @@ -0,0 +1,20 @@ +"""Pydantic AI-backed agent base class for akd_ext. + +Public API: + +- :class:`PydanticAIBaseAgent` — subclass this to build new agents. +- :class:`PydanticAIBaseAgentConfig` — extend for subclass-specific config. + +The subpackage also exposes internal helpers (``_tool_adapter``, +``_context_adapter``, ``_event_translator``, ``_capabilities``) that consumers +typically don't need to import directly. Structural protocols +(``AKDExecutable``, ``AKDTool``, ``RunContextProtocol``, ``TokenCounts``) are +sourced from ``akd._base.protocols``. +""" + +from ._base import PydanticAIBaseAgent, PydanticAIBaseAgentConfig + +__all__ = [ + "PydanticAIBaseAgent", + "PydanticAIBaseAgentConfig", +] diff --git a/akd_ext/agents/_base/pydantic_ai/_base.py b/akd_ext/agents/_base/pydantic_ai/_base.py new file mode 100644 index 0000000..e8b5b5d --- /dev/null +++ b/akd_ext/agents/_base/pydantic_ai/_base.py @@ -0,0 +1,404 @@ +"""``PydanticAIBaseAgent`` — Pydantic AI-backed agent conforming to the AKD contract. + +Subclasses ``pydantic_ai.Agent`` directly and also explicitly +inherits akd-core's ``AKDExecutable`` Protocol +so ``isinstance(agent, AKDExecutable)`` works at runtime. + +Consumers use the same AKD pattern they're used to: + +.. code-block:: python + + class MyAgent(PydanticAIBaseAgent[MyIn, MyOut]): + input_schema = MyIn + output_schema = MyOut | TextOutput + config_schema = MyConfig + + agent = MyAgent(MyConfig(model_name="openai:gpt-5.2")) + result = await agent.arun(MyIn(query="...")) + +Config auto-exposure is handled by ``ConfigBindingMixin`` — ``agent.model_name``, +``agent.description``, etc. route to ``self.config.*`` without per-field +property definitions. + +The single opt-out is ``system_prompt`` (re-bound to +pydantic_ai's decorator method so it isn't shadowed by an auto-property). +""" + +from __future__ import annotations + +from collections.abc import AsyncIterator +from typing import Any + +from pydantic import ConfigDict, Field, model_validator +from pydantic_ai import Agent as PAIAgent +from pydantic_ai import AgentRunResultEvent, ModelRetry +from pydantic_ai import RunContext as PAIRunContext +from pydantic_ai.capabilities import AbstractCapability +from pydantic_ai.capabilities.hooks import Hooks +from pydantic_ai.messages import ModelMessage +from pydantic_ai.result import RunUsage as PAIRunUsage + +from akd._base import ( + CompletedEvent, + CompletedEventData, + ConfigBindingMixin, + InputSchema, + OutputSchema, + StreamEvent, + TextOutput, +) +from akd._base.protocols import AKDExecutable, RunContextProtocol +from akd._base.structures import RunContext as AKDRunContext +from akd.agents._base import BaseAgentConfig +from akd.tools._base import BaseTool +from ._utils import akd_to_pai_tool, pai_event_to_akd_event + +# --------------------------------------------------------------------------- +# Config +# --------------------------------------------------------------------------- + + +class PydanticAIBaseAgentConfig(BaseAgentConfig): + """AKD-style config that is also a superset of ``pydantic_ai.Agent`` kwargs. + + Inherits the full ``BaseAgentConfig`` surface (``model_name``, ``system_prompt``, + ``tools``, ``reasoning_effort``, etc.) and adds pydantic_ai-specific fields like capabilities. + ``extra="allow"`` forwards any additional future pydantic_ai kwargs via + ``model_extra`` without requiring this class to be updated. + """ + + model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) + + capabilities: list[Any] = Field( + default_factory=list, + description=( + "Pydantic AI capabilities (Thinking, WebSearch, MCP, Hooks, custom). " + "Merged with any capabilities auto-derived from AKD scalar fields." + ), + ) + + # -- Silence AKD-core's litellm-based config validators -------------- + # The following validator help for lookups that expect + # litellm's bare model names (e.g. ``gpt-5.2``), not the + # ``provider:model`` format pydantic_ai requires (e.g. ``openai:gpt-5.2``), + # so they emit misleading ERROR / WARNING logs for every construction. + # pydantic_ai handles model resolution itself, so we override both to no-ops. + + @model_validator(mode="after") + def validate_max_tokens_against_model(self): + return self + + @model_validator(mode="after") + def validate_reasoning_params(self): + return self + + +# --------------------------------------------------------------------------- +# Agent +# --------------------------------------------------------------------------- + + +class PydanticAIBaseAgent[InSchema: InputSchema, OutSchema: OutputSchema]( + ConfigBindingMixin, + PAIAgent, + AKDExecutable, +): + """Pydantic AI-backed agent conforming to the AKD ``AKDExecutable`` protocol. + + Subclass this class to build new agents. Subclasses override: + + - ``input_schema`` / ``output_schema`` / ``config_schema`` — class attrs + - ``check_output`` — semantic output validation (optional) + + .. Note (warning):: + + As suggested by pydanticAI Agents we also follow Single-run-per-instance. + Every stream event this agent emits carries + pydantic_ai's live ``RunContext`` (captured via a ``Hooks`` capability + and stored on ``self._live_pai_ctx``). Running two concurrent + ``arun`` / ``astream`` calls on the *same* agent instance will cause + the captured context to race between runs and misattribute + ``event.run_context`` on emitted events. + + For batch or multi-tenant workloads, construct a fresh agent per run, or switch to the + queue-based capture mechanism tracked in follow-up item + "Concurrency-safe run_context capture". + + This is because of current (simple) _build_run_context_capture implementation. + """ + + # Subclasses override these three class attributes. + input_schema: type[InSchema] = InputSchema + output_schema: type[OutSchema] = OutputSchema + config_schema: type[PydanticAIBaseAgentConfig] = PydanticAIBaseAgentConfig + + # ── Construction ────────────────────────────────────────────────────── + + def __init__(self, config: PydanticAIBaseAgentConfig | None = None) -> None: + self.config = config or self.config_schema() + + # Forward-compat: any unknown fields the caller put on the config + # (via ``extra="allow"``) pass straight through to pydantic_ai. + extra_kwargs = dict(self.config.model_extra or {}) + + # Latest pydantic_ai ``RunContext`` observed by our hooks. ``astream`` + # attaches this (wrapped in an AKD ``RunContext``) to every emitted + # event so downstream consumers can drive multi-turn conversations. + # See the class docstring for the concurrency caveat. + self._live_pai_ctx: Any = None + ctx_capture = self._build_run_context_capture() + + super().__init__( + model=self.config.model_name, + system_prompt=self.config.system_prompt, + name=self.config.name, + description=self.config.description, + retries=self.config.num_retries, + output_type=self.output_schema, + tools=self._adapt_tools(self.config.tools), + capabilities=[ + ctx_capture, + *self._build_capabilities_from_scalars(), + *self.config.capabilities, + ], + **extra_kwargs, + ) + + self._register_akd_output_validator() + + def _build_run_context_capture(self) -> Hooks: + """Install a ``Hooks`` capability that captures pydantic_ai's live + ``RunContext`` onto ``self._live_pai_ctx`` whenever the agent reaches a + model request or a tool execution. + + The first hook fires *before* any stream event escapes the run, so by + the time ``astream`` yields its first translated event we already + have a populated context. Both hooks observe-and-return so they don't + alter pydantic_ai's own request/argument flow. + """ + hooks = Hooks() + + @hooks.on.before_model_request + async def _grab_on_model_request(ctx, request_context): + self._live_pai_ctx = ctx + return request_context + + @hooks.on.before_tool_execute + async def _grab_on_tool_execute(ctx, *, call, tool_def, args): + self._live_pai_ctx = ctx + return args + + return hooks + + # ── AKD contract: arun / astream ────────────────────────────────────── + + async def arun( + self, + params: InSchema, + run_context: RunContextProtocol | None = None, + **kwargs: Any, + ) -> OutSchema: + """AKD entry point. Bridges ``InputSchema`` → ``pydantic_ai.Agent.run`` → ``OutputSchema``.""" + prompt = params.model_dump_json(indent=2) + result = await self.run( + prompt, + deps=run_context.deps + if isinstance(run_context, PAIRunContext) + else None, # AKD run contexts don't carry deps. If a caller passes a ``pydantic_ai.RunContext`` directly, forward its ``deps`` field so tool authors can use pydantic_ai-native dependency injection. Subclasses override to inject their own deps construction. + message_history=_message_history_from_run_context(run_context), + usage=_usage_from_run_context(run_context), + **kwargs, + ) + return result.output + + async def astream( + self, + params: InSchema, + run_context: RunContextProtocol | None = None, + **kwargs: Any, + ) -> AsyncIterator[StreamEvent]: + """AKD stream entry point. Translates pydantic_ai events → AKD ``StreamEvent``.""" + + # Drop legacy AKD kwargs that pydantic_ai's run_stream_events doesn't accept. + # 1. ``token_batch_size`` was a no-op on the OpenAI SDK runner; + # nothing in pydantic_ai batches events by count. Call sites + # should stop passing it, but we strip defensively for backward compat. + kwargs.pop("token_batch_size", None) + + prompt = params.model_dump_json(indent=2) + async for pai_event in self.run_stream_events( + prompt, + deps=run_context.deps + if isinstance(run_context, PAIRunContext) + else None, # AKD run contexts don't carry deps. If a caller passes a ``pydantic_ai.RunContext`` directly, forward its ``deps`` field so tool authors can use pydantic_ai-native dependency injection. Subclasses override to inject their own deps construction. + message_history=_message_history_from_run_context(run_context), + usage=_usage_from_run_context(run_context), + **kwargs, + ): + # Terminal result event → emit AKD CompletedEvent with the output. + if isinstance(pai_event, AgentRunResultEvent): + if pai_event.result.output is not None: + yield CompletedEvent( + data=CompletedEventData(output=pai_event.result.output), + run_context=self._wrap_pai_ctx, + ) + continue + akd_event = pai_event_to_akd_event( + pai_event, + run_context=self._wrap_pai_ctx, + ) + if akd_event is not None: + yield akd_event + + # ── RunContext wrapping ─────────────────────────────────────────────── + + @property + def _wrap_pai_ctx(self) -> AKDRunContext: + """Wrap ``self._live_pai_ctx`` in an AKD ``RunContext``. + + Mirrors pai's message history onto ``RunContext.messages`` so backend + serializers and downstream consumers see the conversation directly, + and keeps the live pai ``RunContext`` under the ``pai_run_context`` + extra for lossless round-trip continuation (usage, deps, etc.). + """ + pai_ctx = self._live_pai_ctx + messages = list(pai_ctx.messages) if pai_ctx is not None else None + return AKDRunContext(messages=messages, pai_run_context=pai_ctx) + + @property + def last_run_context(self) -> AKDRunContext | None: + """AKD ``RunContext`` reflecting the latest captured pai state. + + Returns ``None`` before any run has happened on this instance. + + ``arun`` returns only ``OutputSchema`` per the AKD contract, so this + property is the canonical way for ``arun`` callers to obtain a + continuation-ready context: + + .. code-block:: python + + output_1 = await agent.arun(InputSchema(query="first turn")) + ctx = agent.last_run_context # populated AKD ctx + output_2 = await agent.arun( # multi-turn + InputSchema(query="follow-up"), run_context=ctx, + ) + + Each access re-wraps the current ``self._live_pai_ctx`` (pai mutates + its own state in place during a run, so the property always reflects + the latest hook firing). Never silently consulted as a fallback when + the caller passes ``run_context=None`` — ``None`` means fresh + conversation, explicit opt-in required for continuation. + """ + if self._live_pai_ctx is None: + return None + return self._wrap_pai_ctx + + # ── Run-context helpers ─────────────────────────────────────────────── + + # ── scalar-driven capability construction ──────────────────── + + def _build_capabilities_from_scalars(self) -> list[AbstractCapability]: + """Derive capabilities from (scalar) AKD config fields. + + Subclasses override to append their own scalar→capability mappings; + call ``super()._build_capabilities_from_scalars()`` first to inherit + future defaults. + + TLDR; this is to map configs to a capability in pydanticAI + """ + return [] + + # ── Tool adaptation ────────────────────────────────────────────────── + + def _adapt_tools(self, tools: list) -> list: + """Convert AKD ``BaseTool`` instances to pydantic_ai ``Tool`` objects. + + Native pydantic_ai tools (``pydantic_ai.Tool`` instances, decorated + functions, toolsets, etc.) pass through unchanged. Anything structurally + conforming to ``AKDTool`` gets wrapped via ``akd_to_pai_tool``. + """ + adapted = [] + for tool in tools: + if isinstance(tool, BaseTool): + adapted.append(akd_to_pai_tool(tool)) + else: + adapted.append(tool) + return adapted + + # ── check_output bridge ────────────────────────────────────────────── + + def _register_akd_output_validator(self) -> None: + """Wire ``self.check_output`` up as a pydantic_ai output validator. + + Runs once at ``__init__`` time. The closure captures ``self`` and + late-binds ``self.check_output``, so subclass overrides are picked + up polymorphically. + """ + + @self.output_validator + def _akd_check(output): + msg = self.check_output(output) + if msg is not None: + raise ModelRetry(msg) + return output + + def check_output(self, output) -> str | None: + """Semantic output validation beyond pydantic_ai's schema enforcement. + + Default: + + - ``TextOutput`` instances pass through untouched (agent is asking a + clarifying question; the outer caller handles the multi-turn loop). + - Structured outputs are rejected if they define ``is_empty()`` and + it returns ``True`` (an empty answer is never a valid terminal + output). The ``hasattr`` guard keeps this backward-compatible with + schemas that don't declare ``is_empty``. + + Return ``None`` to accept; return a string to ask the model for a + retry with that message as the prompt. + """ + if isinstance(output, TextOutput): + return None + is_empty = getattr(output, "is_empty", None) + if callable(is_empty) and is_empty(): + return ( + "Output is empty. Provide a complete structured answer with meaningful content in all required fields." + ) + return None + + +# --------------------------------------------------------------------------- +# Input-side run-context helpers +# +# Only the ``pai_run_context`` extra is consulted. Callers who want to drive +# multi-turn continuation feed back ``agent.last_run_context`` (or any event's +# ``run_context``) — both carry the live pai ``RunContext`` under the extra, +# so message history / usage come across losslessly with no conversion. +# --------------------------------------------------------------------------- + + +def _message_history_from_run_context(run_context: Any | None) -> list[ModelMessage] | None: + """Return the pai message history from ``run_context.pai_run_context``, or ``None``.""" + if run_context is None: + return None + pai_ctx = getattr(run_context, "pai_run_context", None) + if pai_ctx is None: + return None + messages = getattr(pai_ctx, "messages", None) + return list(messages) if messages else None + + +def _usage_from_run_context(run_context: Any | None) -> PAIRunUsage | None: + """Return the pai ``RunUsage`` from ``run_context.pai_run_context``, or ``None``.""" + if run_context is None: + return None + pai_ctx = getattr(run_context, "pai_run_context", None) + if pai_ctx is None: + return None + return getattr(pai_ctx, "usage", None) + + +__all__ = [ + "PydanticAIBaseAgent", + "PydanticAIBaseAgentConfig", +] diff --git a/akd_ext/agents/_base/pydantic_ai/_utils.py b/akd_ext/agents/_base/pydantic_ai/_utils.py new file mode 100644 index 0000000..5ff3a0a --- /dev/null +++ b/akd_ext/agents/_base/pydantic_ai/_utils.py @@ -0,0 +1,189 @@ +"""This contains utility used by the PydanticAIBaseAgent + +## 1. Translate pydantic_ai stream events into AKD ``StreamEvent`` objects. + +``astream`` on ``PydanticAIBaseAgent`` wraps ``pydantic_ai.Agent.run_stream_events()`` +and yields whatever we can translate. Events we can't faithfully map are +dropped (return ``None``); the caller iterator still terminates naturally when +the run completes, which is the AKD signal for end-of-stream anyway. + +Translation table: + +| pydantic_ai event | AKD event | +|--------------------------------------------------|-----------------------| +| ``PartDeltaEvent(delta=TextPartDelta)`` | ``StreamingTokenEvent`` | +| ``PartStartEvent(part=ThinkingPart)`` | ``ThinkingEvent`` | +| ``PartDeltaEvent(delta=ThinkingPartDelta)`` | ``ThinkingEvent`` | +| ``FunctionToolCallEvent`` / ``BuiltinToolCallEvent`` | ``ToolCallingEvent`` | +| ``FunctionToolResultEvent`` / ``BuiltinToolResultEvent`` | ``ToolResultEvent`` | +| ``FinalResultEvent`` | (dropped; iterator end signals completion) | +| ``PartStartEvent(part=TextPart)``, ``PartEndEvent`` | (dropped; no AKD analogue) | + +A future ``CompletedEvent`` emission with the actual output value is the +responsibility of ``astream`` itself (after the iterator exhausts), not this +function — this translator is per-event. + +## 2. Adapter from AKD ``BaseTool``-protocol instances to ``pydantic_ai.Tool``. + +``BaseTool.as_function()`` already returns a typed async callable with the +right signature metadata for framework introspection. We only need to: + +1. Wrap it so ``pydantic.ValidationError`` / ``akd.SchemaValidationError`` + become ``pydantic_ai.ModelRetry`` (so the LLM can self-correct on bad + tool-call args); all other exceptions propagate and halt the run per + pydantic_ai conventions. +2. Preserve the signature metadata that pydantic_ai introspects to build + the tool's JSON schema. +3. Hand the callable to ``pydantic_ai.Tool`` with name/description pulled + from the AKD tool. + +``sequential``, ``timeout``, and per-tool ``max_retries`` stay at their +pydantic_ai defaults. AKD tools are stateless by design so parallel +execution is safe; per-tool knobs can arrive later via a dedicated +``PydanticAIBaseTool(BaseTool)`` that extends ``BaseToolConfig``. +""" + +from pydantic import ValidationError +from pydantic_ai import ModelRetry +from pydantic_ai import Tool as PAITool +from akd._base.protocols import AKDTool + +from akd._base.errors import SchemaValidationError + +from pydantic_ai.messages import ( + BuiltinToolCallEvent, + BuiltinToolResultEvent, + FunctionToolCallEvent, + FunctionToolResultEvent, + PartDeltaEvent, + PartStartEvent, + TextPartDelta, + ThinkingPart, + ThinkingPartDelta, + ToolCallPart, + ToolCallPartDelta, + ToolReturnPart, +) + +from akd._base import ( + StreamEvent, + StreamingEventData, + StreamingTokenEvent, + ThinkingEvent, + ThinkingEventData, + ToolCall, + ToolCallingEvent, + ToolCallingEventData, + ToolResult, + ToolResultEvent, + ToolResultEventData, +) + +from akd._base.structures import RunContext as AKDRunContext + + +def pai_event_to_akd_event( + pai_event, + run_context: AKDRunContext | None = None, +) -> StreamEvent | None: + """Translate a single pydantic_ai stream event to an AKD ``StreamEvent``. + + Returns ``None`` for events we don't map; callers should skip those. + + ``run_context`` is an AKD ``RunContext`` built by the caller (typically + wrapping the live ``pydantic_ai.RunContext`` under the ``pai_run_context`` + extra attribute); if supplied, it is attached to every constructed event + so consumers can drive multi-turn continuation from any event. + """ + ctx_kwargs = {"run_context": run_context} if run_context is not None else {} + + # Text delta → streaming token + if isinstance(pai_event, PartDeltaEvent): + delta = pai_event.delta + if isinstance(delta, TextPartDelta): + token = getattr(delta, "content_delta", "") or "" + return StreamingTokenEvent(data=StreamingEventData(token=token), **ctx_kwargs) + if isinstance(delta, ThinkingPartDelta): + thinking = getattr(delta, "content_delta", "") or "" + return ThinkingEvent( + data=ThinkingEventData(thinking_content=thinking, streaming=True), + **ctx_kwargs, + ) + if isinstance(delta, ToolCallPartDelta): + # Structured-output runs stream the args JSON of the final-output + # tool call. Surface each chunk as a streaming token so UIs see + # the model assembling its answer rather than nothing. + args_delta = getattr(delta, "args_delta", None) or "" + if isinstance(args_delta, dict): + args_delta = str(args_delta) + return StreamingTokenEvent(data=StreamingEventData(token=args_delta), **ctx_kwargs) + return None + + # Thinking part started → emit a (non-streaming) thinking marker + if isinstance(pai_event, PartStartEvent): + part = pai_event.part + if isinstance(part, ThinkingPart): + content = getattr(part, "content", "") or "" + return ThinkingEvent(data=ThinkingEventData(thinking_content=content), **ctx_kwargs) + # TextPart start and other part kinds don't have a direct AKD analogue + return None + + # Function tool call + if isinstance(pai_event, (FunctionToolCallEvent, BuiltinToolCallEvent)): + part = pai_event.part + if isinstance(part, ToolCallPart): + return ToolCallingEvent( + data=ToolCallingEventData( + tool_call=ToolCall( + tool_call_id=part.tool_call_id or "", + tool_name=part.tool_name, + arguments=part.args_as_dict() if hasattr(part, "args_as_dict") else (part.args or {}), + ), + ), + **ctx_kwargs, + ) + return None + + # Function tool result + if isinstance(pai_event, (FunctionToolResultEvent, BuiltinToolResultEvent)): + result = pai_event.result + if isinstance(result, ToolReturnPart): + return ToolResultEvent( + data=ToolResultEventData( + result=ToolResult( + tool_call_id=result.tool_call_id or "", + tool_name=result.tool_name, + content=result.content, + ), + ), + **ctx_kwargs, + ) + # RetryPromptPart results (tool retries) don't map to a success event + return None + + return None + + +def akd_to_pai_tool(akd_tool: AKDTool) -> PAITool: + """Adapt an AKD-protocol tool into a ``pydantic_ai.Tool``.""" + raw_fn = akd_tool.as_function() + + async def wrapped(*args, **kwargs): + try: + return await raw_fn(*args, **kwargs) + except (ValidationError, SchemaValidationError) as e: + raise ModelRetry(str(e)) from e + + wrapped.__name__ = raw_fn.__name__ + wrapped.__doc__ = raw_fn.__doc__ + wrapped.__signature__ = raw_fn.__signature__ + wrapped.__annotations__ = raw_fn.__annotations__ + + return PAITool( + wrapped, + name=akd_tool.name, + description=akd_tool.description or akd_tool.__class__.__doc__, + ) + + +__all__ = ["akd_to_pai_tool", "pai_event_to_akd_event"] diff --git a/docs/development/creating_agents.md b/docs/development/creating_agents.md index aa52eba..5552333 100644 --- a/docs/development/creating_agents.md +++ b/docs/development/creating_agents.md @@ -1,29 +1,66 @@ # Creating Agents in akd-ext -This guide walks through creating a new agent using the OpenAI Agents SDK base classes provided by akd-ext. +This guide walks through creating a new agent in akd-ext. Two base classes are +available: + +- **`OpenAIBaseAgent`** — built on the OpenAI Agents SDK. Direct path when you + want to rely on OpenAI SDK hosted tools like `HostedMCPTool` / `WebSearchTool`. +- **`PydanticAIBaseAgent`** — built on `pydantic_ai.Agent`. Richer extension + surface (capabilities, hooks, history processors). + +Both expose the same AKD contract — `arun` / `astream`, schema-first +input/output, config-first construction — so downstream callers don't care +which base is underneath. Both import from `akd_ext.agents._base`. ## Overview -Agents in akd-ext follow a **schema-first** pattern built on top of the OpenAI Agents SDK. Every agent requires: +Agents in akd-ext follow a **schema-first** pattern. Every agent requires: - **Input schema** — typed parameters the agent receives - **Output schema** — typed structured output the agent returns -- **Config** — model settings, system prompt, tools +- **System prompt** — module-level constant defining agent behavior +- **Config** — model settings, system prompt, tools, (pydantic_ai) capabilities - **Agent class** — ties everything together -### Class Hierarchy +### Choosing a base + +| You want … | Use | +|---|---| +| OpenAI Agents SDK hosted tools (`HostedMCPTool`, `WebSearchTool`) straight through | `OpenAIBaseAgent` | +| Pydantic AI capabilities (`Thinking`, `MCP`, `WebSearch`, `Hooks`, …), native union outputs, `TestModel` for hermetic tests, `agent.last_run_context` for multi-turn continuation | `PydanticAIBaseAgent` | + +Both satisfy akd-core's `AKDExecutable` protocol at runtime; +`isinstance(agent, AKDExecutable)` works either way. + +### Class hierarchies ``` BaseAgent (akd-core) -└── OpenAIBaseAgent (akd_ext/agents/_base.py) - └── Your agent +├── OpenAIBaseAgent (akd_ext.agents._base.openai) +│ └── Your agent +│ +└── ConfigBindingMixin (akd-core) + pydantic_ai.Agent + AKDExecutable + └── PydanticAIBaseAgent (akd_ext.agents._base.pydantic_ai) + └── Your agent ``` -## Step-by-step Guide +`OpenAIBaseAgent` follows AKD's historic single-inheritance pattern. +`PydanticAIBaseAgent` multi-inherits `ConfigBindingMixin` from akd-core to +auto-expose config fields as properties, subclasses `pydantic_ai.Agent` for +behavior, and explicitly lists `AKDExecutable` in the bases so runtime +`isinstance` checks succeed. + +## Building blocks (shared) + +The input schema, output schema, system prompt, `check_output` override, union +outputs, file layout, registration, tests, and streaming contract are +identical across both bases. Differences are isolated to config class, model +name format, and hosted-tool wiring (next section). -### 1. Define the Input Schema +### Input schema -Extend `InputSchema` from akd-core. Every field needs a `Field(...)` with a description. +Extend `InputSchema` from akd-core. Every field needs `Field(...)` with a +`description`. ```python from akd._base import InputSchema @@ -38,13 +75,16 @@ class MyAgentInputSchema(InputSchema): ``` Rules: -- Docstring is **required** -- All fields must have `description` in `Field()` -- Use modern type hints: `str | None` not `Optional[str]`, `list[str]` not `List[str]` -### 2. Define the Output Schema +- Docstring is **required**. +- All fields must carry a `description` in `Field()`. +- Use modern type hints (`str | None`, `list[str]`) — not `Optional[str]` / + `List[str]`. -Extend `OutputSchema`. Set `__response_field__` to indicate which field contains the primary text response. +### Output schema + +Extend `OutputSchema`. Set `__response_field__` to indicate which field +contains the primary text response (used for streaming). ```python from akd._base import OutputSchema @@ -58,7 +98,7 @@ class MyAgentOutputSchema(OutputSchema): report: str = Field(default="", description="The full analysis report") ``` -For multiple output fields: +Multiple fields: ```python class MyAgentOutputSchema(OutputSchema): @@ -69,11 +109,23 @@ class MyAgentOutputSchema(OutputSchema): reasoning: str = Field(default="", description="Reasoning behind design choices") ``` -The `__response_field__` tells the framework which field contains the main text content for streaming. +### Union output with TextOutput + +Setting `output_schema = MyAgentOutputSchema | TextOutput` lets the agent +return either: + +- **Structured output** (`MyAgentOutputSchema`) — when it has results. +- **Free-form text** (`TextOutput`) — for clarification questions or when + inputs are insufficient. -### 3. Write the System Prompt +Use a single schema (`output_schema = MyAgentOutputSchema`) if you don't need +this flexibility. On `PydanticAIBaseAgent`, the union is handled natively by +pydantic_ai. -Define the system prompt as a module-level constant. This is the core of your agent's behavior. +### System prompt + +Define the system prompt as a module-level constant — this is the core of your +agent's behavior. ```python MY_AGENT_SYSTEM_PROMPT = """\ @@ -94,41 +146,84 @@ You are a ... """ ``` -### 4. Define the Config +### `check_output()` override + +Override `check_output()` to validate the agent's output before returning it. +Return `None` if valid, or a string to reject and retry: + +```python +def check_output(self, output) -> str | None: + if isinstance(output, MyAgentOutputSchema) and not output.report.strip(): + return "Report is empty. Provide a complete analysis." + return super().check_output(output) +``` + +On `PydanticAIBaseAgent` this method is automatically bridged to pydantic_ai's +`@output_validator` — a non-`None` return value is raised as `ModelRetry` so +the model can self-correct. `TextOutput` always passes through (it represents +a mid-conversation clarification request, not a terminal answer). + +## Config & agent class -Extend `OpenAIBaseAgentConfig` with your defaults. +The surface is identical on both bases — only the imports and model-name +format differ. + +### On `OpenAIBaseAgent` ```python from typing import Literal -from akd_ext.agents._base import OpenAIBaseAgentConfig +from akd._base import TextOutput +from akd_ext.agents._base import OpenAIBaseAgent, OpenAIBaseAgentConfig from pydantic import Field + class MyAgentConfig(OpenAIBaseAgentConfig): """Configuration for My Agent.""" system_prompt: str = Field(default=MY_AGENT_SYSTEM_PROMPT) - model_name: str = Field(default="gpt-5.2") + model_name: str = Field(default="gpt-5.2") # bare model name reasoning_effort: Literal["low", "medium", "high"] | None = Field(default="medium") + + +class MyAgent(OpenAIBaseAgent[MyAgentInputSchema, MyAgentOutputSchema]): + """My Agent description.""" + + input_schema = MyAgentInputSchema + output_schema = MyAgentOutputSchema | TextOutput + config_schema = MyAgentConfig + + def check_output(self, output) -> str | None: + if isinstance(output, MyAgentOutputSchema) and not output.report.strip(): + return "Report is empty. Provide a complete analysis." + return super().check_output(output) ``` -Key config properties: -- `model_name` — model to use (e.g., `"gpt-5.2"`, `"gpt-4o"`, `"gpt-5-nano"`) -- `reasoning_effort` — for reasoning models: `"low"`, `"medium"`, `"high"`, or `None` -- `tools` — list of tools (see [Adding Tools](#adding-tools)) -- `stateless` — `False` (default) keeps conversation history, `True` for single-turn -- `temperature`, `max_tokens`, `top_p` — sampling parameters +### On `PydanticAIBaseAgent` -### 5. Create the Agent Class +Same shape, different imports. Note the `provider:model` prefix on +`model_name` — pydantic_ai uses that format for provider resolution instead +of bare model names. ```python +from typing import Literal from akd._base import TextOutput -from akd_ext.agents._base import OpenAIBaseAgent +from akd_ext.agents._base import PydanticAIBaseAgent, PydanticAIBaseAgentConfig +from pydantic import Field -class MyAgent(OpenAIBaseAgent[MyAgentInputSchema, MyAgentOutputSchema]): + +class MyAgentConfig(PydanticAIBaseAgentConfig): + """Configuration for My Agent.""" + + system_prompt: str = Field(default=MY_AGENT_SYSTEM_PROMPT) + model_name: str = Field(default="openai:gpt-5.2") # provider:model + reasoning_effort: Literal["low", "medium", "high"] | None = Field(default="medium") + + +class MyAgent(PydanticAIBaseAgent[MyAgentInputSchema, MyAgentOutputSchema]): """My Agent description.""" input_schema = MyAgentInputSchema - output_schema = MyAgentOutputSchema | TextOutput # union output + output_schema = MyAgentOutputSchema | TextOutput config_schema = MyAgentConfig def check_output(self, output) -> str | None: @@ -137,21 +232,59 @@ class MyAgent(OpenAIBaseAgent[MyAgentInputSchema, MyAgentOutputSchema]): return super().check_output(output) ``` -#### Union Output with TextOutput +### Common config fields + +Both config classes inherit from `akd.agents._base.BaseAgentConfig` and share: -Setting `output_schema = MyAgentOutputSchema | TextOutput` allows the agent to return either: -- **Structured output** (`MyAgentOutputSchema`) — when it has results -- **Free-form text** (`TextOutput`) — for clarification questions or when inputs are insufficient +- `model_name` — model identifier (format differs per base; see above). +- `system_prompt` — agent instructions. +- `tools` — list of tools (see below). +- `reasoning_effort` — `"low"` / `"medium"` / `"high"` / `None` (reasoning models only). +- `num_retries` — max retries for tool calls and output validation. +- `max_tool_iterations` / `max_tool_calls` — per-run tool-call caps. +- `reflection_prompt` — injected reflection before each model request. +- `stateless` — `False` (default) keeps conversation history, `True` for single-turn. +- `temperature`, `max_tokens`, `top_p` — sampling parameters. -If you don't need this flexibility, use a single schema: `output_schema = MyAgentOutputSchema`. +### Pydantic AI-only config fields -#### `check_output()` Override +`PydanticAIBaseAgentConfig` adds: -Override `check_output()` to validate the agent's output before returning it. Return `None` if valid, or an error message string to reject and retry. +- `capabilities: list[Any]` — pydantic_ai capability objects (`Thinking`, + `MCP`, `WebSearch`, `WebFetch`, `Hooks`, …). Merged with capabilities the + base auto-derives from the scalar fields above (e.g. `reasoning_effort` + becomes a `Thinking(effort=...)` capability). +- `history_processors: list[Any]` — per-request message-history callables. +- `extra="allow"` — any additional fields on a subclass config are forwarded + to `pydantic_ai.Agent.__init__` via `model_extra` (forward-compat). +- `enable_trimming: bool = False` — disabled by default because the naive + ratio-trimmer violates pydantic_ai's tool-call/assistant pairing invariant. + Supply your own processor via `history_processors` if you need trimming. -### 6. Adding Tools +## Tools -#### MCP Tools (Hosted) +AKD tools (`BaseTool` subclasses) are auto-converted on both bases — just pass +instances via `config.tools=[...]`: + +```python +from akd_ext.tools.dummy import DummyTool + +class MyAgentConfig(OpenAIBaseAgentConfig): # or PydanticAIBaseAgentConfig + tools: list[Any] = Field(default_factory=lambda: [DummyTool()]) +``` + +On `OpenAIBaseAgent`, AKD `BaseTool` instances are converted to the OpenAI +SDK's `FunctionTool`. On `PydanticAIBaseAgent`, they're adapted to +`pydantic_ai.Tool`; `ValidationError` / `SchemaValidationError` raised inside +the tool become `ModelRetry` so the model can self-correct bad arguments. + +Where the two bases diverge is on **hosted / built-in tools** — each ecosystem +has its own mechanism. + +### MCP tools + +**On `OpenAIBaseAgent`** — register the OpenAI SDK's `HostedMCPTool` as a +tool: ```python import os @@ -167,7 +300,7 @@ def get_default_tools(): "require_approval": "never", "server_description": "Description of the MCP server", "server_url": os.environ.get("MY_MCP_URL", "https://default-url.com/mcp"), - } + }, ), ] @@ -175,7 +308,32 @@ class MyAgentConfig(OpenAIBaseAgentConfig): tools: list[Any] = Field(default_factory=get_default_tools) ``` -#### Web Search +**On `PydanticAIBaseAgent`** — MCP is a **capability**, not a tool. Register it +via `config.capabilities`: + +```python +import os +from pydantic_ai.capabilities import MCP + +def get_default_capabilities(): + return [ + MCP( + # Trailing slash matters: the endpoint returns a 307 redirect to + # the slashed form, and the MCP streamable-HTTP client won't + # follow redirects on POST. + url=os.environ.get("MY_MCP_URL", "https://default-url.com/mcp/"), + allowed_tools=["tool_a", "tool_b"], + description="Description of the MCP server", + ), + ] + +class MyAgentConfig(PydanticAIBaseAgentConfig): + capabilities: list[Any] = Field(default_factory=get_default_capabilities) +``` + +### Web search + +**On `OpenAIBaseAgent`**: ```python from agents import WebSearchTool @@ -184,21 +342,130 @@ class MyAgentConfig(OpenAIBaseAgentConfig): tools: list[Any] = Field(default_factory=lambda: [WebSearchTool()]) ``` -#### AKD Tools +**On `PydanticAIBaseAgent`** — `WebSearch` is a capability: + +```python +from pydantic_ai.capabilities import WebSearch + +class MyAgentConfig(PydanticAIBaseAgentConfig): + capabilities: list[Any] = Field(default_factory=lambda: [WebSearch()]) +``` + +## Pydantic AI-specific features + +`PydanticAIBaseAgent` exposes a handful of extension points that have no +analogue on `OpenAIBaseAgent`. + +### Capabilities + +Pydantic AI's primary extension point. Capability instances registered on +`config.capabilities` run alongside capabilities the base auto-derives from +scalar config fields. Useful built-ins: + +| Capability | Purpose | +|---|---| +| `Thinking(effort=...)` | Reasoning models; auto-derived from `config.reasoning_effort` | +| `MCP(url=..., allowed_tools=...)` | MCP server integration (see above) | +| `WebSearch()` / `WebFetch()` | Built-in web search / fetch (model-dependent) | +| `Hooks()` | Lifecycle hooks (see below) | +| *custom* | Subclass `pydantic_ai.capabilities.AbstractCapability` | + +### Hooks + +`pydantic_ai.capabilities.hooks.Hooks` lets you register decorator-style +callbacks on the run lifecycle — `before_run`, `before_model_request`, +`before_tool_execute`, `after_run`, etc. `PydanticAIBaseAgent` itself installs +an internal `Hooks` capability to capture each run's live `RunContext` onto +`self._live_pai_ctx`; subclasses can add their own `Hooks()` via +`config.capabilities`: + +```python +from pydantic_ai.capabilities.hooks import Hooks + +hooks = Hooks() + +@hooks.on.before_tool_execute +async def audit_tool(ctx, *, call, tool_def, args): + print(f"About to call tool {call.tool_name} with {args!r}") + return args # pass through unchanged + +class MyAgentConfig(PydanticAIBaseAgentConfig): + capabilities: list[Any] = Field(default_factory=lambda: [hooks]) +``` + +### History processors + +`config.history_processors: list[Callable[[list[ModelMessage]], list[ModelMessage]]]` +runs per-request and lets you transform the message history sent to the model +(trim, summarize, filter). AKD's ratio-trimmer is *off* by default on +`PydanticAIBaseAgent` because it breaks tool-call/assistant pairing; supply +your own if you need trimming. + +### `RunContext` propagation and multi-turn runs + +Every event `agent.astream(...)` yields carries a `run_context` populated from +pydantic_ai's live `RunContext` — AKD-shape `messages` / `usage` / `run_id` +reflected for read-only inspection, plus a lossless `pai_run_context` extra +with the full pai object. For `arun` callers (whose return is pinned to +`OutputSchema` per the AKD contract), the same wrapper is reachable via +`agent.last_run_context`: + +```python +out_1 = await agent.arun(MyAgentInputSchema(query="first turn")) +ctx = agent.last_run_context # populated AKD RunContext with pai_run_context extra + +out_2 = await agent.arun( + MyAgentInputSchema(query="follow-up"), + run_context=ctx, # carries prior-turn messages + usage +) +``` + +Passing `event.run_context` or `agent.last_run_context` verbatim into the next +call triggers lossless pai-native continuation — the agent's input-side +helpers prefer the `pai_run_context` extra over converting from the AKD-shape +typed fields. + +**Concurrency note**: `PydanticAIBaseAgent` is designed for one active run per +instance. Concurrent `arun` / `astream` calls on the same agent will race +the captured `_live_pai_ctx`. Use a fresh agent per run for concurrent +workloads. + +### Hermetic tests with `TestModel` -AKD `BaseTool` instances are auto-converted to OpenAI `FunctionTool` via the config validator. +`pydantic_ai.models.test.TestModel` stands in for a real model so unit tests +don't touch a provider: -## File Structure +```python +from pydantic_ai.models.test import TestModel + +agent = MyAgent(MyAgentConfig(capabilities=[])) # disable MCP for hermetic run +with agent.override(model=TestModel()): + result = await agent.arun(MyAgentInputSchema(query="x")) +``` + +`TestModel` auto-fills the declared output schema with stub data, so +`agent.arun` returns a real `MyAgentOutputSchema` (or `TextOutput`) with no +network call. + +## File structure Place your agent in the appropriate directory: ``` akd_ext/agents/ -├── _base.py # Base classes (don't modify) +├── _base/ # Base classes (don't modify) +│ ├── __init__.py # Re-exports both base classes +│ ├── openai.py # OpenAIBaseAgent +│ └── pydantic_ai/ # PydanticAIBaseAgent + adapters +│ ├── _base.py +│ ├── _capabilities.py +│ ├── _context_adapter.py +│ ├── _event_translator.py +│ └── _tool_adapter.py ├── __init__.py # Top-level exports -├── cmr_care.py # Standalone agent example +├── cmr_care.py # Standalone agent (OpenAI-based) └── research_partner/ # Agent group - ├── __init__.py # Group exports + ├── __init__.py ├── capability_feasibility_mapper.py ├── workflow_spec_builder.py ├── experiment_implementation.py @@ -206,17 +473,18 @@ akd_ext/agents/ ``` Each agent file follows this internal layout: + 1. Module docstring 2. Imports 3. System prompt constant -4. Tool factory function (if applicable) +4. Tool / capability factory function (if applicable) 5. Config class -6. Input/Output schema classes +6. Input / output schema classes (can live above the config if they're prerequisites) 7. Agent class ## Registration -### In the agent group `__init__.py` +### Group `__init__.py` ```python # akd_ext/agents/research_partner/__init__.py @@ -236,7 +504,7 @@ __all__ = [ ] ``` -### In the top-level `akd_ext/agents/__init__.py` +### Top-level `akd_ext/agents/__init__.py` ```python from akd_ext.agents.research_partner import ( @@ -256,9 +524,10 @@ __all__ = [ ] ``` -## Writing Tests +## Writing tests -Tests live in `tests/agents/` mirroring the source structure. Use the `reasoning_effort` fixture from `tests/conftest.py`. +Tests live in `tests/agents/` mirroring the source structure. Use the +`reasoning_effort` fixture from `tests/conftest.py`. ```python """Functional tests for My Agent.""" @@ -309,6 +578,19 @@ async def test_my_agent(query: str, reasoning_effort: str): assert result.report.strip(), "Report should not be empty" ``` +For **hermetic unit tests on `PydanticAIBaseAgent`**, swap the real model for +`TestModel` and disable any MCP / network capabilities: + +```python +from pydantic_ai.models.test import TestModel + +async def test_my_agent_hermetic(): + agent = MyAgent(MyAgentConfig(capabilities=[])) # no MCP in hermetic run + with agent.override(model=TestModel()): + result = await agent.arun(_make_input(query="x")) + assert isinstance(result, (MyAgentOutputSchema, TextOutput)) +``` + Run tests with: ```bash @@ -316,7 +598,7 @@ uv run pytest tests/agents/research_partner/test_my_agent.py -n=3 uv run pytest tests/agents/research_partner/test_my_agent.py --reasoning-effort=low -n=3 ``` -## Running the Agent +## Running the agent ```python import asyncio @@ -337,6 +619,10 @@ async for event in agent.astream(MyAgentInputSchema(query="my question", data_pa print(event.event_type, event.data) ``` +On `PydanticAIBaseAgent`, each `event.run_context` carries the live pai state; +feed it (or `agent.last_run_context`) into the next call for multi-turn +continuation as shown in *RunContext propagation and multi-turn runs* above. + ## Linting Always run before committing: @@ -345,12 +631,14 @@ Always run before committing: uv run pre-commit run --all-files ``` -## Reference Examples +## Reference examples -| Pattern | Example File | -|---------|-------------| -| Agent without tools | `akd_ext/agents/research_partner/capability_feasibility_mapper.py` | -| Agent with MCP tools | `akd_ext/agents/cmr_care.py` | +| Pattern | Example file | +|---|---| +| Agent without tools (OpenAI) | `akd_ext/agents/research_partner/capability_feasibility_mapper.py` | +| Agent with MCP tools (OpenAI) | `akd_ext/agents/cmr_care.py` | +| Agent with MCP capability (Pydantic AI) | `examples/cmr_care_pydantic.py` | | Multiple output fields | `akd_ext/agents/research_partner/workflow_spec_builder.py` | | Single structured output (no union) | `akd_ext/agents/code_search_care.py` | | Test with parametrize | `tests/agents/test_cmr_care.py` | +| Hermetic test with `TestModel` (Pydantic AI) | `tests/agents/test_base_pydantic.py` | diff --git a/docs/pydantic_ai_base.md b/docs/pydantic_ai_base.md new file mode 100644 index 0000000..ff9de98 --- /dev/null +++ b/docs/pydantic_ai_base.md @@ -0,0 +1,480 @@ +# PydanticAIBaseAgent — Full Class Diagram + +Full structural diagram for `akd_ext/agents/_base/pydantic_ai/` traced from the +entry file `_base.py`. Focus: the classes this subpackage adds, the protocols +it defines, and the adapter modules that bridge AKD ↔ pydantic_ai. External +SDK types are shown as single boundary boxes; their internal structure is +captured in the translation tables below, not in the graph. + +```mermaid +classDiagram + direction TB + + %% ══════════════════════════════════════════════════════════ + %% External boundaries (collapsed — details in tables below) + %% ══════════════════════════════════════════════════════════ + + class PAIAgent { + <> + +run(prompt, deps, message_history, usage) RunResult + +run_stream_events(...) AsyncIterator + +output_validator «decorator» + +system_prompt «decorator» + } + + class AbstractCapability { + <> + } + class Hooks { + <> + +on.before_model_request + +on.before_tool_execute + } + AbstractCapability <|-- Hooks + + class PAITool { + <> + +name / description / function + } + class ModelRetry { + <> + } + class AgentRunResultEvent { + <> + +result.output + } + + class ConfigBindingMixin { + <> + +__init_subclass__() auto-exposes config fields as properties + +skips in-dict redeclarations (e.g. system_prompt) + } + class BaseAgentConfig { + <> + +model_name / system_prompt / tools + +reasoning_effort / num_retries + +max_tool_iterations / max_tool_calls + +reflection_prompt / enable_trimming / trim_ratio + } + class BaseTool { + <> + +name / description + +arun / as_function + } + class InputSchema { + <> + } + class OutputSchema { + <> + } + class TextOutput { + <> + } + OutputSchema <|-- TextOutput + + class AKDStreamEvent { + <> + +Completed / StreamingToken / Thinking + +ToolCalling / ToolResult + +run_context: AKDRunContext + } + + class AKDRunContext { + <> + +messages: list[dict] | None + +usage: RunUsage + +run_id: str | None + +pai_run_context: pai.RunContext «extra / lossless» + } + + %% ══════════════════════════════════════════════════════════ + %% Protocols (akd._base.protocols) + %% ══════════════════════════════════════════════════════════ + + class TokenCounts { + <> + +input_tokens / output_tokens / requests + } + class RunContextProtocol { + <> + +messages / usage / run_id + } + class AKDExecutable { + <> + +input_schema / output_schema / config_schema + +name / description + +arun(params, run_context) Any + +astream(params, run_context) AsyncIterator + } + class AKDTool { + <> + +name / description + +as_function() Callable + } + AKDExecutable <|-- AKDTool + RunContextProtocol ..> TokenCounts + + %% ══════════════════════════════════════════════════════════ + %% Adapter / bridge modules (_*.py) — function-only + %% ══════════════════════════════════════════════════════════ + + class ContextAdapter { + <<_context_adapter.py>> + +_akd_dicts_to_pai_messages(msgs)$ list + +_usage_to_pai(usage)$ PAIRunUsage + +_pai_messages_to_akd_dicts(msgs)$ list[dict] + +_pai_usage_to_akd_usage(pai_usage)$ AKDRunUsage + +_message_history_from_run_context(ctx)$ list + +_usage_from_run_context(ctx)$ PAIRunUsage + } + class EventTranslator { + <<_event_translator.py>> + +pai_event_to_akd_event(event, run_context=None)$ AKDStreamEvent + } + class ToolAdapter { + <<_tool_adapter.py>> + +akd_to_pai_tool(tool)$ PAITool + } + class CapabilityFactories { + <<_capabilities.py>> + +ToolCallLimits(max_iterations, max_calls)$ Hooks + +ReflectionCapability(prompt)$ Hooks + +make_ratio_trimmer(ratio)$ Callable + } + + %% ══════════════════════════════════════════════════════════ + %% Config + Core (_base.py) + %% ══════════════════════════════════════════════════════════ + + class PydanticAIBaseAgentConfig { + +capabilities: list + +history_processors: list + +enable_trimming: bool + +extra allowed for forward-compat + } + BaseAgentConfig <|-- PydanticAIBaseAgentConfig + + class PydanticAIBaseAgent~InSchema OutSchema~ { + +input_schema: type + +output_schema: type + +config_schema: type + +config: PydanticAIBaseAgentConfig + +_live_pai_ctx: Any + ── AKD contract ── + +arun(params, run_context) OutSchema + +astream(params, run_context) AsyncIterator + ── RunContext propagation ── + +_build_run_context_capture() Hooks + +_wrap_pai_ctx() AKDRunContext + +last_run_context AKDRunContext? «property» + ── hooks ── + +_to_prompt(params) str + +_deps_from_run_context(ctx) Any + +_build_capabilities_from_scalars() list + +_build_history_processors_from_scalars() list + +_adapt_tools(tools) list + +check_output(output) str + } + + %% ── Inheritance / conformance ───────────────────────────── + ConfigBindingMixin <|-- PydanticAIBaseAgent + PAIAgent <|-- PydanticAIBaseAgent + AKDExecutable <|.. PydanticAIBaseAgent + + %% ── Composition ─────────────────────────────────────────── + PydanticAIBaseAgent --> PydanticAIBaseAgentConfig : config + PydanticAIBaseAgent --> AKDRunContext : wraps live pai ctx + PydanticAIBaseAgent --> Hooks : installs ctx_capture + + %% ── Adapter usage (one edge per bridge module) ──────────── + PydanticAIBaseAgent ..> ContextAdapter : uses + PydanticAIBaseAgent ..> EventTranslator : uses + PydanticAIBaseAgent ..> ToolAdapter : uses + PydanticAIBaseAgent ..> CapabilityFactories : uses + + %% ── Adapters target their respective external types ────── + ContextAdapter ..> RunContextProtocol : accepts + ContextAdapter ..> AKDRunContext : reads pai_run_context extra + EventTranslator ..> AKDStreamEvent : produces + EventTranslator ..> AgentRunResultEvent : consumes (terminal) + EventTranslator ..> AKDRunContext : attaches to events + ToolAdapter ..> AKDTool : accepts + ToolAdapter ..> PAITool : produces + ToolAdapter ..> ModelRetry : raises + CapabilityFactories ..> Hooks : returns + CapabilityFactories ..> AbstractCapability : (Thinking) returns + + %% Base agent surfaces BaseTool via _adapt_tools + PydanticAIBaseAgent ..> BaseTool : isinstance (adapt) + PydanticAIBaseAgent ..> TextOutput : passthrough in check_output + PydanticAIBaseAgent ..> ModelRetry : raises in validator + + %% ══════════════════════════════════════════════════════════ + %% Styling + %% ══════════════════════════════════════════════════════════ + + %% Core new classes — green + style PydanticAIBaseAgent fill:#e8f5e9,stroke:#2e7d32 + style PydanticAIBaseAgentConfig fill:#e8f5e9,stroke:#2e7d32 + + %% Protocols — orange + style TokenCounts fill:#fff3e0,stroke:#e65100 + style RunContextProtocol fill:#fff3e0,stroke:#e65100 + style AKDExecutable fill:#fff3e0,stroke:#e65100 + style AKDTool fill:#fff3e0,stroke:#e65100 + + %% Adapters / Bridges — pink + style ContextAdapter fill:#fce4ec,stroke:#c62828 + style EventTranslator fill:#fce4ec,stroke:#c62828 + style ToolAdapter fill:#fce4ec,stroke:#c62828 + style CapabilityFactories fill:#fce4ec,stroke:#c62828 + + %% External pydantic_ai — blue + style PAIAgent fill:#e3f2fd,stroke:#1565c0 + style AbstractCapability fill:#e3f2fd,stroke:#1565c0 + style Hooks fill:#e3f2fd,stroke:#1565c0 + style PAITool fill:#e3f2fd,stroke:#1565c0 + style ModelRetry fill:#e3f2fd,stroke:#1565c0 + style AgentRunResultEvent fill:#e3f2fd,stroke:#1565c0 + + %% External akd-core — gray + style ConfigBindingMixin fill:#eceff1,stroke:#455a64 + style BaseAgentConfig fill:#eceff1,stroke:#455a64 + style BaseTool fill:#eceff1,stroke:#455a64 + style InputSchema fill:#eceff1,stroke:#455a64 + style OutputSchema fill:#eceff1,stroke:#455a64 + style TextOutput fill:#eceff1,stroke:#455a64 + style AKDStreamEvent fill:#eceff1,stroke:#455a64 + style AKDRunContext fill:#eceff1,stroke:#455a64 +``` + +## Module Layout + +``` +akd_ext/agents/_base/pydantic_ai/ +├── __init__.py # re-exports PydanticAIBaseAgent, PydanticAIBaseAgentConfig +├── _base.py # PydanticAIBaseAgent, PydanticAIBaseAgentConfig +├── _capabilities.py # ToolCallLimits, ReflectionCapability, make_ratio_trimmer (factories) +├── _context_adapter.py # AKD ↔ pydantic_ai run-context / message-history / usage translation +├── _event_translator.py # pai_event_to_akd_event — stream event mapping +└── _tool_adapter.py # akd_to_pai_tool — BaseTool → pydantic_ai.Tool +``` + +Structural protocols (``AKDExecutable``, ``AKDTool``, ``RunContextProtocol``, ``TokenCounts``) +are sourced from ``akd._base.protocols`` and no longer duplicated here. + +## Color Legend + +| Color | Layer | Meaning | +|-------|-------|---------| +| 🟢 Green (`#e8f5e9`) | Core new classes | The two classes this subpackage adds: `PydanticAIBaseAgent`, `PydanticAIBaseAgentConfig`. | +| 🔵 Blue (`#e3f2fd`) | External — pydantic_ai | Library types being subclassed, composed, or translated. Collapsed to a handful of boundary boxes — internal shape isn't diagrammed. | +| ⚪ Gray (`#eceff1`) | External — akd-core | Foundations from the parent framework — schemas, config base, tool base, stream-event hierarchy, `ConfigBindingMixin`. | +| 🟠 Orange (`#fff3e0`) | Protocols | Structural interfaces defined in `akd._base.protocols`. Runtime-checkable; satisfied by both AKD and pydantic_ai sides. | +| 🩷 Pink (`#fce4ec`) | Adapters / bridges | Function-only modules presented as classes with static methods; each sits between an AKD concept and a pydantic_ai concept. | + +## Inheritance & Conformance + +``` +Runtime (class) inheritance +────────────────────────── +akd.ConfigBindingMixin ──┐ +pydantic_ai.Agent ├──► PydanticAIBaseAgent [Path B] +akd AKDExecutable ───────┘ (Protocol listed in bases so isinstance works) + +akd.BaseAgentConfig ──► PydanticAIBaseAgentConfig + +Structural (Protocol) conformance +────────────────────────────────── +AKDExecutable ◄── AKDTool (both runtime_checkable) +TokenCounts is satisfied by both akd.RunUsage and pydantic_ai.RunUsage +RunContextProtocol is satisfied by both akd.RunContext and pydantic_ai.RunContext +``` + +## Key Flows + +### `arun(params)` — one-shot inference + +``` +PydanticAIBaseAgent.arun(params, run_context=?) + ├─ self._to_prompt(params) → str (default: params.model_dump_json) + ├─ ContextAdapter._message_history_from_run_context → list[ModelMessage] | None + │ └─ if msgs are dicts: _akd_dicts_to_pai_messages + ├─ ContextAdapter._usage_from_run_context → PAIRunUsage | None + │ └─ _usage_to_pai (pass-through or copy 3 fields) + ├─ self._deps_from_run_context(run_context) → Any | None + └─ super().run(prompt, deps, message_history, usage, **kwargs) + └─ (inside pydantic_ai) output_validator: self.check_output + └─ returns str → raises ModelRetry; returns None → accept + result.output → OutSchema +``` + +### `astream(params)` — event stream + +``` +PydanticAIBaseAgent.astream(params, run_context=None) + ├─ prompt = self._to_prompt(params) + └─ async for pai_event in super().run_stream_events(prompt, deps, msg_history, usage): + ├─ AgentRunResultEvent → yield CompletedEvent( + │ data=CompletedEventData(output=...), + │ run_context=self._wrap_pai_ctx(), + │ ) + └─ else: + akd_event = EventTranslator.pai_event_to_akd_event( + pai_event, run_context=self._wrap_pai_ctx() + ) + ├─ PartDeltaEvent(TextPartDelta) → StreamingTokenEvent + ├─ PartDeltaEvent(ThinkingPartDelta) → ThinkingEvent(streaming=True) + ├─ PartDeltaEvent(ToolCallPartDelta) → StreamingTokenEvent (args JSON chunk) + ├─ PartStartEvent(ThinkingPart) → ThinkingEvent + ├─ FunctionToolCallEvent → ToolCallingEvent + ├─ BuiltinToolCallEvent → ToolCallingEvent + ├─ FunctionToolResultEvent → ToolResultEvent + ├─ BuiltinToolResultEvent → ToolResultEvent + └─ (other) → None (dropped) +``` + +### `__init__(config)` — construction pipeline + +``` +PydanticAIBaseAgent.__init__(config) + ├─ self.config = config or self.config_schema() + ├─ extra_kwargs = dict(config.model_extra or {}) # forward-compat + ├─ self._live_pai_ctx = None # RunContext capture slot + ├─ ctx_capture = self._build_run_context_capture() # Hooks: before_model_request, + │ # before_tool_execute + ├─ super().__init__(PAIAgent, …) + │ ├─ tools = self._adapt_tools(config.tools) + │ │ └─ for each BaseTool: ToolAdapter.akd_to_pai_tool → PAITool + │ │ └─ else: pass through (already pydantic_ai.Tool) + │ ├─ capabilities = [ctx_capture, + │ │ *self._build_capabilities_from_scalars(), + │ │ *config.capabilities] + │ │ ├─ reasoning_effort → Thinking(effort=…) + │ │ ├─ max_tool_iterations /calls → ToolCallLimits(Hooks) + │ │ └─ reflection_prompt → ReflectionCapability(Hooks) + │ └─ history_processors = [*self._build_history_processors_from_scalars(), *config.history_processors] + │ └─ enable_trimming → make_ratio_trimmer(1 - trim_ratio) + └─ self._register_akd_output_validator() # wires self.check_output via @output_validator +``` + +### RunContext propagation — Hooks capture and emission + +``` +At __init__ + └─ self._live_pai_ctx = None + ctx_capture = Hooks().on.before_model_request / .on.before_tool_execute + (registered as the first capability on super().__init__) + +During a run (hooks fire on each model request / tool execution) + ├─ before_model_request(ctx, request) → self._live_pai_ctx = ctx; return request + └─ before_tool_execute(ctx, call, tool_def, args) → self._live_pai_ctx = ctx; return args + ↑ ctx is pai's live RunContext — a shared reference into GraphAgentState, + so .messages / .usage / .run_id reflect the latest state between hooks + +On stream emission (astream) + └─ self._wrap_pai_ctx() builds an AKD RunContext per event: + ├─ messages = ContextAdapter._pai_messages_to_akd_dicts(pai_ctx.messages) + ├─ usage = ContextAdapter._pai_usage_to_akd_usage(pai_ctx.usage) + ├─ run_id = pai_ctx.run_id + └─ pai_run_context = pai_ctx # lossless extra for continuation + +On input (arun / astream receiving a prior turn's run_context) + ├─ ContextAdapter._message_history_from_run_context(ctx) + │ └─ if ctx.pai_run_context: use its .messages verbatim (lossless) + │ else: convert ctx.messages dicts → ModelMessage (AKD-shape fallback) + └─ ContextAdapter._usage_from_run_context(ctx) + └─ if ctx.pai_run_context: use its .usage verbatim + else: copy 3 structural fields from ctx.usage + +For arun callers (return stays OutputSchema) + └─ agent.last_run_context → self._wrap_pai_ctx() # None before any run + Used as: out_1 = await agent.arun(X) + ctx = agent.last_run_context + out_2 = await agent.arun(Y, run_context=ctx) # multi-turn +``` + +## Translation / Mapping Tables + +### Stream events (pydantic_ai → akd) + +| pydantic_ai event | AKD event | Notes | +|---|---|---| +| `PartDeltaEvent(TextPartDelta)` | `StreamingTokenEvent` | `token = delta.content_delta` | +| `PartDeltaEvent(ThinkingPartDelta)` | `ThinkingEvent(streaming=True)` | delta content only | +| `PartDeltaEvent(ToolCallPartDelta)` | `StreamingTokenEvent` | args-JSON chunks surfaced as tokens | +| `PartStartEvent(ThinkingPart)` | `ThinkingEvent` | one-shot, non-streaming | +| `FunctionToolCallEvent` / `BuiltinToolCallEvent` | `ToolCallingEvent` | wraps `ToolCall(tool_call_id, tool_name, arguments)` | +| `FunctionToolResultEvent` / `BuiltinToolResultEvent` | `ToolResultEvent` | wraps `ToolResult(tool_call_id, tool_name, content)` | +| `AgentRunResultEvent` | `CompletedEvent(data=CompletedEventData(output=…))` | emitted by `astream` itself, not by translator | +| `FinalResultEvent`, `PartStartEvent(TextPart)`, `PartEndEvent`, `RetryPromptPart` results | *(dropped — return `None`)* | iterator termination is the end-of-stream signal | + +### Run context — input side (caller → pydantic_ai) + +| Field | AKD `RunContext` | pydantic_ai `RunContext` | Adapter behavior | +|---|---|---|---| +| `messages` | `list[dict]` (OpenAI-style) | `list[ModelMessage]` (typed) | **Preferred path**: if `ctx.pai_run_context` is present, use `pai_run_context.messages` verbatim (lossless). **Fallback**: dicts → `ModelRequest` / `ModelResponse` via `_akd_dicts_to_pai_messages`; already-typed list passes through. | +| `usage` | `akd.RunUsage` (pydantic BaseModel) | `pydantic_ai.RunUsage` (dataclass) | **Preferred path**: `ctx.pai_run_context.usage` (already `PAIRunUsage`). **Fallback**: copy 3 structural fields from AKD `RunUsage`; rest default to 0. | +| `deps` | (not present) | `Any` | If run_context is a `PAIRunContext` itself, forward `.deps`; else `None`. | +| `run_id` | structural | structural | Read via `RunContextProtocol`, not converted. | + +Preferring `pai_run_context` means handing back `event.run_context` verbatim on the next turn "just works": the lossless pai objects round-trip without re-conversion, and the reflected AKD typed fields below are never read as input. + +### Run context — output side (pydantic_ai → event.run_context) + +Emitted by `PydanticAIBaseAgent._wrap_pai_ctx()` from `self._live_pai_ctx` (the live pai `RunContext` captured by the hooks capability). Populates AKD's typed fields for read-only inspection and carries the lossless pai object under an extra slot: + +| AKD `RunContext` field | Source | Helper | Lossiness | +|---|---|---|---| +| `messages` | `pai_ctx.messages` (`list[ModelMessage]`) | `_pai_messages_to_akd_dicts` | Lossy — multi-part responses collapse to OpenAI-style dicts; `ThinkingPart`s prefix-tagged into `content`; `ToolCallPart.args` serialized to JSON strings. | +| `usage` | `pai_ctx.usage` (`PAIRunUsage`) | `_pai_usage_to_akd_usage` | Near-lossless — 3 structural fields exact; overflow fields (cache / audio tokens, `tool_calls`) + pai `details` collapse into AKD `details` dict. | +| `run_id` | `pai_ctx.run_id` | (verbatim) | Lossless. | +| `pai_run_context` (extra) | `pai_ctx` itself | (verbatim) | **Lossless escape hatch** — the full pai `RunContext` preserved intact. Input-side helpers consult this first when it's present. | + +### Scalar config → capability + +| Config field | Capability / processor | Factory location | +|---|---|---| +| `reasoning_effort` | `Thinking(effort=…)` | built inline in `_build_capabilities_from_scalars` | +| `max_tool_iterations` / `max_tool_calls` | `ToolCallLimits` (`Hooks` with `before_model_request` / `before_tool_execute`) | `_capabilities.ToolCallLimits` | +| `reflection_prompt` | `ReflectionCapability` (`Hooks` with `before_model_request`) | `_capabilities.ReflectionCapability` | +| `enable_trimming` (+ `trim_ratio`) | stateless history processor (keeps head, drops oldest fraction) | `_capabilities.make_ratio_trimmer` | + +### AKD tool → pydantic_ai tool (`akd_to_pai_tool`) + +| Step | Source (AKD) | Target (pydantic_ai) | +|---|---|---| +| 1. Get callable | `BaseTool.as_function()` | — | +| 2. Wrap | catch `pydantic.ValidationError` / `akd.SchemaValidationError` | raise `ModelRetry(str(e))` | +| 3. Preserve metadata | `__signature__`, `__annotations__`, `__name__`, `__doc__` | forwarded onto wrapper | +| 4. Construct | `name = akd_tool.name`, `description = akd_tool.description or cls.__doc__` | `pydantic_ai.Tool(wrapped, name, description)` | + +## Design Notes + +- **Path B** (subclassing `PAIAgent` directly) keeps this implementation lean. + `AKDExecutable` is also listed in the bases so `isinstance(agent, AKDExecutable)` + returns `True` at runtime. Swapping to Path A (multi-inheriting `BaseAgent` for + shared machinery) is a small diff if ever needed. +- **Config auto-exposure** uses akd-core's `ConfigBindingMixin` (a mixin driven + by `__init_subclass__`, no metaclass). Fields already defined in the class + dict are skipped automatically — we re-bind `system_prompt = PAIAgent.system_prompt` + at class scope to prevent the auto-property from shadowing pydantic_ai's + `system_prompt` decorator method. +- **RunContext propagation** pairs a `Hooks`-capability capture of pai's live + `RunContext` (stored on `self._live_pai_ctx`) with `_wrap_pai_ctx()` emission. + Every stream event carries an AKD `RunContext` whose typed fields reflect + pai state (lossy but inspectable) and whose `pai_run_context` extra holds + the lossless pai object. Input-side helpers prefer the extra when present, + so callers round-trip `event.run_context` verbatim. `arun` keeps its + `OutputSchema` return contract; `agent.last_run_context` exposes the same + wrapped context for multi-turn continuation. **Concurrency caveat**: + `_live_pai_ctx` is instance-scoped, so concurrent `arun` / `astream` calls on + one agent will race — use a fresh agent per run or move to the queue-based + `event_stream_handler` design (tracked follow-up). +- **Trimming is off by default** on `PydanticAIBaseAgentConfig`: the naive + ratio-trimmer breaks pydantic_ai's invariant that every `tool` message must + follow an `assistant` with matching `tool_calls`. A pydantic_ai-aware + trimmer is a follow-up. +- **Litellm validators are silenced**: `BaseAgentConfig`'s model_validator + hooks call `litellm.get_model_info`, which expects bare model names, not + `provider:model`. Both are overridden to no-ops. diff --git a/docs/pydantic_ai_base.svg b/docs/pydantic_ai_base.svg new file mode 100644 index 0000000..b6da994 --- /dev/null +++ b/docs/pydantic_ai_base.svg @@ -0,0 +1 @@ +

config

wraps live pai ctx

installs ctx_capture

uses

uses

uses

uses

accepts

reads pai_run_context extra

produces

consumes (terminal)

attaches to events

accepts

produces

raises

returns

(Thinking) returns

isinstance (adapt)

passthrough in check_output

raises in validator

«pydantic-ai»

PAIAgent

+output_validator «decorator»

+system_prompt «decorator»

+run(prompt, deps, message_history, usage) : RunResult

+run_stream_events(...) : AsyncIterator

«pydantic-ai»

AbstractCapability

«pydantic-ai»

Hooks

+on.before_model_request

+on.before_tool_execute

«pydantic-ai»

PAITool

+name / description / function

«pydantic-ai Exception»

ModelRetry

«pydantic-ai»

AgentRunResultEvent

+result.output

«akd mixin»

ConfigBindingMixin

+init_subclass() : auto-exposes config fields as properties

+skips in-dict redeclarations(e.g. system_prompt)

«akd»

BaseAgentConfig

+model_name / system_prompt / tools

+reasoning_effort / num_retries

+max_tool_iterations / max_tool_calls

+reflection_prompt / enable_trimming / trim_ratio

«akd»

BaseTool

+name / description

+arun / as_function

«akd»

InputSchema

«akd»

OutputSchema

«akd»

TextOutput

«akd»

AKDStreamEvent

+Completed / StreamingToken / Thinking

+ToolCalling / ToolResult

+run_context: AKDRunContext

«akd»

AKDRunContext

+messages: list[dict] | None

+usage: RunUsage

+run_id: str | None

+pai_run_context: pai.RunContext «extra / lossless»

«Protocol»

TokenCounts

+input_tokens / output_tokens / requests

«Protocol»

RunContextProtocol

+messages / usage / run_id

«Protocol»

AKDExecutable

+input_schema / output_schema / config_schema

+name / description

+arun(params, run_context) : Any

+astream(params, run_context) : AsyncIterator

«Protocol»

AKDTool

+name / description

+as_function() : Callable

«_context_adapter.py»

ContextAdapter

+_akd_dicts_to_pai_messages(msgs) : list

+_usage_to_pai(usage) : PAIRunUsage

+_pai_messages_to_akd_dicts(msgs) : list[dict]

+_pai_usage_to_akd_usage(pai_usage) : AKDRunUsage

+_message_history_from_run_context(ctx) : list

+_usage_from_run_context(ctx) : PAIRunUsage

«_event_translator.py»

EventTranslator

+pai_event_to_akd_event(event, run_context=None) : AKDStreamEvent

«_tool_adapter.py»

ToolAdapter

+akd_to_pai_tool(tool) : PAITool

«_capabilities.py»

CapabilityFactories

+ToolCallLimits(max_iterations, max_calls) : Hooks

+ReflectionCapability(prompt) : Hooks

+make_ratio_trimmer(ratio) : Callable

PydanticAIBaseAgentConfig

+capabilities: list

+history_processors: list

+enable_trimming: bool

+extra allowed for forward-compat

PydanticAIBaseAgent<InSchema OutSchema>

+input_schema: type

+output_schema: type

+config_schema: type

+config: PydanticAIBaseAgentConfig

+_live_pai_ctx: Any

── AKD contract ──

── RunContext propagation ──

+last_run_context AKDRunContext? «property»

── hooks ──

+arun(params, run_context) : OutSchema

+astream(params, run_context) : AsyncIterator

+_build_run_context_capture() : Hooks

+_wrap_pai_ctx() : AKDRunContext

+_to_prompt(params) : str

+_deps_from_run_context(ctx) : Any

+_build_capabilities_from_scalars() : list

+_build_history_processors_from_scalars() : list

+_adapt_tools(tools) : list

+check_output(output) : str

diff --git a/examples/cmr_care_pydantic.py b/examples/cmr_care_pydantic.py new file mode 100644 index 0000000..de6fab4 --- /dev/null +++ b/examples/cmr_care_pydantic.py @@ -0,0 +1,497 @@ +"""CMR CARE Pydantic Agent — NASA CMR dataset discovery on ``PydanticAIBaseAgent``. + +Parallel to :mod:`akd_ext.agents.cmr_care` (which is built on ``OpenAIBaseAgent``), +this module rebuilds the same agent on top of ``PydanticAIBaseAgent`` so the +new base class can be validated against a real-shaped AKD agent. Input/output +schemas are reused from :mod:`akd_ext.agents.cmr_care` to keep parity. + +The ``system_prompt`` is a **placeholder** — replace +``CMR_CARE_PYDANTIC_SYSTEM_PROMPT`` below with the real CARE prompt before +running against a live model. +""" + +from __future__ import annotations + +import os + +from typing import Any, Literal + +from pydantic import Field +from pydantic_ai.capabilities import MCP + +from akd._base import TextOutput + +from akd_ext.agents._base import ( + PydanticAIBaseAgent, + PydanticAIBaseAgentConfig, +) +from akd_ext.agents.cmr_care import ( + CMRCareAgentInputSchema, + CMRCareAgentOutputSchema, +) + +from loguru import logger + +# ----------------------------------------------------------------------------- +# System Prompt +# ----------------------------------------------------------------------------- +# PLACEHOLDER — paste the real CMR CARE system prompt here before running +# this agent against a live model. The parity tests use ``TestModel`` so they +# don't depend on the prompt content. + +CMR_CARE_PYDANTIC_SYSTEM_PROMPT = """ROLE + You are the NASA Earthdata / CMR Scientific Data Discovery Agent. + You are a non-decision-making, human-in-the-loop scientific data discovery assistant whose sole function is to help users discover, organize, and understand NASA Earthdata CMR datasets relevant to Earth science questions. + You are not a scientific authority, analyst, or recommender. + + OBJECTIVE + Enable transparent, reproducible, and user-controlled discovery and ranking of NASA Earthdata (CMR) datasets that may answer an Earth science question, including indirect (multi-hop) discovery when direct datasets are insufficient. + Your success criteria are: + Scientific relevance is reflected only through metadata + All assumptions are surfaced and confirmed by the user + Users clearly understand why datasets appear + No dataset is selected, endorsed, or judged for suitability + + CONTEXT & INPUTS + You operate only within Earth science domains: + Atmosphere + Ocean + Land + Cryosphere + Biosphere + Solid Earth + You accept: + A free-text science question + An explicitly selected user expertise level (Intermediate / Advanced) + You may use only the following tools and data sources along with the attached context documents:  + NASA CMR Search API (REST) — collection discovery only + GCMD Keyword Management System (KMS) — vocabulary mapping only + Semantic Scholar API — optional, user-approved indirect discovery only  + Google Scholar as a last resort. + Earthdata Search Web App — link handoff only (no API calls) + + CONSTRAINTS & STYLE RULES + Non-Negotiable Guardrails You must never: + Recommend, select, or endorse datasets + Claim quality, accuracy, uncertainty, or suitability + Draw conclusions, trends, causality, or implications + Infer or fabricate missing metadata + Automate spatial, temporal, or variable assumptions + Execute searches without explicit user confirmation + Perform downloads or request credentials + Operate outside Earth science + All missing or ambiguous metadata must be treated as unknown. + All indirect (multi-hop) inference requires explicit user approval. + + PROCESS + You must follow this canonical reasoning loop exactly: + Primary Loop (Direct Discovery First) + Interpret the user query into: + Phenomenon + Explicit variables + Expand scientific synonyms (candidate terms only) + Clarify (blocking): + Variables + Spatial bounds + Temporal bounds + Indirect inference permission (if needed) + Map terms → GCMD keywords + Translate GCMD concepts → CMR API parameters + Search CMR Collections (retrieve multiple candidates) + Rank datasets: + Primary: metadata relevance + Secondary: usage (tie-breaker only) + Explain relevance and gaps (no recommendations) + Conditional Multi-Hop Loop (Only If Needed) + Detect gaps in direct results + Identify indirect variables + Search Semantic Scholar (rate-limited) + Exclude variables that cannot map to GCMD + Obtain explicit user approval + Re-run the entire loop + If scope is non-Earth science → respond "I'm sorry, this query falls outside my area of expertise in Earth science data discovery. I'm unable to assist with this request." and stop. + + + OUTPUT FORMAT + All responses must follow this structure exactly. No free-form text is allowed outside these sections. + 1. Clarifying Questions + You must ask clarifying questions to the user to reduce ambiquity in search. + 2. Interpreted Scope + Restate user intent without inference + Separate confirmed inputs vs unresolved ambiguities + List phenomenon, variables, spatial & temporal bounds + 3. Curated / Ranked CMR Dataset List + For each dataset (CMR only), include: + Short Name + CMR Concept ID + Variables (verbatim) + Temporal Coverage + Spatial Coverage + ProcessingLevelId + Explicitly listed missing or ambiguous metadata + Ranking reflects metadata relevance only. + 4. Search Reproducibility Log + CMR endpoints used + Query parameters + GCMD mappings + Paging behavior + Ranking logic + UTC timestamps + 5. Fact-Check / User Verification List + Items the user must confirm manually + Variable definitions, QA flags, caveats + Documentation links only + No interpretation + + + CONDITIONAL SECTIONS + Tabular Summary → only if ≥2 datasets + JSON Audit Block → only if datasets returned (pure JSON, null for missing fields, no inference) + + + STOP / DEGRADED OUTPUT + If blocked due to missing inputs, ambiguity, or tool failure, output only: + “Here’s what I cannot determine and what I need from you.” + Then list: + What cannot be determined + Why + Exact user action required + Stop immediately. + + ADDITIONAL CONTEXT : + + # CMR Search API Documentation + + ## Overview + + The Common Metadata Repository (CMR) Search API provides access to NASA Earth science metadata, enabling programmatic discovery and retrieval of collections, granules, and related concepts. This REST-based API supports multiple search parameters, result formats, and authentication methods. + + **Base URL**: `https://cmr.earthdata.nasa.gov/search/` + + ## Key Concepts & Terminology + + ### Core CMR Concepts + - **Collection**: A grouping of related data files or granules, representing a dataset + - **Granule**: Individual data files within a collection (e.g., a single HDF file) + - **Concept ID**: Unique identifier for CMR concepts in format `-` + - Collection concept IDs start with "C" (e.g., `C123456-LPDAAC_ECS`) + - Granule concept IDs start with "G" (e.g., `G123456-LPDAAC_ECS`) + - **Provider**: Data center or organization that hosts the data (e.g., LPDAAC_ECS, NSIDC_ECS) + - **Instrument**: Sensor that collected the data (e.g., MODIS, VIIRS, ASTER) + - **Platform**: Satellite or aircraft carrying the instrument (e.g., Terra, Aqua, Landsat-8) + - **Dataset**: Another term for collection, representing a coherent set of data + + ### Metadata Standards + - **UMM** (Unified Metadata Model): NASA's standard for Earth science metadata + - **ECHO**: Legacy metadata format (Earth Observing System Clearinghouse) + - **DIF** (Directory Interchange Format): GCMD metadata format + - **STAC** (Spatio-Temporal Asset Catalog): Modern geospatial metadata standard + + ## API Endpoints + + ### Primary Search Endpoints + - **Collections**: `/search/collections` - Search for datasets/collections + - **Granules**: `/search/granules` - Search for individual data files + - **Variables**: `/search/variables` - Search for science variables + - **Services**: `/search/services` - Search for data services + - **Tools**: `/search/tools` - Search for analysis tools + + ### Utility Endpoints + - **Autocomplete**: `/search/autocomplete` - Get search suggestions + - **Facets**: Access faceted search capabilities + + ## Authentication + + ### Token Types + 1. **EDL Bearer Token**: Earth Data Login token + 2. **Launchpad Token**: Legacy authentication system + + ### Authentication Methods + - **Authorization Header**: `Authorization: Bearer ` + - **Token Parameter**: `?token=` in URL + + ### Example + ```bash + # Using Authorization header + curl -H "Authorization: Bearer YOUR_TOKEN" "https://cmr.earthdata.nasa.gov/search/collections" + + # Using token parameter + curl "https://cmr.earthdata.nasa.gov/search/collections?token=YOUR_TOKEN" + ``` + + ## Request Parameters + + ### Common Parameters + - `page_size`: Number of results per page (default: 10, max: 2000) + - `page_num`: Page number to return (1-based) + - `sort_key`: Field(s) to sort results by + - `concept_id`: Search by unique identifier + - `provider`: Filter by data provider + - `token`: Authentication token + + ### Collection Search Parameters + - `keyword`: Text search across collection metadata + - `short_name`: Collection short name + - `version`: Collection version + - `temporal`: Temporal range in format `YYYY-MM-DDTHH:mm:ssZ,YYYY-MM-DDTHH:mm:ssZ` + - `platform`: Platform/satellite name + - `instrument`: Instrument name + - `science_keywords`: Science keyword hierarchy + - `project`: Project or mission name + - `processing_level`: Data processing level (L0, L1A, L1B, L2, L3, L4) + - `data_center`: Data center name + - `archive_center`: Archive center name + - `spatial`: Spatial search parameters + + ### Granule Search Parameters + - `collection_concept_id`: Filter granules by collection + - `temporal`: Temporal range for granule search + - `bounding_box`: Spatial bounding box `[west,south,east,north]` + - `point`: Point search `[longitude,latitude]` + - `polygon`: Polygon search (WKT format) + - `producer_granule_id`: Producer-assigned granule ID + - `online_only`: Return only online-accessible granules + - `downloadable`: Return only downloadable granules + - `cloud_cover`: Cloud cover percentage range + + ### Advanced Parameters + - `options[case_sensitive]`: Case-sensitive search (true/false) + - `options[pattern]`: Enable pattern matching (true/false) + - `options[ignore_case]`: Ignore case in search (true/false) + - `options[and]`: AND logic for multiple values (true/false) + + ## Response Formats + + ### Supported Formats + - **JSON**: Default format, comprehensive metadata + - **XML**: Various XML schemas available + - **ATOM**: XML feed format + - **CSV**: Comma-separated values + - **KML**: Keyhole Markup Language for mapping + - **STAC**: Spatio-Temporal Asset Catalog + - **UMM JSON**: Unified Metadata Model JSON + + ### Format Selection + - **Accept Header**: `Accept: application/json` + - **Extension**: `.json`, `.xml`, `.atom`, `.csv`, `.kml`, `.stac` + - **Format Parameter**: `?format=json` + + ### Example Response Structure (JSON) + ```json + { + "hits": 1234, + "took": 45, + "items": [ + { + "concept_id": "C123456-LPDAAC_ECS", + "revision_id": 1, + "provider_id": "LPDAAC_ECS", + "short_name": "MOD09A1", + "version_id": "6.1", + "meta": { + "concept_type": "collection", + "native_id": "MOD09A1_V6.1", + "provider_id": "LPDAAC_ECS" + }, + "umm": { + "EntryTitle": "MODIS/Terra Surface Reflectance 8-Day L3 Global 500m SIN Grid V061", + "ShortName": "MOD09A1", + "Version": "6.1", + "DataDates": [ + { + "Date": "2000-02-18T00:00:00.000Z", + "Type": "CREATE" + } + ], + "Platforms": [ + { + "ShortName": "Terra", + "LongName": "Earth Observing System, Terra" + } + ] + } + } + ] + } + ``` + + ## Rate Limiting & Performance + + ### Limits + - **Request Timeout**: 180 seconds maximum + - **Query Timeout**: 170 seconds internal timeout + - **Rate Limiting**: 429 status code with `retry-after` header + - **URL Length**: ~6,000 characters maximum + + ### Optimization Tips + - Use `page_size` for pagination instead of large single requests + - Implement exponential backoff for rate limit responses + - Use specific search parameters to reduce result sets + - Consider using scroll/search-after for large result sets + + ## Error Handling + + ### Common HTTP Status Codes + - **200**: Success + - **400**: Bad Request (invalid parameters) + - **401**: Unauthorized (authentication required) + - **403**: Forbidden (insufficient permissions) + - **404**: Not Found + - **429**: Too Many Requests (rate limited) + - **500**: Internal Server Error + + ### Error Response Format + ```json + { + "errors": [ + { + "code": "INVALID_PARAMETER", + "message": "Parameter 'temporal' is not valid" + } + ] + } + ``` + + ## Common Usage Patterns + + ### 1. Find Collections by Keyword + ```bash + GET /search/collections?keyword=temperature&provider=NSIDC_ECS + ``` + + ### 2. Get Granules for a Specific Collection + ```bash + GET /search/granules?collection_concept_id=C123456-LPDAAC_ECS&temporal=2023-01-01T00:00:00Z,2023-12-31T23:59:59Z + ``` + + ### 3. Spatial Search + ```bash + GET /search/collections?bounding_box=-180,-90,180,90&platform=Terra + ``` + + ### 4. Paginated Results + ```bash + GET /search/collections?page_size=50&page_num=2&sort_key=short_name + ``` + + ## Integration Notes for MCP Server + + ### Typical Workflow + 1. **Collection Discovery**: Search collections using keywords, platform, or instrument + 2. **Collection Selection**: Choose appropriate collection based on metadata + 3. **Granule Search**: Find granules within selected collection using temporal/spatial filters + 4. **Data Access**: Use granule metadata to access actual data files + + ### Key Fields for MCP Integration + - **Collection**: `concept_id`, `short_name`, `version_id`, `entry_title` + - **Granule**: `concept_id`, `producer_granule_id`, `online_access_urls` + - **Temporal**: `temporal_extent`, `temporal` + - **Spatial**: `bounding_box`, `polygons` + + ### Authentication Considerations + - EDL tokens are preferred for new integrations + - Tokens should be securely stored and refreshed as needed + - Consider implementing token validation before API calls + + ## Additional Resources + + - **CMR Documentation**: https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html + - **UMM Specification**: https://earthdata.nasa.gov/eosdis/science-system-description/eosdis-components/common-metadata-repository +""" + + +# ----------------------------------------------------------------------------- +# Configuration +# ----------------------------------------------------------------------------- + + +def get_default_cmr_capabilities() -> list[Any]: + """Default CMR MCP capability for pydantic_ai. + + Points at the CMR MCP server (override via ``CMR_MCP_URL`` env var) and + exposes the same allowed-tool subset the OpenAI-based ``cmr_care.py`` + uses, so the two agents hit the same backend surface. + """ + return [ + MCP( + # Trailing slash matters: the endpoint returns a 307 redirect + # to the slashed form, which the MCP streamable-HTTP client + # won't follow on POST (per HTTP semantics for POST redirects). + url=os.environ.get( + "CMR_MCP_URL", + "https://w4hu71445m.execute-api.us-east-1.amazonaws.com/mcp/cmr/mcp/", + ), + allowed_tools=[ + "search_collections", + "get_granules", + "get_collection_metadata", + ], + description="CMR MCP server for NASA dataset discovery", + ), + ] + + +class CMRCarePydanticConfig(PydanticAIBaseAgentConfig): + """Config for ``CMRCarePydanticAgent``. + + Mirrors ``CMRCareConfig`` from ``cmr_care.py`` but wires MCP via + pydantic_ai's native ``MCP`` capability (passed through the inherited + ``capabilities`` field), not the OpenAI Agents SDK's ``HostedMCPTool``. + """ + + description: str = Field( + default=( + "Earth science dataset discovery agent using NASA's Common Metadata Repository (CMR). " + "Helps users discover, organize, and understand NASA Earthdata datasets across atmosphere, " + "ocean, land, cryosphere, biosphere, and solid earth domains. " + "Outputs are delivered via a structured schema and interactive chat with the user " + "for clarification, guidance, approval gates, or status updates." + ), + ) + system_prompt: str = Field(default=CMR_CARE_PYDANTIC_SYSTEM_PROMPT) + model_name: str = Field(default="openai:gpt-5.2") + reasoning_effort: Literal["low", "medium", "high"] | None = Field(default="medium") + capabilities: list[Any] = Field(default_factory=get_default_cmr_capabilities) + + +# ----------------------------------------------------------------------------- +# Agent +# ----------------------------------------------------------------------------- + + +class CMRCarePydanticAgent(PydanticAIBaseAgent[CMRCareAgentInputSchema, CMRCareAgentOutputSchema]): + """Earth Science Data Search Agent on ``PydanticAIBaseAgent``. + + Parity build of ``CMRCareAgent`` on the new Pydantic AI base class; uses + the same input/output schemas so downstream callers can swap between the + two implementations without changing their contract. + """ + + input_schema = CMRCareAgentInputSchema + output_schema = CMRCareAgentOutputSchema | TextOutput + config_schema = CMRCarePydanticConfig + + def check_output(self, output) -> str | None: + if isinstance(output, CMRCareAgentOutputSchema) and not output.result.strip(): + return "Result is empty. Provide search reasoning and details." + return super().check_output(output) + + +__all__ = [ + "CMR_CARE_PYDANTIC_SYSTEM_PROMPT", + "CMRCarePydanticAgent", + "CMRCarePydanticConfig", +] + +if __name__ == "__main__": + import asyncio + + async def main(): + agent = CMRCarePydanticAgent(CMRCarePydanticConfig(debug=True)) + logger.info(f"Agent description: {agent.description}") + question = "Can you find me datasets about sea ice?" + + async for event in agent.astream(CMRCareAgentInputSchema(query=question)): + logger.info(event) + + asyncio.run(main()) diff --git a/pyproject.toml b/pyproject.toml index f42950e..5143ede 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,8 +18,10 @@ classifiers = [ requires-python = ">=3.12" dependencies = [ "akd @ git+https://github.com/NASA-IMPACT/accelerated-discovery.git@develop", - "fastmcp>=2.0.0", + "fastmcp>=2.0.0,<3.2.4", + "griffe>=1.0.0,<2", "openai-agents>=0.6.7", + "pydantic-ai>=1.81.0", "PyGithub>=2.1.1", ] @@ -51,6 +53,7 @@ exclude = ["tests*"] [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] +pythonpath = ["."] python_files = ["test_*.py", "*_test.py"] python_functions = ["test_*"] addopts = [ @@ -70,7 +73,7 @@ markers = [ ] [tool.uv] -override-dependencies = ["wrapt>=1.14"] +override-dependencies = ["wrapt>=1.14", "mistralai<2"] [tool.ruff] target-version = "py312" @@ -85,6 +88,7 @@ quote-style = "double" [dependency-groups] dev = [ + "marimo>=0.23.1", "pytest>=9.0.2", "pytest-asyncio>=1.3.0", "pytest-cov>=7.0.0", diff --git a/tests/agents/test_base_pydantic.py b/tests/agents/test_base_pydantic.py new file mode 100644 index 0000000..6e5f11d --- /dev/null +++ b/tests/agents/test_base_pydantic.py @@ -0,0 +1,440 @@ +"""Tests for ``PydanticAIBaseAgent`` and its supporting subpackage. + +Uses pydantic_ai's ``TestModel`` for deterministic behavior instead of +touching real model providers. Existing agents (``CMRCareAgent`` et al.) +are deliberately untouched here — this suite exercises a dedicated test +agent built on top of ``PydanticAIBaseAgent`` instead. +""" + +from __future__ import annotations + +import pytest +from pydantic import Field +from pydantic_ai import ModelRetry +from pydantic_ai.models.test import TestModel + +from akd._base import InputSchema, OutputSchema, TextOutput +from akd._base.errors import SchemaValidationError +from akd._base.protocols import ( + AKDExecutable, + AKDTool, + RunContextProtocol, + TokenCounts, +) +from akd.tools._base import BaseTool + +from akd_ext.agents._base import PydanticAIBaseAgent, PydanticAIBaseAgentConfig +from akd_ext.agents._base.pydantic_ai._utils import akd_to_pai_tool + + +# --------------------------------------------------------------------------- +# Fixtures: a small test agent exercising the base class's surface +# --------------------------------------------------------------------------- + + +class _EchoInput(InputSchema): + """Input schema for the echo test agent.""" + + query: str = Field(..., description="Free-form query text") + + +class _EchoOutput(OutputSchema): + """Output schema for the echo test agent.""" + + answer: str = Field(..., description="Echoed answer from the agent") + + def is_empty(self) -> bool: + return not self.answer.strip() + + +class _EchoConfig(PydanticAIBaseAgentConfig): + """Config for the echo test agent.""" + + +class _EchoAgent(PydanticAIBaseAgent[_EchoInput, _EchoOutput]): + """Minimal test agent exercising the base class's feature surface.""" + + input_schema = _EchoInput + output_schema = _EchoOutput | TextOutput + config_schema = _EchoConfig + + +# --------------------------------------------------------------------------- +# Structural tests (no model interaction) +# --------------------------------------------------------------------------- + + +def test_agent_instantiates_with_defaults(): + agent = _EchoAgent() + assert isinstance(agent.config, _EchoConfig) + assert agent.input_schema is _EchoInput + + +def test_agent_instantiates_with_custom_config(): + cfg = _EchoConfig(model_name="test", description="Echo agent for tests") + agent = _EchoAgent(cfg) + assert agent.config is cfg + + +def test_metaclass_auto_exposes_config_fields(): + """agent.model_name / agent.description route to self.config.* without hand-written properties.""" + cfg = _EchoConfig(model_name="test", description="hello") + agent = _EchoAgent(cfg) + assert agent.model_name == "test" + assert agent.description == "hello" + + +def test_system_prompt_not_shadowed_by_auto_exposure(): + """pydantic_ai's ``system_prompt`` decorator method must still work.""" + agent = _EchoAgent() + # Must be a callable, not a string + assert callable(agent.system_prompt) + # Must come from pydantic_ai.Agent, not a property descriptor + from pydantic_ai import Agent as PAIAgent + + assert _EchoAgent.system_prompt is PAIAgent.system_prompt + + +def test_agent_is_runtime_akd_agent(): + """Explicit Protocol inheritance means isinstance(agent, AKDExecutable) works.""" + agent = _EchoAgent() + assert isinstance(agent, AKDExecutable) + + +def test_config_is_runtime_run_context_protocol(): + """Sanity check: TokenCounts and RunContextProtocol are importable and checkable.""" + # Concrete instances satisfy these structurally in practice; here we just + # confirm the symbols exist and are runtime-checkable. + assert hasattr(TokenCounts, "_is_runtime_protocol") + assert hasattr(RunContextProtocol, "_is_runtime_protocol") + + +# --------------------------------------------------------------------------- +# check_output bridge +# --------------------------------------------------------------------------- + + +def test_check_output_passes_through_text_output(): + agent = _EchoAgent() + assert agent.check_output(TextOutput(content="Ask the user?")) is None + + +def test_check_output_rejects_empty_structured_output(): + agent = _EchoAgent() + msg = agent.check_output(_EchoOutput(answer=" ")) + assert isinstance(msg, str) + assert "empty" in msg.lower() + + +def test_check_output_accepts_non_empty_structured_output(): + agent = _EchoAgent() + assert agent.check_output(_EchoOutput(answer="real answer")) is None + + +# --------------------------------------------------------------------------- +# arun / astream with TestModel (no external calls) +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_arun_returns_output_schema(): + agent = _EchoAgent() + with agent.override(model=TestModel()): + result = await agent.arun(_EchoInput(query="hello")) + # TestModel produces a best-effort structured output matching the schema + assert isinstance(result, (_EchoOutput, TextOutput)) + + +@pytest.mark.asyncio +async def test_astream_yields_akd_stream_events(): + agent = _EchoAgent() + from akd._base import StreamEvent + + with agent.override(model=TestModel()): + events = [ev async for ev in agent.astream(_EchoInput(query="hello"))] + # Every yielded event must be an AKD StreamEvent (or subclass thereof); may be empty + # with TestModel depending on its stream behavior, but nothing foreign should appear. + assert all(isinstance(ev, StreamEvent) for ev in events) + + +@pytest.mark.asyncio +async def test_astream_emits_completed_event_with_output(): + """astream must emit a terminal CompletedEvent carrying the final output + so callers matching the AKD contract (e.g. agent_chat.py) see the answer.""" + from akd._base import CompletedEvent + + agent = _EchoAgent() + with agent.override(model=TestModel()): + events = [ev async for ev in agent.astream(_EchoInput(query="hello"))] + + # At least one CompletedEvent, and the last event must carry the output. + completed = [ev for ev in events if isinstance(ev, CompletedEvent)] + assert completed, "astream should emit a CompletedEvent when the run finishes" + assert isinstance(events[-1], CompletedEvent) + assert completed[-1].data.output is not None + + +@pytest.mark.asyncio +async def test_astream_run_context_available_after_run(): + """Every emitted event — including the terminal CompletedEvent — must + carry the live pydantic_ai ``RunContext`` under ``run_context.pai_run_context``. + + Consumers rely on this to drive multi-turn continuation (Flavor B HITL): + they read ``pai_run_context.messages`` / ``.usage`` from the last event + and feed them back into the next ``astream`` call. + """ + from akd._base import CompletedEvent + from pydantic_ai import RunContext as PAIRunContext + + agent = _EchoAgent() + with agent.override(model=TestModel()): + events = [ev async for ev in agent.astream(_EchoInput(query="hello"))] + + # Every event carries a run_context — the Hooks capability fires before + # anything gets translated, so the live ctx is always captured in time. + for ev in events: + pai_ctx = getattr(ev.run_context, "pai_run_context", None) + assert pai_ctx is not None, f"{type(ev).__name__} missing pai_run_context" + assert isinstance(pai_ctx, PAIRunContext) + + # Terminal event specifically: the captured context reflects a real run — + # at least one model request fired, and pydantic_ai attached a message + # history to the ctx. + completed = [ev for ev in events if isinstance(ev, CompletedEvent)] + assert completed + terminal_pai_ctx = completed[-1].run_context.pai_run_context + assert terminal_pai_ctx.usage.requests >= 1 + assert isinstance(terminal_pai_ctx.messages, list) + assert len(terminal_pai_ctx.messages) >= 1 + + +@pytest.mark.asyncio +async def test_arun_run_context_available_after_run(): + """After ``await agent.arun(...)``, the agent's ``_live_pai_ctx`` holds + the pydantic_ai ``RunContext`` captured during the run. + + ``arun`` itself returns the output value (per the AKD contract); the + captured ctx is retained on the instance so callers can inspect it via + ``agent.last_run_context``. + """ + from pydantic_ai import RunContext as PAIRunContext + + agent = _EchoAgent() + # Before any run, nothing has been captured. + assert agent._live_pai_ctx is None + assert agent.last_run_context is None + + with agent.override(model=TestModel()): + result = await agent.arun(_EchoInput(query="hello")) + + assert isinstance(result, (_EchoOutput, TextOutput)) + assert agent._live_pai_ctx is not None + assert isinstance(agent._live_pai_ctx, PAIRunContext) + assert agent._live_pai_ctx.usage.requests >= 1 + + # last_run_context wraps the pai ctx; the lossless extra is populated. + ctx = agent.last_run_context + assert ctx is not None + assert isinstance(ctx.pai_run_context, PAIRunContext) + + +@pytest.mark.asyncio +async def test_multi_turn_via_last_run_context(): + """Feeding ``agent.last_run_context`` from turn 1 into turn 2 causes + pydantic_ai to see the full prior message history (via the lossless + ``pai_run_context`` extra).""" + agent = _EchoAgent() + with agent.override(model=TestModel()): + await agent.arun(_EchoInput(query="first turn")) + ctx_after_turn_1 = agent.last_run_context + assert ctx_after_turn_1 is not None + turn_1_message_count = len(ctx_after_turn_1.pai_run_context.messages) + + await agent.arun(_EchoInput(query="follow-up"), run_context=ctx_after_turn_1) + ctx_after_turn_2 = agent.last_run_context + + assert ctx_after_turn_2 is not None + assert len(ctx_after_turn_2.pai_run_context.messages) > turn_1_message_count + + +def test_input_side_reads_pai_run_context(): + """Message history and usage are pulled verbatim from + ``run_context.pai_run_context`` — no conversion, no fallback branches.""" + from akd._base.structures import RunContext as AKDRunContext + from pydantic_ai import RunContext as PAIRunContext + from pydantic_ai.messages import ModelRequest, UserPromptPart + from pydantic_ai.result import RunUsage as PAIRunUsage + + from akd_ext.agents._base.pydantic_ai._base import ( + _message_history_from_run_context, + _usage_from_run_context, + ) + + pai_messages = [ModelRequest(parts=[UserPromptPart(content="pai-truth")])] + pai_usage = PAIRunUsage(input_tokens=10, output_tokens=20, requests=3) + pai_ctx = PAIRunContext(deps=None, model=None, usage=pai_usage) + pai_ctx.messages = pai_messages + + akd_ctx = AKDRunContext(pai_run_context=pai_ctx) + + history = _message_history_from_run_context(akd_ctx) + assert history == pai_messages + assert history[0].parts[0].content == "pai-truth" + + usage = _usage_from_run_context(akd_ctx) + assert usage is pai_usage + assert usage.input_tokens == 10 + + +def test_input_side_returns_none_without_pai_run_context(): + """AKD ``RunContext`` with no ``pai_run_context`` extra yields ``None`` — + Phase 1 does not convert AKD-shape typed fields into pai shapes.""" + from akd._base.structures import RunContext as AKDRunContext + from akd._base.structures import RunUsage as AKDRunUsage + + from akd_ext.agents._base.pydantic_ai._base import ( + _message_history_from_run_context, + _usage_from_run_context, + ) + + akd_ctx = AKDRunContext( + messages=[{"role": "user", "content": "hi"}], + usage=AKDRunUsage(input_tokens=5, output_tokens=7, requests=1), + ) + + assert _message_history_from_run_context(akd_ctx) is None + assert _usage_from_run_context(akd_ctx) is None + assert _message_history_from_run_context(None) is None + assert _usage_from_run_context(None) is None + + +# --------------------------------------------------------------------------- +# Tool adapter +# --------------------------------------------------------------------------- + + +class _GreetInput(InputSchema): + """Input schema for the greet tool.""" + + name: str = Field(..., description="Name to greet") + + +class _GreetOutput(OutputSchema): + """Output schema for the greet tool.""" + + greeting: str = Field(..., description="Generated greeting") + + +class _GreetTool(BaseTool[_GreetInput, _GreetOutput]): + """A trivial greeting tool for adapter tests.""" + + input_schema = _GreetInput + output_schema = _GreetOutput + + async def _arun(self, params: _GreetInput) -> _GreetOutput: + return _GreetOutput(greeting=f"Hello, {params.name}!") + + +@pytest.mark.asyncio +async def test_tool_adapter_produces_callable_pai_tool(): + from pydantic_ai import Tool as PAITool + + tool = _GreetTool() + pai = akd_to_pai_tool(tool) + assert isinstance(pai, PAITool) + + +@pytest.mark.asyncio +async def test_tool_adapter_validation_error_becomes_model_retry(): + """If the wrapped AKD tool raises a validation error, the adapter must + re-raise as ModelRetry so pydantic_ai retries instead of halting.""" + + class _AlwaysRaisesInput(InputSchema): + """Input schema for always-raises tool.""" + + query: str = Field(...) + + class _AlwaysRaisesOutput(OutputSchema): + """Output schema for always-raises tool.""" + + result: str = Field(...) + + class _AlwaysRaisesTool(BaseTool[_AlwaysRaisesInput, _AlwaysRaisesOutput]): + """Tool that always raises SchemaValidationError.""" + + input_schema = _AlwaysRaisesInput + output_schema = _AlwaysRaisesOutput + + async def _arun(self, params: _AlwaysRaisesInput) -> _AlwaysRaisesOutput: + raise SchemaValidationError("bad shape") + + pai = akd_to_pai_tool(_AlwaysRaisesTool()) + # Call the wrapped function directly to bypass pydantic_ai's tool manager + with pytest.raises(ModelRetry): + await pai.function(query="whatever") + + +def test_tool_adapter_accepts_akdtool_protocol(): + """The adapter's parameter type is the AKDTool protocol; concrete AKD BaseTools satisfy it structurally.""" + assert isinstance(_GreetTool(), AKDTool) + + +@pytest.mark.asyncio +async def test_existing_akd_tool_is_pai_compatible(): + """Real shipped AKD tool (``DummyTool``) adapts cleanly to pydantic_ai. + + Compatibility checks: + + 1. The adapter returns a real ``pydantic_ai.Tool``. + 2. ``name`` and ``description`` are populated (pydantic_ai uses these for + the tool definition it advertises to the model). + 3. The wrapped function's signature exposes the AKD ``InputSchema``'s + fields as parameters (so pydantic_ai's JSON-schema introspection + produces the same shape the AKD schema declares). + 4. Invoking the wrapped function with valid kwargs returns the AKD + ``OutputSchema`` instance — the conversion is invocation-safe, not + just structural. + 5. Dropping the tool into ``PydanticAIBaseAgentConfig(tools=[...])`` and + building a ``PydanticAIBaseAgent`` succeeds; the agent's toolset lists + the adapted tool under the expected name. + """ + import inspect + + from pydantic_ai import Tool as PAITool + + from akd_ext.tools.dummy import DummyInputSchema, DummyOutputSchema, DummyTool + + akd_tool = DummyTool() + pai = akd_to_pai_tool(akd_tool) + + assert isinstance(pai, PAITool) + assert pai.name == akd_tool.name + assert pai.description and akd_tool.description.splitlines()[0] in pai.description + + # Signature preserved from AKDInputSchema → kwargs pydantic_ai can introspect. + sig = inspect.signature(pai.function) + assert "query" in sig.parameters + assert sig.return_annotation is DummyOutputSchema + + # Invocation round-trips through the AKD tool. + result = await pai.function(query="compatibility-check") + assert isinstance(result, DummyOutputSchema) + assert result.query == "compatibility-check" + + # Registration on a PydanticAIBaseAgent: the config path wraps the AKD + # tool via ``_adapt_tools``; the agent's toolset must end up carrying + # it. We use _EchoAgent's schemas since the smoke agent shape is + # immaterial — we only care that registration succeeds. + agent = _EchoAgent(_EchoConfig(tools=[DummyTool()])) + assert agent.toolset is not None + # Walk the registered toolsets to find our adapted tool by name. + assert any( + pai.name in getattr(ts, "tools", {}) or pai.name in getattr(ts, "_tools", {}) + for ts in getattr(agent, "toolsets", []) + ), f"expected adapted tool {pai.name!r} to be reachable on agent.toolsets" + + # Sanity: the AKD tool still satisfies the AKDTool protocol after + # passing through the adapter (nothing mutates the source). + assert isinstance(akd_tool, AKDTool) + # And the input schema reference is intact. + assert akd_tool.input_schema is DummyInputSchema diff --git a/uv.lock b/uv.lock index e9abbf2..15e5839 100644 --- a/uv.lock +++ b/uv.lock @@ -14,7 +14,22 @@ resolution-markers = [ ] [manifest] -overrides = [{ name = "wrapt", specifier = ">=1.14" }] +overrides = [ + { name = "mistralai", specifier = "<2" }, + { name = "wrapt", specifier = ">=1.14" }, +] + +[[package]] +name = "ag-ui-protocol" +version = "0.1.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/d7/5711eada86da9bd7684e58645653a1693ef20b66cc3efbb1deeafef80f8d/ag_ui_protocol-0.1.18.tar.gz", hash = "sha256:b37c672c3fd6bac12b316c39f45ad9db9f137bbb885489c79f268507029a22ff", size = 9937, upload-time = "2026-04-21T20:44:59.151Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/74/913c9b8fc566c6da650aecbddf25a5d8186b54138df265eb9eb546f56141/ag_ui_protocol-0.1.18-py3-none-any.whl", hash = "sha256:d151c0f0a34160647f1571163f7185746f4326b15a56d1560de5082a7a0e7a12", size = 12607, upload-time = "2026-04-21T20:45:00.097Z" }, +] [[package]] name = "aiofile" @@ -185,7 +200,9 @@ source = { editable = "." } dependencies = [ { name = "akd" }, { name = "fastmcp" }, + { name = "griffe" }, { name = "openai-agents" }, + { name = "pydantic-ai" }, { name = "pygithub" }, ] @@ -200,6 +217,7 @@ dev = [ [package.dev-dependencies] dev = [ + { name = "marimo" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, @@ -209,9 +227,11 @@ dev = [ [package.metadata] requires-dist = [ { name = "akd", git = "https://github.com/NASA-IMPACT/accelerated-discovery.git?rev=develop" }, - { name = "fastmcp", specifier = ">=2.0.0" }, + { name = "fastmcp", specifier = ">=2.0.0,<3.2.4" }, + { name = "griffe", specifier = ">=1.0.0,<2" }, { name = "openai-agents", specifier = ">=0.6.7" }, { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.2.0" }, + { name = "pydantic-ai", specifier = ">=1.81.0" }, { name = "pygithub", specifier = ">=2.1.1" }, { name = "pygithub", marker = "extra == 'dev'", specifier = ">=2.1.1" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" }, @@ -222,6 +242,7 @@ provides-extras = ["dev"] [package.metadata.requires-dev] dev = [ + { name = "marimo", specifier = ">=0.23.1" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, { name = "pytest-cov", specifier = ">=7.0.0" }, @@ -265,6 +286,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "anthropic" +version = "0.97.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "docstring-parser" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/93/f66ea8bfe39f2e6bb9da8e27fa5457ad2520e8f7612dfc547b17fad55c4d/anthropic-0.97.0.tar.gz", hash = "sha256:021e79fd8e21e90ad94dc5ba2bbbd8b1599f424f5b1fab6c06204009cab764be", size = 669502, upload-time = "2026-04-23T20:52:34.445Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/b6/8e851369fa661ad0fef2ae6266bf3b7d52b78ccf011720058f4adaca59e2/anthropic-0.97.0-py3-none-any.whl", hash = "sha256:8a1a472dfabcfc0c52ff6a3eecf724ac7e07107a2f6e2367be55ceb42f5d5613", size = 662126, upload-time = "2026-04-23T20:52:32.377Z" }, +] + [[package]] name = "anyio" version = "4.12.1" @@ -278,6 +318,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, ] +[[package]] +name = "argcomplete" +version = "3.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, +] + [[package]] name = "astroid" version = "2.5" @@ -343,6 +392,34 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/44/1c/577d3ce406e88f370e80a6ebf76ae52a2866521e0b585e8ec612759894f1/bibtexparser-1.4.4.tar.gz", hash = "sha256:093b6c824f7a71d3a748867c4057b71f77c55b8dbc07efc993b781771520d8fb", size = 55594, upload-time = "2026-01-29T18:58:01.366Z" } +[[package]] +name = "boto3" +version = "1.42.96" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/2d/69fb3acd50bab83fb295c167d33c4b653faeb5fb0f42bfca4d9b69d6fb68/boto3-1.42.96.tar.gz", hash = "sha256:b38a9e4a3fbbee9017252576f1379780d0a5814768676c08df2f539d31fcdd68", size = 113203, upload-time = "2026-04-24T19:47:18.677Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/9d/b3f617d011c42eb804d993103b8fa9acdce153e181a3042f58bfe33d7cb4/boto3-1.42.96-py3-none-any.whl", hash = "sha256:2f4566da2c209a98bdbfc874d813ef231c84ad24e4f815e9bc91de5f63351a24", size = 140557, upload-time = "2026-04-24T19:47:15.824Z" }, +] + +[[package]] +name = "botocore" +version = "1.42.96" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/77/2c333622a1d47cf5bf73cdcab0cb6c92addafbef2ec05f81b9f75687d9e5/botocore-1.42.96.tar.gz", hash = "sha256:75b3b841ffacaa944f645196655a21ca777591dd8911e732bfb6614545af0250", size = 15263344, upload-time = "2026-04-24T19:47:05.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/56/152c3a859ca1b9d77ed16deac3cf81682013677c68cf5715698781fc81bd/botocore-1.42.96-py3-none-any.whl", hash = "sha256:db2c3e2006628be6fde81a24124a6563c363d6982fb92728837cf174bad9d98a", size = 14945920, upload-time = "2026-04-24T19:47:00.323Z" }, +] + [[package]] name = "brotli" version = "1.2.0" @@ -576,6 +653,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/5a/4f025bc751087833686892e17e7564828e409c43b632878afeae554870cd/click_log-0.4.0-py2.py3-none-any.whl", hash = "sha256:a43e394b528d52112af599f2fc9e4b7cf3c15f94e53581f74fa6867e68c91756", size = 4273, upload-time = "2022-03-13T11:10:17.594Z" }, ] +[[package]] +name = "cohere" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastavro", marker = "sys_platform != 'emscripten'" }, + { name = "httpx", marker = "sys_platform != 'emscripten'" }, + { name = "pydantic", marker = "sys_platform != 'emscripten'" }, + { name = "pydantic-core", marker = "sys_platform != 'emscripten'" }, + { name = "requests", marker = "sys_platform != 'emscripten'" }, + { name = "tokenizers", marker = "sys_platform != 'emscripten'" }, + { name = "types-requests", marker = "sys_platform != 'emscripten'" }, + { name = "typing-extensions", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/7aff8a870889ee931aa19e1deb138691e3cc909ee61e1daea86f3475a818/cohere-6.1.0.tar.gz", hash = "sha256:6a52bb459b71b5e79735412ee1a8e87028c5b3afba050c39815fe03c083249b3", size = 207302, upload-time = "2026-04-10T19:44:43.103Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/b4/00c2f9f8387a2e77faf8410210466c46d55dd30a0388de41c54441b148fb/cohere-6.1.0-py3-none-any.whl", hash = "sha256:ad286b3af2583c75ba93624e6f680603d3578a3d73704f997430260b87537ac7", size = 350543, upload-time = "2026-04-10T19:44:41.805Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -885,6 +981,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, ] +[[package]] +name = "eval-type-backport" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/a3/cafafb4558fd638aadfe4121dc6cefb8d743368c085acb2f521df0f3d9d7/eval_type_backport-0.3.1.tar.gz", hash = "sha256:57e993f7b5b69d271e37482e62f74e76a0276c82490cf8e4f0dffeb6b332d5ed", size = 9445, upload-time = "2025-12-02T11:51:42.987Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/22/fdc2e30d43ff853720042fa15baa3e6122722be1a7950a98233ebb55cd71/eval_type_backport-0.3.1-py3-none-any.whl", hash = "sha256:279ab641905e9f11129f56a8a78f493518515b83402b860f6f06dd7c011fdfa8", size = 6063, upload-time = "2025-12-02T11:51:41.665Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.1" @@ -906,6 +1011,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, ] +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + [[package]] name = "fake-http-header" version = "0.3.5" @@ -923,6 +1037,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/37/b3ea9cd5558ff4cb51957caca2193981c6b0ff30bd0d2630ac62505d99d0/fake_useragent-2.2.0-py3-none-any.whl", hash = "sha256:67f35ca4d847b0d298187443aaf020413746e56acd985a611908c73dba2daa24", size = 161695, upload-time = "2025-04-14T15:32:17.732Z" }, ] +[[package]] +name = "fastavro" +version = "1.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/5b/ccb338db71f347e3bc031d268bf6dc41e5ead63b6997b8e72af92f05e18e/fastavro-1.12.2.tar.gz", hash = "sha256:3c79502d56cf6b76210032e1c53494ddfbc73c140bccf2ef4092b3f0825323ab", size = 1030127, upload-time = "2026-04-24T14:36:01.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/bc/fe5731d6724d978694fbd3196bc1c0d7cab3fd0766e9551c40c39f798b52/fastavro-1.12.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e331896e8efffc72fa03e63b87ebfc37960113127da8e0f5152d91664ffed68", size = 964331, upload-time = "2026-04-24T14:36:31.297Z" }, + { url = "https://files.pythonhosted.org/packages/98/36/50abf1145e4f1c4f418cd4b5f2ac806643d0b14e360b60e953826edf1b34/fastavro-1.12.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f01ebaada59d74fdf6d28e5031a961a413b3752e9edb0c03866fa18480cf4c8", size = 3340170, upload-time = "2026-04-24T14:36:33.364Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8c/76ef4641e6c1c1aa3e6bb3c9efb5533ffda5dd975c8b5ae54e794322d9e3/fastavro-1.12.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25ef6855935f67582740ffa6bb978e40ec51be876117a3555c36fa2488dcdf25", size = 3425061, upload-time = "2026-04-24T14:36:35.497Z" }, + { url = "https://files.pythonhosted.org/packages/31/10/379ff23425b2b470d5209cbc6736a6e5cbc34392ff17bb7355b8fd4aa0ca/fastavro-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84a4f76a0aece0aa72b5ed8162ba2ff8c78908b8361b5a5d92ddd161977ccb74", size = 3243618, upload-time = "2026-04-24T14:36:37.969Z" }, + { url = "https://files.pythonhosted.org/packages/88/29/4c8f9e7cd78f932f0d82823899e67a6d7f7e8f2524992db03956f9d9f5ef/fastavro-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81e8da77d201916f6771fc357fda8267c2a256d7aa11923d43bc5f2fc155878b", size = 3378427, upload-time = "2026-04-24T14:36:40.278Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/eafeb302aaaea6055d4a9c11272b4aeaf713e43fe8eaf782f43a1fee2b44/fastavro-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:1924349c74666c89417bd5cc2749f598e2f15f1d56ee81428b2317ab02c88aae", size = 441077, upload-time = "2026-04-24T14:36:41.791Z" }, + { url = "https://files.pythonhosted.org/packages/56/9d/67e831041ba8efc16265c65bd71ba92e1095bba19b91be99e102f19d9be6/fastavro-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:4c346cf449baf3b113e997c34151ad205e7135bc429469b005b180ade7e65e28", size = 378205, upload-time = "2026-04-24T14:36:43.679Z" }, + { url = "https://files.pythonhosted.org/packages/83/39/f489a441d41cc9c0a8449fb1325d7a9c9eb57a5634e6ab19dfb0a1105324/fastavro-1.12.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:57bb6b908cb2e05baab63b04c3a31be3b4545a10bfab9748b8763016b5256704", size = 958566, upload-time = "2026-04-24T14:36:45.49Z" }, + { url = "https://files.pythonhosted.org/packages/31/69/776cc025aee2d02acacb734cf690d2fbc295eaadde1b5d47caf8c77a6a2b/fastavro-1.12.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a007f95cc682f56e6d83f1d17c29c00bf719d6fe8e003282b535af3a1ba09c0", size = 3276390, upload-time = "2026-04-24T14:36:47.875Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bc/b7e15fa788f42cbe65827af2ec06c9ad91bb9f72c213110dbef61b53a5b0/fastavro-1.12.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e90460b0cd21f62be3cb26087e706e2cebb7b3fcef9e05b4473b61bb0415b5e", size = 3372779, upload-time = "2026-04-24T14:36:50.122Z" }, + { url = "https://files.pythonhosted.org/packages/79/c2/98993ca810231fc1397212f48c3d46626983722a24bbaaa5c27ee0963751/fastavro-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ccd15966b8218d41b06ec3e7c2556be89a8a693026c771e6564d2e40bbaf8ea", size = 3187591, upload-time = "2026-04-24T14:36:52.451Z" }, + { url = "https://files.pythonhosted.org/packages/c6/bb/c180f340eba6478f1b20deccdd17e2b4a4d5074dafd812e3c4254fd035f7/fastavro-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06b6971d3dae10cb34353b857d16ad21ebd6f0ea394e86c96abdcad109005d6e", size = 3320589, upload-time = "2026-04-24T14:36:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e9/aca0456216b5b8992e7b0a8542711b66799c05bfe24c8e32ef6f56e7eb93/fastavro-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:98dfcdfaf1498ae2f0e2fafe900a82e8320cc81d8ae5a95b8b8879eaa3298c39", size = 440883, upload-time = "2026-04-24T14:36:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7e/984896e716af504927be71b80a1e9661aa96c6f9e1e777d52823aacb99f2/fastavro-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:3888ef7a51adc77cdf07251bc762566a1be36211e1cff689f13980f3776a2f36", size = 377536, upload-time = "2026-04-24T14:36:58.274Z" }, + { url = "https://files.pythonhosted.org/packages/e9/42/09a1e1f8d9998d73848a6ff0aad6713ae6abf0dbf99918776f8ef33344a7/fastavro-1.12.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:283dcd3129b632021894425974bedd0eb6db3bbf5994e448ccad10db4d803d31", size = 1049506, upload-time = "2026-04-24T14:36:59.797Z" }, + { url = "https://files.pythonhosted.org/packages/52/ef/80cc16f43919d532f25a707f34b275cccc09dca87a05b000fbbfc8e8f255/fastavro-1.12.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d125e210d5a0a1f701f12c0ecad9a03f1b04b5eddbce6ca36a1fc217da977ef", size = 3495899, upload-time = "2026-04-24T14:37:02.306Z" }, + { url = "https://files.pythonhosted.org/packages/c1/54/a0817d1d0236e9e0233f5c996f450cc795b056b8e06edb531f24b9df82ed/fastavro-1.12.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d4d66afad78e8f47feaa307728a6b71fe3effc63ba2b9eeb109ee687c9bd397", size = 3399232, upload-time = "2026-04-24T14:37:04.837Z" }, + { url = "https://files.pythonhosted.org/packages/38/0a/650f256c15f5875b6081544b9ba7ed8254329213e7e49e3db0aec68b5bee/fastavro-1.12.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2328ec07925c04c89719e3971c9068a165c7fd474ea87675b1204de0440e71ff", size = 3320222, upload-time = "2026-04-24T14:37:07.281Z" }, + { url = "https://files.pythonhosted.org/packages/f5/54/8351d388f94fbb0870e8cffaae41d3cc607acc8d6a8a6a217e2794829593/fastavro-1.12.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:55dea7e74b834d4b70467fc19c5b9ccb5509fe39abc4d26891187c1b22176423", size = 3337096, upload-time = "2026-04-24T14:37:09.452Z" }, + { url = "https://files.pythonhosted.org/packages/da/eb/b36ba9a88826e8c272df02e2f8b5da717e88b6eb508fddca3ca450043731/fastavro-1.12.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8d37c87826ae7195cfbd20fcd448801f2f563bb38f2691ec6574e39cb9eca6c8", size = 963119, upload-time = "2026-04-24T14:37:11.557Z" }, + { url = "https://files.pythonhosted.org/packages/e1/02/3d7f540fb26ba4ea1f4ebd2783c586614da9ac00906a3092e92fd3f104a2/fastavro-1.12.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c463a3701f293e30d3d62e71e1989f112028d07f87432baf4507eeb57ec3831", size = 3266238, upload-time = "2026-04-24T14:37:13.84Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0b/b77be56c5109da0fc7dcfd7e6b6752fe0a61d0a5c58c6a65e38b4501946a/fastavro-1.12.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f604ba83498e209fff4c7ecc5063a39421dc538dace694bc592f9f338254f3dc", size = 3324020, upload-time = "2026-04-24T14:37:16.096Z" }, + { url = "https://files.pythonhosted.org/packages/e7/6e/951d41f244107e91bf2f59245b71783c03eaab4bdbc960d58316c19652bb/fastavro-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bfac2dada8ddc002e8b7d8289d6fad4f070bc1fec20371cec684a7d10d932e96", size = 3170160, upload-time = "2026-04-24T14:37:18.168Z" }, + { url = "https://files.pythonhosted.org/packages/94/6f/2adb571fda448d4afd2466e1cef2963fefdc6b37847da05249983e415f17/fastavro-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bc44ba6289fb1f5ee318335958dde6ad6d742dcb4bb8930de843e9024c64b68c", size = 3281842, upload-time = "2026-04-24T14:37:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/17/07/4bad2e96c4c6bae40253be2573cc09c1e5b9ccf821e1ff74e0d33b64bf90/fastavro-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:a475418f71c5aed69899813ecccf392429c08c3a63df3030129db71760b0db8f", size = 450903, upload-time = "2026-04-24T14:37:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b7/180f67ba9a46ba23a1ff6432f48d3087d4f2048579ecc262b00426cb1c63/fastavro-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:daec9f9655a1d4636613c47d6d3343f6e039150d66cdce62543e20ca36612a8a", size = 391076, upload-time = "2026-04-24T14:37:24.756Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ac/a1fa1fc29df0efc89d4946a743b09bdc9500591b5b92083eaf8e93664916/fastavro-1.12.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74412132bbfb153cbf704517f2c89f7d3e170feb681b13bceace690f66f8d5fa", size = 3503075, upload-time = "2026-04-24T14:37:26.826Z" }, + { url = "https://files.pythonhosted.org/packages/82/bf/4f669e10b6bc38a731ee3400aed1a1e2d0a3e3cf411e72f6b320d3af0eaf/fastavro-1.12.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e367a84c9133018e0a3bc822abe78d7f1f9a6092991a0ec409468cf4ef260282", size = 3410900, upload-time = "2026-04-24T14:37:29.233Z" }, + { url = "https://files.pythonhosted.org/packages/10/39/ecb19fdae4158a7730b5963fbf1b6d38d74678392d73083be518642af0c1/fastavro-1.12.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:044fafca0853e9ae14009de7763ac9e8e8f8b96f8a4e90bd58b695443266a370", size = 3335637, upload-time = "2026-04-24T14:37:31.472Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/f21bd5319113e89ceceed2df840df21e9c5150d181db74b6ba80400f9f48/fastavro-1.12.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afede7324822800e4f90e96b9514188a237a60f35e8e7a10b2129c10c78f6e4d", size = 3356664, upload-time = "2026-04-24T14:37:34.231Z" }, +] + [[package]] name = "fastmcp" version = "3.0.2" @@ -1126,6 +1278,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/21/35dd0a0b7428bd67b12b358d7b4277f693493a3839b071d540a4c8357b78/gdown-5.2.1-py3-none-any.whl", hash = "sha256:391f0480d495fb87644d1a1ee3ddfeb2144e1de31408fbc74f7e3b3ba927052b", size = 18241, upload-time = "2026-01-11T09:34:02.637Z" }, ] +[[package]] +name = "genai-prices" +version = "0.0.57" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/30/11f3d683cf3b1d9612475ad8bfffe3423ce9f50fc617733109033e73a038/genai_prices-0.0.57.tar.gz", hash = "sha256:6e101e9c53975557ceffa237b0995787d81fe75aac12410f2898504188bcad89", size = 66555, upload-time = "2026-04-21T13:42:52.554Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/fe/d0095040c120d97cb63d055224ecd4e913dc5655315c203c8e83bf13aa86/genai_prices-0.0.57-py3-none-any.whl", hash = "sha256:14e50fb69cdc5a06ddb2a6df5a7fe06741b9e44304ce3f1728f56abdf1856cca", size = 69654, upload-time = "2026-04-21T13:42:51.236Z" }, +] + +[[package]] +name = "google-auth" +version = "2.49.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyasn1-modules" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/fc/e925290a1ad95c975c459e2df070fac2b90954e13a0370ac505dff78cb99/google_auth-2.49.2.tar.gz", hash = "sha256:c1ae38500e73065dcae57355adb6278cf8b5c8e391994ae9cbadbcb9631ab409", size = 333958, upload-time = "2026-04-10T00:41:21.888Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/76/d241a5c927433420507215df6cac1b1fa4ac0ba7a794df42a84326c68da8/google_auth-2.49.2-py3-none-any.whl", hash = "sha256:c2720924dfc82dedb962c9f52cabb2ab16714fd0a6a707e40561d217574ed6d5", size = 240638, upload-time = "2026-04-10T00:41:14.501Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "google-genai" +version = "1.73.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "google-auth", extra = ["requests"] }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "sniffio" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/d8/40f5f107e5a2976bbac52d421f04d14fc221b55a8f05e66be44b2f739fe6/google_genai-1.73.1.tar.gz", hash = "sha256:b637e3a3b9e2eccc46f27136d470165803de84eca52abfed2e7352081a4d5a15", size = 530998, upload-time = "2026-04-14T21:06:19.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/af/508e0528015240d710c6763f7c89ff44fab9a94a80b4377e265d692cbfd6/google_genai-1.73.1-py3-none-any.whl", hash = "sha256:af2d2287d25e42a187de19811ef33beb2e347c7e2bdb4dc8c467d78254e43a2c", size = 783595, upload-time = "2026-04-14T21:06:17.464Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.74.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/18/a746c8344152d368a5aac738d4c857012f2c5d1fd2eac7e17b647a7861bd/googleapis_common_protos-1.74.0.tar.gz", hash = "sha256:57971e4eeeba6aad1163c1f0fc88543f965bb49129b8bb55b2b7b26ecab084f1", size = 151254, upload-time = "2026-04-02T21:23:26.679Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/b0/be5d3329badb9230b765de6eea66b73abd5944bdeb5afb3562ddcd80ae84/googleapis_common_protos-1.74.0-py3-none-any.whl", hash = "sha256:702216f78610bb510e3f12ac3cafd281b7ac45cc5d86e90ad87e4d301a3426b5", size = 300743, upload-time = "2026-04-02T21:22:49.108Z" }, +] + [[package]] name = "greenlet" version = "3.3.2" @@ -1181,6 +1397,73 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, ] +[[package]] +name = "griffelib" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, +] + +[[package]] +name = "groq" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/51/4728c13611849ff6cf8536740ae78ba3ee5e665d67b572a47c9ead0f9788/groq-1.2.0.tar.gz", hash = "sha256:85459e27c9c17f22404349c785cd08680362cfe85e07cc060be46c4832f108c3", size = 155609, upload-time = "2026-04-18T10:43:50.68Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/82/748639c95c60ad8846c65b167ca611c815d06d5f67a9e73b23486dce4fdf/groq-1.2.0-py3-none-any.whl", hash = "sha256:1002060a743b27c8f86765e1bc9749c98498e961d9fe2e4902bf7804a71c3c84", size = 142334, upload-time = "2026-04-18T10:43:49.125Z" }, +] + +[[package]] +name = "grpcio" +version = "1.80.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" }, + { url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060, upload-time = "2026-03-30T08:47:21.113Z" }, + { url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" }, + { url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860, upload-time = "2026-03-30T08:47:29.439Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" }, + { url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904, upload-time = "2026-03-30T08:47:35.319Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944, upload-time = "2026-03-30T08:47:37.831Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" }, + { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" }, + { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" }, + { url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830, upload-time = "2026-03-30T08:47:49.643Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" }, + { url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866, upload-time = "2026-03-30T08:47:55.687Z" }, + { url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602, upload-time = "2026-03-30T08:47:58.303Z" }, + { url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752, upload-time = "2026-03-30T08:48:01.311Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310, upload-time = "2026-03-30T08:48:04.594Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833, upload-time = "2026-03-30T08:48:07.363Z" }, + { url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" }, + { url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" }, + { url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" }, + { url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" }, + { url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" }, + { url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" }, + { url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" }, + { url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" }, + { url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -1394,6 +1677,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/95/64/6542ac826a4c9b937b67c096a785af1aaa26b22fcb7c81223cfe4038205b/instructor-1.13.0-py3-none-any.whl", hash = "sha256:2b735b6ea0d3194548369a18254f1dde83cb5ec0b182de77adbadd8be73caddc", size = 160904, upload-time = "2025-11-06T04:19:24.674Z" }, ] +[[package]] +name = "invoke" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/bd/b461d3424a24c80490313fd77feeb666ca4f6a28c7e72713e3d9095719b4/invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707", size = 304762, upload-time = "2025-10-11T00:36:35.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/4b/b99e37f88336009971405cbb7630610322ed6fbfa31e1d7ab3fbf3049a2d/invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8", size = 160287, upload-time = "2025-10-11T00:36:33.703Z" }, +] + [[package]] name = "isort" version = "5.13.2" @@ -1403,6 +1695,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310, upload-time = "2023-12-13T20:37:23.244Z" }, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + [[package]] name = "jaraco-classes" version = "3.4.0" @@ -1436,6 +1737,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, ] +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + [[package]] name = "jeepney" version = "0.9.0" @@ -1525,6 +1838,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/71/71408b02c6133153336d29fa3ba53000f1e1a3f78bb2fc2d1a1865d2e743/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18c77aaa9117510d5bdc6a946baf21b1f0cfa58ef04d31c8d016f206f2118960", size = 343697, upload-time = "2025-10-17T11:31:13.773Z" }, ] +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + [[package]] name = "joblib" version = "1.5.3" @@ -1674,6 +1996,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/89/eb28bfcf97d6b045c400e72eb047c381594467048c237dbb6c227764084c/litellm-1.82.0-py3-none-any.whl", hash = "sha256:5496b5d4532cccdc7a095c21cbac4042f7662021c57bc1d17be4e39838929e80", size = 14911978, upload-time = "2026-03-01T02:35:26.844Z" }, ] +[[package]] +name = "logfire" +version = "4.32.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "executing" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-sdk" }, + { name = "protobuf" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/d7/70c6def7f3f459b2d57aa7fb37863d31b8d877e391547f200ee8c31d2e30/logfire-4.32.1.tar.gz", hash = "sha256:8e7ff418b5f2629c8a8e9426283ff82c760a30f24516c4c389d6cbb1d9768c58", size = 1089612, upload-time = "2026-04-15T14:11:57.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/77/70f6d97d7d74d2f2eeb695fe491b28906ae5c350b48516bb237ace9a1778/logfire-4.32.1-py3-none-any.whl", hash = "sha256:cb7873efec0e94a3de6e603539daaa6509a454599621c80dd227fbfa0ade37d4", size = 313021, upload-time = "2026-04-15T14:11:54.024Z" }, +] + +[package.optional-dependencies] +httpx = [ + { name = "opentelemetry-instrumentation-httpx" }, +] + +[[package]] +name = "logfire-api" +version = "4.32.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/1b/0c74ad85f977743ba4c589e46e0cb138d6a6e69487830f4e86ebbdb145a3/logfire_api-4.32.1.tar.gz", hash = "sha256:5e8714b2bb5fb5d1f4a4a833941e4ca711b75d2c1f98e76c5ad680fe6991af6a", size = 78788, upload-time = "2026-04-15T14:11:58.788Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ab/d5adeab6253c7ecd5904fc5ef3265859f218610caf4e1e55efe9aff6ac49/logfire_api-4.32.1-py3-none-any.whl", hash = "sha256:4b4c27cf6e27e8e26ef4b22a77f2a2988dd1d07e2d24ee70673ef34b234fb8a5", size = 124394, upload-time = "2026-04-15T14:11:56.157Z" }, +] + [[package]] name = "loguru" version = "0.7.3" @@ -1687,6 +2041,72 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, ] +[[package]] +name = "loro" +version = "1.10.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/27/ea6f3298fc87ea5f2d60ebfbca088e7d9b2ceb3993f67c83bfb81778ec01/loro-1.10.3.tar.gz", hash = "sha256:68184ab1c2ab94af6ad4aaba416d22f579cabee0b26cbb09a1f67858207bbce8", size = 68833, upload-time = "2025-12-09T10:14:06.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/b6/cfbf8088e8ca07d66e6c1eccde42e00bd61708f28e8ea0936f9582306323/loro-1.10.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:028948b48dcc5c2127f974dae4ad466ab69f0d1eeaf367a8145eb6501fb988f2", size = 3239592, upload-time = "2025-12-09T10:11:32.505Z" }, + { url = "https://files.pythonhosted.org/packages/78/e4/7b614260bf16c5e33c0bea6ac47ab0284efd21f89f2e5e4e15cd93bead40/loro-1.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5253b8f436d90412b373c583f22ac9539cfb495bf88f78d4bb41daafef0830b7", size = 3045107, upload-time = "2025-12-09T10:11:17.481Z" }, + { url = "https://files.pythonhosted.org/packages/ae/17/0a78ec341ca69d376629ff2a1b9b3511ee7dd54f2b018616ef03328024f7/loro-1.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14be8a5539d49468c94d65742355dbe79745123d78bf769a23e53bf9b60dd46a", size = 3292720, upload-time = "2025-12-09T10:08:14.027Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9b/f36a4654508e9b8ddbe08a62a0ce8b8e7fd511a39b161821917530cffd8e/loro-1.10.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91b2b9139dfc5314a0197132a53b6673fddb63738380a522d12a05cec7ad76b4", size = 3353260, upload-time = "2025-12-09T10:08:48.251Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0e/7d441ddecc7695153dbe68af4067d62e8d7607fce3747a184878456a91f6/loro-1.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:247897288911c712ee7746965573299fc23ce091e94456da8da371e6adae30f4", size = 3712354, upload-time = "2025-12-09T10:09:26.38Z" }, + { url = "https://files.pythonhosted.org/packages/1c/33/10e66bb84599e61df124f76c00c5398eb59cbb6f69755f81c40f65a18344/loro-1.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:835abc6025eb5b6a0fe22c808472affc95e9a661b212400cfd88ba186b0d304c", size = 3422926, upload-time = "2025-12-09T10:10:00.347Z" }, + { url = "https://files.pythonhosted.org/packages/b2/70/00dc4246d9f3c69ecbb9bc36d5ad1a359884464a44711c665cb0afb1e9de/loro-1.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e660853617fc29e71bb7b796e6f2c21f7722c215f593a89e95cd4d8d5a32aca0", size = 3353092, upload-time = "2025-12-09T10:10:55.786Z" }, + { url = "https://files.pythonhosted.org/packages/19/37/60cc0353c5702e1e469b5d49d1762e782af5d5bd5e7c4e8c47556335b4c6/loro-1.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8059063cab57ca521012ed315a454784c20b0a86653e9014795e804e0a333659", size = 3687798, upload-time = "2025-12-09T10:10:33.253Z" }, + { url = "https://files.pythonhosted.org/packages/88/c4/4db1887eb08dfbb305d9424fdf1004c0edf147fd53ab0aaf64a90450567a/loro-1.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9748359343b5fd7019ab3c2d1d583a0c13c633a4dd21d75e50e3815ab479f493", size = 3474451, upload-time = "2025-12-09T10:11:49.489Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/10d2e00c43b05f56e96e62100f86a1261f8bbd6422605907f118a752fe61/loro-1.10.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:def7c9c2e16ad5470c9c56f096ac649dd4cd42d5936a32bb0817509a92d82467", size = 3621647, upload-time = "2025-12-09T10:12:25.536Z" }, + { url = "https://files.pythonhosted.org/packages/47/f0/ef8cd6654b09a03684195c650b1fba00f42791fa4844ea400d94030c5615/loro-1.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:34b223fab58591a823f439d9a13d1a1ddac18dc4316866503c588ae8a9147cb1", size = 3667946, upload-time = "2025-12-09T10:13:00.711Z" }, + { url = "https://files.pythonhosted.org/packages/bb/5d/960b62bf85c38d6098ea067438f037a761958f3a17ba674db0cf316b0f60/loro-1.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d5fa4baceb248d771897b76d1426c7656176e82e770f6790940bc3e3812436d", size = 3565866, upload-time = "2025-12-09T10:13:35.401Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d4/0d499a5e00df13ce497263aef2494d9de9e9d1f11d8ab68f89328203befb/loro-1.10.3-cp312-cp312-win32.whl", hash = "sha256:f25ab769b84a5fbeb1f9a1111f5d28927eaeaa8f5d2d871e237f80eaca5c684e", size = 2720785, upload-time = "2025-12-09T10:14:28.79Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9b/2b5be23f1da4cf20c6ce213cfffc66bdab2ea012595abc9e3383103793d0/loro-1.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:3b73b7a3a32e60c3424fc7deaf8b127af7580948e27d8bbe749e3f43508aa0a2", size = 2954650, upload-time = "2025-12-09T10:14:10.235Z" }, + { url = "https://files.pythonhosted.org/packages/75/67/8467cc1c119149ada86903b67ce10fc4b47fb6eb2a8ca5f94c0938fd010f/loro-1.10.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:380ef692c5272e8b607be2ee6a8eef5113e65dc38e6739526c30e3db6abc3fbc", size = 3239527, upload-time = "2025-12-09T10:11:33.884Z" }, + { url = "https://files.pythonhosted.org/packages/bc/3b/d1a01af3446cb98890349215bea7e71ba49dc3e50ffbfb90c5649657a8b8/loro-1.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed966ce6ff1fb3787b3f6c4ed6dd036baa5fb738b84a466a5e764f2ab534ccc2", size = 3044767, upload-time = "2025-12-09T10:11:18.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/93/37f891fa46767001ae2518697fb01fc187497e3a5238fe28102be626055d/loro-1.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d7c8d2f3d88578fdf69845a9ae16fc5ea3ac54aa838a6bf43a24ce11908220", size = 3292648, upload-time = "2025-12-09T10:08:15.404Z" }, + { url = "https://files.pythonhosted.org/packages/6c/67/82273eeba2416b0410595071eda1eefcdf4072c014d44d2501b660aa7145/loro-1.10.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62283c345bfeedef19c8a6d029cd8830e5d2c20b5fb45975d8a70a8a30a7944b", size = 3353181, upload-time = "2025-12-09T10:08:50.144Z" }, + { url = "https://files.pythonhosted.org/packages/82/33/894dccf132bece82168dfbe61fad25a13ed89d18f20649f99e87c38f9228/loro-1.10.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e7e6ae091179fa5f0fca1f8612fde20236ee0a678744bf51ff7d26103ea04f", size = 3712583, upload-time = "2025-12-09T10:09:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/99292729d8b271bcc4bff5faa20b33e4c749173af4c9cb9d34880ae3b4c8/loro-1.10.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6abc6de4876aa205498cef52a002bc38662fbd8d742351ea0f535479208b8b1c", size = 3421491, upload-time = "2025-12-09T10:10:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/fb/188b808ef1d9b6d842d53969b99a16afb1b71f04739150959c8946345d0e/loro-1.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acbbfd24cf28a71bbdad8544852e9bbba0ba8535f8221f8859b2693555fa8356", size = 3352623, upload-time = "2025-12-09T10:10:57.361Z" }, + { url = "https://files.pythonhosted.org/packages/53/cc/e2d008cc24bddcf05d1a15b8907a73b1731921ab40897f73a3385fdd274a/loro-1.10.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5faf4ebbe8ca39605024f16dbbbde354365f4e2dcfda82c753797461b504bbd3", size = 3687687, upload-time = "2025-12-09T10:10:34.453Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b6/4251822674230027103caa4fd46a1e83c4d676500074e7ab297468bf8f40/loro-1.10.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e049c21b292c4ff992b23a98812840735db84620721c10ae7f047a921202d090", size = 3474316, upload-time = "2025-12-09T10:11:51.207Z" }, + { url = "https://files.pythonhosted.org/packages/c4/54/ecff3ec08d814f3b9ec1c78a14ecf2e7ff132a71b8520f6aa6ad1ace0056/loro-1.10.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:20e8dacfb827c1f7ffb73e127029d7995a9ab2c3b7b7bc3ecc91d22ee32d78d0", size = 3622069, upload-time = "2025-12-09T10:12:27.059Z" }, + { url = "https://files.pythonhosted.org/packages/ac/84/c1b8251000f46df5f4d043af8c711bdbff9818727d26429378e0f3a5115e/loro-1.10.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1b743c1c4f93f5b4f0e12efbb352d26e9f80bcbf20f45d9c70f3d0b522f42060", size = 3667722, upload-time = "2025-12-09T10:13:02.012Z" }, + { url = "https://files.pythonhosted.org/packages/ef/13/c5c02776f4ad52c6361b95e1d7396c29071533cef45e3861a2e35745be27/loro-1.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:446d67bc9e28036a5a5e03526d28a1559ef2a47b3ccad6b07820dae123cc3697", size = 3564952, upload-time = "2025-12-09T10:13:37.227Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f1/63d4bc63a1521a9b577f6d13538ec4790865584fdf87569d5af943792406/loro-1.10.3-cp313-cp313-win32.whl", hash = "sha256:45d7d8ec683599897695bb714771baccabc1b4c4a412283cc39787c7a59f7ff0", size = 2720952, upload-time = "2025-12-09T10:14:30.17Z" }, + { url = "https://files.pythonhosted.org/packages/29/3c/65c8b0b7f96c9b4fbd458867cf91f30fcd58ac25449d8ba9303586061671/loro-1.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:a42bf73b99b07fed11b65feb0a5362b33b19de098f2235848687f4c41204830e", size = 2953768, upload-time = "2025-12-09T10:14:11.965Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e9/f6a242f61aa4d8b56bd11fa467be27d416401d89cc3244b58651a3a44c88/loro-1.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4866325b154aeebcd34be106c7597acf150c374481ac3c12035a1af715ac0f01", size = 3289791, upload-time = "2025-12-09T10:08:16.926Z" }, + { url = "https://files.pythonhosted.org/packages/a7/81/8f5f4d6805658c654264e99467f3f46facdbb2062cbf86743768ee4b942a/loro-1.10.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ea7b8849660a28ce8cd90a82db4f76c23453836fcbc88f5767feaaf8739045e2", size = 3348007, upload-time = "2025-12-09T10:08:53.305Z" }, + { url = "https://files.pythonhosted.org/packages/c3/15/bba0fad18ec5561a140e9781fd2b38672210b52e847d207c57ae85379efd/loro-1.10.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e82cdaf9a5892557d3167e07ed5093f87dfa31ef860a63b0eac6c0c2f435705", size = 3707937, upload-time = "2025-12-09T10:09:29.165Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b2/5519c92bd4f9cde068dc60ba35d7f3e4f8cce41e7bf39febd4fb08908e97/loro-1.10.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7ee99e5dc844fb20fca830906a0d721022ad1c37aad0b1a440c4ecb98d0c02f", size = 3416744, upload-time = "2025-12-09T10:10:02.956Z" }, + { url = "https://files.pythonhosted.org/packages/81/ba/92d97c27582c0ce12bb83df19b9e080c0dfe95068966296a4fa2279c0477/loro-1.10.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:153c297672ad98d0fe6ff8985decf1e64528ad1dd01ae1452bb83bdeb31f858f", size = 3470978, upload-time = "2025-12-09T10:11:52.707Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8b/acb39b0e74af1c317d3121e75a4bc5bc77d7fda5a79c60399746486f60d9/loro-1.10.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:0ed72f8c6a5f521252ee726954055339abba3fcf00404fb4b5c2da168f0cce79", size = 3615039, upload-time = "2025-12-09T10:12:28.631Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/154e3361e5ef42012f6842dbd93f8fbace6eec06517b5a4a9f8c4a46e873/loro-1.10.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f612ab17acdac16c0139e63ff45b33175ebfb22e61a60eb7929a4583389348d6", size = 3663731, upload-time = "2025-12-09T10:13:03.557Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dd/a283cf5b1c957e0bbc67503a10e17606a8f8c87f51d3cf3d83dc3a0ac88a/loro-1.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f2741db05c79f3618c954bac90f4572d28c01c243884453f379e9a8738f93d81", size = 3558807, upload-time = "2025-12-09T10:13:38.926Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4a/a5340b6fdf4cd34d758bed23bd1f64063b3b1b41ff4ecc94ee39259ee9a7/loro-1.10.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:623cf7df17626aa55bc6ca54e89177dbe71a5f1c293e102d6153f43991a1a041", size = 3213589, upload-time = "2025-12-09T10:11:35.377Z" }, + { url = "https://files.pythonhosted.org/packages/00/93/5164e93a77e365a92def77c1258386daef233516a29fb674a3b9d973b8b8/loro-1.10.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d8e715d475f32a1462969aca27eeb3f998f309182978f55bc37ce5c515d92e90", size = 3029557, upload-time = "2025-12-09T10:11:20.076Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/94592d7c01f480ce99e1783b0d9203eb20ba2eab42575dabd384e3c9d1fa/loro-1.10.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e012a80e8c9fe248b9d0a76e91664c9479a72d976eaeed78f87b15b5d1d732", size = 3282335, upload-time = "2025-12-09T10:08:18.168Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a8/7ae3c0b955aa638fa7dbd2d194c7759749a0d0d96a94805d5dec9b30eaea/loro-1.10.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:686ece56756acbaf80c986848915e9126a29a06d7a62209747e3ef1efc0bd8f6", size = 3333071, upload-time = "2025-12-09T10:08:55.314Z" }, + { url = "https://files.pythonhosted.org/packages/f7/10/151edebdb2bca626ad50911b761164ced16984b25b0b37b34b674ded8b29/loro-1.10.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aa821c8871deca98f4605eb0c40fb26bcf82bd29c9e7fa33b183516c5395b11", size = 3698226, upload-time = "2025-12-09T10:09:30.474Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ac/02a490e38466506b1003df4910d2a8ae582265023dae9e2217c98b56ea3f/loro-1.10.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:507d34137adb4148f79e1da7f89a21a4aab18565621a5dc2b389773fe98ac25b", size = 3407322, upload-time = "2025-12-09T10:10:04.199Z" }, + { url = "https://files.pythonhosted.org/packages/81/db/da51f2bcad81ca3733bc21e83f3b6752446436b565b90f5c350ad227ad01/loro-1.10.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91d3b2e187ccfe2b14118a6e5617266fedcdf3435f6fa0a3db7b4afce8afa687", size = 3330268, upload-time = "2025-12-09T10:10:58.61Z" }, + { url = "https://files.pythonhosted.org/packages/4e/af/50d136c83d504a3a1f4ad33a6bf38b6933985a82741302255cf446a5f7ad/loro-1.10.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0016f834fd1626710081334400aed8494380b55ef131f7133d21c3bd22d892a", size = 3673582, upload-time = "2025-12-09T10:10:35.849Z" }, + { url = "https://files.pythonhosted.org/packages/63/4d/53288aae777218e05c43af9c080652bcdbbc8d97c031607eedd3fc15617d/loro-1.10.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:71c4275dca5a8a86219d60545d4f60e081b4af44b490ac912c0481906934bfc6", size = 3463731, upload-time = "2025-12-09T10:11:54.102Z" }, + { url = "https://files.pythonhosted.org/packages/75/01/2389f26ffe8bc3ffe48a0a578f610dd49c709bbcf0d5d2642c6e2b52f490/loro-1.10.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:490f12571b2ed1a8eaf1edd3a7fffc55adac5010b1875fe1bb9e9af9a3907c38", size = 3602334, upload-time = "2025-12-09T10:12:30.082Z" }, + { url = "https://files.pythonhosted.org/packages/a7/16/07b64af13f5fcea025e003ca27bbd6f748217abbd4803dad88ea0900526c/loro-1.10.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a374a43cadaa48528a5411496481df9ae52bf01e513f4509e37d6c986f199c0e", size = 3657896, upload-time = "2025-12-09T10:13:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/4050770d7675ceced71651fe76971d5c27456b7098c0de03a4ecdbb0a02d/loro-1.10.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1a93b2ee59f1fa8d98dd552211fd5693551893b34c1dd2ba0324806d6d14022f", size = 3544339, upload-time = "2025-12-09T10:13:40.396Z" }, + { url = "https://files.pythonhosted.org/packages/c9/21/67e27cb404c968fc19a841d5c6277f13a17c69a56f49e3c15ea1c92a28eb/loro-1.10.3-cp314-cp314-win32.whl", hash = "sha256:baa863e3d869422e3320e822c0b1f87f5dc44cda903d1bd3b7a16f8413ce3d92", size = 2706731, upload-time = "2025-12-09T10:14:31.604Z" }, + { url = "https://files.pythonhosted.org/packages/08/54/6770cf36aeb994489375e9ab9c01201e70ab7cc286fa97e907aa41b1bae6/loro-1.10.3-cp314-cp314-win_amd64.whl", hash = "sha256:f10ed3ca89485f942b8b2de796ed9783edb990e7e570605232de77489e9f3548", size = 2933563, upload-time = "2025-12-09T10:14:13.805Z" }, + { url = "https://files.pythonhosted.org/packages/24/f5/eb089fd25eb428709dbe79fd4d36b82a00572aa54badd1dff62511a38fe3/loro-1.10.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b4d049efb1953aebfc16fa0b445ff5a37d4d08a1ab93f3b5a577a454b7a5ded", size = 3282369, upload-time = "2025-12-09T10:08:20.011Z" }, + { url = "https://files.pythonhosted.org/packages/30/d7/692cb87c908f6a8af6cbfc10ebab69e16780e3796e11454c2b481b5c3817/loro-1.10.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56ecad7fbac58aa8bee52bb261a764aeef6c7b39c20f0d69e8fad908ab2ca7d8", size = 3332530, upload-time = "2025-12-09T10:08:57.07Z" }, + { url = "https://files.pythonhosted.org/packages/54/46/ed3afbf749288b6f70f3b859a6762538818bf6a557ca873b07d6b036946b/loro-1.10.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d8d1be349d08b3a95592c6a17b80b1ea6aef892b1b8e2b93b540062d04e34e0", size = 3702599, upload-time = "2025-12-09T10:09:31.779Z" }, + { url = "https://files.pythonhosted.org/packages/fe/30/6cb616939c12bfe96a71a01a6e3551febf1c34bf9de114fafadbcfb65064/loro-1.10.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ec0a0b9bc4e32c46f14710062ec5b536c72110318aaf85632a4f8b37e9a470a", size = 3404412, upload-time = "2025-12-09T10:10:05.448Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/3d4006d3333589f9158ac6d403979bf5c985be8b461b18e7a2ea23b05414/loro-1.10.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c5d4437987f7a4a4ff5927f39d0f43ded5b34295dfb0a3c8e150687e25c3d6b8", size = 3462948, upload-time = "2025-12-09T10:11:55.405Z" }, + { url = "https://files.pythonhosted.org/packages/41/30/c640ccd3e570b08770a9f459decc2d8e7ceefdc34ac28a745418fb9cb5ba/loro-1.10.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:86d4f0c631ca274ad2fa2c0bdb8e1e141882d94339b7284a8bef5bf73fa6957d", size = 3599851, upload-time = "2025-12-09T10:12:31.759Z" }, + { url = "https://files.pythonhosted.org/packages/59/8f/062ea50554c47ae30e98b1f0442a458c0edecc6d4edc7fcfc4d901734dd0/loro-1.10.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:15e03084ff1b472e14623183ed6e1e43e0f717c2112697beda5e69b5bd0ff236", size = 3655558, upload-time = "2025-12-09T10:13:06.529Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/c7dd8cdbd57454b23d89799c22cd42b6d2dda283cd87d7b198dc424a462c/loro-1.10.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42d6a5ce5bc518eaa682413e82d597299650eeb03e8bc39341752d6e0d22503e", size = 3541282, upload-time = "2025-12-09T10:13:42.189Z" }, +] + [[package]] name = "lxml" version = "5.4.0" @@ -1746,6 +2166,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/76/7ffc1d3005cf7749123bc47cb3ea343cd97b0ac2211bab40f57283577d0e/lxml_html_clean-0.4.4-py3-none-any.whl", hash = "sha256:ce2ef506614ecb85ee1c5fe0a2aa45b06a19514ec7949e9c8f34f06925cfabcb", size = 14565, upload-time = "2026-02-27T09:35:51.86Z" }, ] +[[package]] +name = "marimo" +version = "0.23.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "docutils" }, + { name = "itsdangerous" }, + { name = "jedi" }, + { name = "loro" }, + { name = "markdown" }, + { name = "msgspec" }, + { name = "narwhals" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "pyyaml" }, + { name = "pyzmq", marker = "python_full_version < '3.15'" }, + { name = "starlette" }, + { name = "tomlkit" }, + { name = "uvicorn" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/3f/7fb38c6c2a1f8d6b3c3ffb8ca6db5ff0b9dacbb113b4d05aa7690b51a771/marimo-0.23.3.tar.gz", hash = "sha256:251a8724b58882d65956ff6a20552cb21e59a6fd4149ca437727894375ec31e9", size = 38406206, upload-time = "2026-04-24T17:56:21.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/e7/02d672006fb04cb8aef23aeaf0384482fe63a13f9db6125ad8e13146daee/marimo-0.23.3-py3-none-any.whl", hash = "sha256:329b35b9ca221db9c78780d1714b11f010a00e2a929942db8ae6187960d42496", size = 38828150, upload-time = "2026-04-24T17:56:16.204Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + [[package]] name = "markdown-it-py" version = "4.0.0" @@ -1877,6 +2335,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "mistralai" +version = "1.12.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport" }, + { name = "httpx" }, + { name = "invoke" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-sdk" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/12/c3476c53e907255b5f485f085ba50dd9a84b40fe662e9a888d6ded26fa7b/mistralai-1.12.4.tar.gz", hash = "sha256:e52b53bab58025dcd208eeac13e3c3df5778d4112eeca1f08124096c7738929f", size = 243129, upload-time = "2026-02-20T17:55:13.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/f9/98d825105c450b9c67c27026caa374112b7e466c18331601d02ca278a01b/mistralai-1.12.4-py3-none-any.whl", hash = "sha256:7b69fcbc306436491ad3377fbdead527c9f3a0ce145ec029bf04c6308ff2cca6", size = 509321, upload-time = "2026-02-20T17:55:15.27Z" }, +] + [[package]] name = "more-itertools" version = "10.8.0" @@ -1886,6 +2365,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, ] +[[package]] +name = "msgspec" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/60/f79b9b013a16fa3a58350c9295ddc6789f2e335f36ea61ed10a21b215364/msgspec-0.21.1.tar.gz", hash = "sha256:2313508e394b0d208f8f56892ca9b2799e2561329de9763b19619595a6c0f72c", size = 319193, upload-time = "2026-04-12T21:44:50.394Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/cf/317224852c00248c620a9bcf4b26e2e4ab8afd752f18d2a6ef73ebd423b6/msgspec-0.21.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4248cf0b6129b7d230eacd493c17cc2d4f3989f3bb7f633a928a85b7dcfa251", size = 196188, upload-time = "2026-04-12T21:44:07.181Z" }, + { url = "https://files.pythonhosted.org/packages/6d/81/074612945c0666078f7366f40000013de9f6ba687491d450df699bceebc9/msgspec-0.21.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5102c7e9b3acff82178449b85006d96310e690291bb1ea0142f1b24bcb8aabcb", size = 188473, upload-time = "2026-04-12T21:44:08.736Z" }, + { url = "https://files.pythonhosted.org/packages/8a/37/655101799590bcc5fddb2bd3fe0e6194e816c2d1da7c361725f5eb89a910/msgspec-0.21.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:846758412e9518252b2ac9bffd6f0e54d9ff614f5f9488df7749f81ff5c80920", size = 218871, upload-time = "2026-04-12T21:44:09.917Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d1/d4cd9fe89c7d400d7a18f86ccc94daa3f0927f53558846fcb60791dce5d6/msgspec-0.21.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21995e74b5c598c2e004110ad66ec7f1b8c20bf2bcf3b2de8fd9a3094422d3ff", size = 225025, upload-time = "2026-04-12T21:44:11.191Z" }, + { url = "https://files.pythonhosted.org/packages/24/bf/e20549e602b9edccadeeff98760345a416f9cce846a657e8b18e3396b212/msgspec-0.21.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6129f0cca52992e898fd5344187f7c8127b63d810b2fd73e36fca73b4c6475ee", size = 222672, upload-time = "2026-04-12T21:44:12.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/68/04d7a8f0f786545cf9b8c280c57aa6befb5977af6e884b8b54191cbe44b3/msgspec-0.21.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ef3ec2296248d1f8b9231acb051b6d471dfde8f21819e86c9adaaa9f42918521", size = 227303, upload-time = "2026-04-12T21:44:13.709Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4d/619866af2840875be408047bf9e70ceafbae6ab50660de7134ed1b25eb86/msgspec-0.21.1-cp312-cp312-win_amd64.whl", hash = "sha256:d4ab834a054c6f0cbeef6df9e7e1b33d5f1bc7b86dea1d2fd7cad003873e783d", size = 190017, upload-time = "2026-04-12T21:44:14.977Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2e/a8f9eca8fd00e097d7a9e99ba8a4685db994494448e3d4f0b7f6e9a3c0f7/msgspec-0.21.1-cp312-cp312-win_arm64.whl", hash = "sha256:628aaa35c74950a8c59da330d7e98917e1c7188f983745782027748ee4ca573e", size = 175345, upload-time = "2026-04-12T21:44:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/7e/74/f11ede02839b19ff459f88e3145df5d711626ca84da4e23520cebf819367/msgspec-0.21.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:764173717a01743f007e9f74520ed281f24672c604514f7d76c1c3a10e8edb66", size = 196176, upload-time = "2026-04-12T21:44:17.613Z" }, + { url = "https://files.pythonhosted.org/packages/bb/40/4476c1bd341418a046c4955aff632ec769315d1e3cb94e6acf86d461f9ed/msgspec-0.21.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:344c7cd0eaed1fb81d7959f99100ef71ec9b536881a376f11b9a6c4803365697", size = 188524, upload-time = "2026-04-12T21:44:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d9/9e9d7d7e5061b47540d03d640fab9b3965ba7ae49c1b2154861c8f007518/msgspec-0.21.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48943e278b3854c2f89f955ddc6f9f430d3f0784b16e47d10604ee0463cd21f5", size = 218880, upload-time = "2026-04-12T21:44:20.028Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/2bb344f34abb4b57e60c7c9c761994e0417b9718ec1460bf00c296f2a7ea/msgspec-0.21.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9aa659ebb0101b1cbc31461212b87e341d961f0ab0772aaf068a99e001ec4aa", size = 225050, upload-time = "2026-04-12T21:44:21.577Z" }, + { url = "https://files.pythonhosted.org/packages/1a/84/7c1e412f76092277bf760cef12b7979d03314d259ab5b5cafde5d0c1722d/msgspec-0.21.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7b27d1a8ead2b6f5b0c4f2d07b8be1ccfcc041c8a0e704781edebe3ae13c484", size = 222713, upload-time = "2026-04-12T21:44:22.83Z" }, + { url = "https://files.pythonhosted.org/packages/4e/27/0bba04b2b4ef05f3d068429410bc71d2cea925f1596a8f41152cccd5edb8/msgspec-0.21.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38fe93e86b61328fe544cb7fd871fad5a27c8734bfda90f65e5dbe288ae50f61", size = 227259, upload-time = "2026-04-12T21:44:24.11Z" }, + { url = "https://files.pythonhosted.org/packages/b0/2d/09574b0eea02fed2c2c1383dbaae2c7f79dc16dcd6487a886000afb5d7c4/msgspec-0.21.1-cp313-cp313-win_amd64.whl", hash = "sha256:8bc666331c35fcce05a7cd2d6221adbe0f6058f8e750711413d22793c080ac6a", size = 189857, upload-time = "2026-04-12T21:44:25.359Z" }, + { url = "https://files.pythonhosted.org/packages/46/34/105b1576ad182879914f0c821f17ee1d13abb165cb060448f96fe2aff078/msgspec-0.21.1-cp313-cp313-win_arm64.whl", hash = "sha256:42bb1241e0750c1a4346f2aa84db26c5ffd99a4eb3a954927d9f149ff2f42898", size = 175403, upload-time = "2026-04-12T21:44:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ad/86954e987d1d6a5c579e2c2e7832b65e0fff194179fdac4f581536086024/msgspec-0.21.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fab48eb45fdbfbdb2c0edfec00ffc53b6b6085beefc6b50b61e01659f9f8757f", size = 196261, upload-time = "2026-04-12T21:44:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a1/c5e46c3e42b866199365e35d11dddfd1fbd8bba4fdb3c52f965b1607ce94/msgspec-0.21.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3cb779ea0c35bc807ff941d415875c1f69ca0be91a2e907ab99a171811d86a9a", size = 188729, upload-time = "2026-04-12T21:44:28.99Z" }, + { url = "https://files.pythonhosted.org/packages/85/7d/1e29a319d678d6cb962ae5bdf32a6858ebdf38f73bc654c0e9c742a0c2c8/msgspec-0.21.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68604db36b3b4dd9bf160e436e12798a4738848144cea1aca1cb984011eb160f", size = 219866, upload-time = "2026-04-12T21:44:31.104Z" }, + { url = "https://files.pythonhosted.org/packages/25/1f/cca084ca2572810fff12ea9dbdcbe39eac048f40daf4a9077b49fcbe8cee/msgspec-0.21.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d6b9dc50948eaf65df54d2fd0ff66e6d8c32f116037209ee861810eb9b676cb", size = 224993, upload-time = "2026-04-12T21:44:32.649Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/d2120fc9d419a89a3a7c13e5b7078798c4b392a96a02a6e2b3ce43a8766c/msgspec-0.21.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:52c5e21930942302394429c5a582ce7e6b62c7f983b3760834c2ce107e0dd6df", size = 223535, upload-time = "2026-04-12T21:44:33.839Z" }, + { url = "https://files.pythonhosted.org/packages/75/17/42418b66a3ad972a89bab73dd78b79cc6282bb488a25e73c853cee7443b9/msgspec-0.21.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:abbb39d65681fa24ed394e01af3d59d869068324f900c61d06062b7fb9980f2f", size = 227222, upload-time = "2026-04-12T21:44:35.093Z" }, + { url = "https://files.pythonhosted.org/packages/c4/33/265c894268cca88ff67b144ca2b4c522fc8b9a6f1966a3640c70516e78e1/msgspec-0.21.1-cp314-cp314-win_amd64.whl", hash = "sha256:5666b1b560b97b6ec2eb3fca8a502298ebac56e13bbca1f88523538ce83d01ea", size = 193810, upload-time = "2026-04-12T21:44:36.612Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8f/a6d35f25bf1fc63c492fdd88fdce01ba0875ead48c2b91f90f33653b4131/msgspec-0.21.1-cp314-cp314-win_arm64.whl", hash = "sha256:d8b8578e4c83b14ceea4cef0d0b747e31d9330fe4b03b2b2ad4063866a178f93", size = 179125, upload-time = "2026-04-12T21:44:38.198Z" }, + { url = "https://files.pythonhosted.org/packages/c6/39/74839641e64b99d87da55af0fc472854d42b46e2183b9e2a67fe1bb2a512/msgspec-0.21.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15f523d51c00ebad412213bfe9f06f0a50ec2b93e0c19e824a2d267cabb48ea2", size = 200171, upload-time = "2026-04-12T21:44:39.414Z" }, + { url = "https://files.pythonhosted.org/packages/70/9b/ce0cca6d2d87fcd4b6ff97600790494e64f26a2c55d61507cd2755c16193/msgspec-0.21.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e47390360583ba3d5c6cb44cf0a9f61b0a06a899d3c2c00627cedebb2e2884b", size = 192879, upload-time = "2026-04-12T21:44:40.882Z" }, + { url = "https://files.pythonhosted.org/packages/a7/08/673a7bb05e5702dc787ddd3011195b509f9867927970da59052211929987/msgspec-0.21.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f60800e6299b798142dc40b0644da77ceac5ea0568be58228417eae14135c847", size = 226281, upload-time = "2026-04-12T21:44:42.181Z" }, + { url = "https://files.pythonhosted.org/packages/7d/45/86508cf57283e9070b3c447e3ab25b792a7a0855a3ea4e0c6d111ac34c97/msgspec-0.21.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5f8e9dfcd98419cf7568808470c4317a3fb30bef0e3715b568730a2b272a20d7", size = 229863, upload-time = "2026-04-12T21:44:43.442Z" }, + { url = "https://files.pythonhosted.org/packages/2c/62/e7c9367cd08d590559faacd711edbae36840342843e669440363f33c7d36/msgspec-0.21.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92d89dfad13bd1ea640dc3e37e724ed380da1030b272bdf5ecafb983c3ad7c75", size = 230445, upload-time = "2026-04-12T21:44:44.806Z" }, + { url = "https://files.pythonhosted.org/packages/42/b4/c0f54632103846b658a10930025f4de41c8724b5e4805a5f3b395586cb7e/msgspec-0.21.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0d03867786e5d7ba25d666df4b11320c27170f4aeafcb8e3a8b0a50a4fb742ca", size = 231822, upload-time = "2026-04-12T21:44:46.343Z" }, + { url = "https://files.pythonhosted.org/packages/ea/1d/0d85cc79d0ccf5508e9c846cc66552a6a16bf92abd1dbd8362617f7b35cd/msgspec-0.21.1-cp314-cp314t-win_amd64.whl", hash = "sha256:740fbf1c9d59992ca3537d6fbe9ebbf9eaf726a65fbf31448e0ecbc710697a63", size = 206650, upload-time = "2026-04-12T21:44:47.601Z" }, + { url = "https://files.pythonhosted.org/packages/90/91/56c5d560f20e6c20e9e4f55bd0e458f7f162aa689ee350346c04c48eac0b/msgspec-0.21.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0d2cc73df6058d811a126ac3a8ad63a4dfa210c82f9cf5a004802eaf4712de90", size = 183149, upload-time = "2026-04-12T21:44:48.833Z" }, +] + [[package]] name = "multidict" version = "6.7.1" @@ -1985,6 +2504,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] +[[package]] +name = "narwhals" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/f3/257adc69a71011b4c8cda321b00f02c5bf1980ae38ffd05a58d9632d4de8/narwhals-2.20.0.tar.gz", hash = "sha256:c10994975fa7dc5a68c2cffcddbd5908fc8ebb2d463c5bab085309c0ee1f551e", size = 627848, upload-time = "2026-04-20T12:11:45.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl", hash = "sha256:16e750ea5507d4ba6e8d03455b5f93a535e0405976561baea235bca5dc9f475d", size = 449373, upload-time = "2026-04-20T12:11:43.596Z" }, +] + [[package]] name = "networkx" version = "3.6.1" @@ -1994,6 +2522,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, ] +[[package]] +name = "nexus-rpc" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/d5/cd1ffb202b76ebc1b33c1332a3416e55a39929006982adc2b1eb069aaa9b/nexus_rpc-1.4.0.tar.gz", hash = "sha256:3b8b373d4865671789cc43623e3dc0bcbf192562e40e13727e17f1c149050fba", size = 82367, upload-time = "2026-02-25T22:01:34.053Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/52/6327a5f4fda01207205038a106a99848a41c83e933cd23ea2cab3d2ebc6c/nexus_rpc-1.4.0-py3-none-any.whl", hash = "sha256:14c953d3519113f8ccec533a9efdb6b10c28afef75d11cdd6d422640c40b3a49", size = 29645, upload-time = "2026-02-25T22:01:33.122Z" }, +] + [[package]] name = "nltk" version = "3.9.3" @@ -2081,7 +2621,7 @@ wheels = [ [[package]] name = "openai" -version = "2.24.0" +version = "2.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -2093,9 +2633,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/13/17e87641b89b74552ed408a92b231283786523edddc95f3545809fab673c/openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673", size = 658717, upload-time = "2026-02-24T20:02:07.958Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/59/bdcc6b759b8c42dd73afaf5bf8f902c04b37987a5514dbc1c64dba390fef/openai-2.32.0.tar.gz", hash = "sha256:c54b27a9e4cb8d51f0dd94972ffd1a04437efeb259a9e60d8922b8bd26fe55e0", size = 693286, upload-time = "2026-04-15T22:28:19.434Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122, upload-time = "2026-02-24T20:02:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c1/d6e64ccd0536bf616556f0cad2b6d94a8125f508d25cfd814b1d2db4e2f1/openai-2.32.0-py3-none-any.whl", hash = "sha256:4dcc9badeb4bf54ad0d187453742f290226d30150890b7890711bda4f32f192f", size = 1162570, upload-time = "2026-04-15T22:28:17.714Z" }, ] [[package]] @@ -2141,13 +2681,122 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, ] +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde", size = 18366, upload-time = "2025-12-11T13:32:20.2Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size = 17288, upload-time = "2025-12-11T13:32:42.029Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985", size = 19641, upload-time = "2025-12-11T13:32:22.248Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-httpx" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/08/11208bcfcab4fc2023252c3f322aa397fd9ad948355fea60f5fc98648603/opentelemetry_instrumentation_httpx-0.60b1.tar.gz", hash = "sha256:a506ebaf28c60112cbe70ad4f0338f8603f148938cb7b6794ce1051cd2b270ae", size = 20611, upload-time = "2025-12-11T13:37:01.661Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/59/b98e84eebf745ffc75397eaad4763795bff8a30cbf2373a50ed4e70646c5/opentelemetry_instrumentation_httpx-0.60b1-py3-none-any.whl", hash = "sha256:f37636dd742ad2af83d896ba69601ed28da51fa4e25d1ab62fde89ce413e275b", size = 15701, upload-time = "2025-12-11T13:36:04.56Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, +] + +[[package]] +name = "opentelemetry-util-http" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/fc/c47bb04a1d8a941a4061307e1eddfa331ed4d0ab13d8a9781e6db256940a/opentelemetry_util_http-0.60b1.tar.gz", hash = "sha256:0d97152ca8c8a41ced7172d29d3622a219317f74ae6bb3027cfbdcf22c3cc0d6", size = 11053, upload-time = "2025-12-11T13:37:25.115Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/5c/d3f1733665f7cd582ef0842fb1d2ed0bc1fba10875160593342d22bba375/opentelemetry_util_http-0.60b1-py3-none-any.whl", hash = "sha256:66381ba28550c91bee14dcba8979ace443444af1ed609226634596b4b0faf199", size = 8947, upload-time = "2025-12-11T13:36:37.151Z" }, +] + [[package]] name = "packaging" -version = "26.0" +version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] @@ -2202,6 +2851,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085, upload-time = "2026-02-17T22:20:14.331Z" }, ] +[[package]] +name = "parso" +version = "0.8.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, +] + [[package]] name = "patchright" version = "1.58.0" @@ -2352,6 +3010,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, ] +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + [[package]] name = "propcache" version = "0.4.1" @@ -2436,6 +3106,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] +[[package]] +name = "protobuf" +version = "6.33.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, +] + [[package]] name = "psutil" version = "7.2.2" @@ -2489,6 +3174,27 @@ memory = [ { name = "cachetools" }, ] +[[package]] +name = "pyasn1" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + [[package]] name = "pychainedproxy" version = "1.3" @@ -2527,6 +3233,106 @@ email = [ { name = "email-validator" }, ] +[[package]] +name = "pydantic-ai" +version = "1.81.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "fastmcp", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "spec", "temporal", "ui", "vertexai", "xai"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/f1/612b9a742452614a5f28030f9a119999d139ba354171697c59bdbd6d85e6/pydantic_ai-1.81.0.tar.gz", hash = "sha256:2c860aafa9ab09dd88cf408c02f6bbde1aabe18e7c6bb59ebcc68fb3be27d22b", size = 12816, upload-time = "2026-04-14T01:47:56.682Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/93/fef51a92fb2481e5e1633b31ff6fceb1bdb7e2bf0265f9c56832f6182900/pydantic_ai-1.81.0-py3-none-any.whl", hash = "sha256:90a8d4aaf8497d2105c6ef6917865016a3602515c12fe0f4204fd7e64c57eb1c", size = 7576, upload-time = "2026-04-14T01:47:48.739Z" }, +] + +[[package]] +name = "pydantic-ai-slim" +version = "1.81.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "genai-prices" }, + { name = "griffelib" }, + { name = "httpx" }, + { name = "opentelemetry-api" }, + { name = "pydantic" }, + { name = "pydantic-graph" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/e9/8fbc609f28cb708bfcc5db7864d40bd4ac84f3e3780321ca50d7739f3c3d/pydantic_ai_slim-1.81.0.tar.gz", hash = "sha256:9895e2d3ae46b8e0342af5b862c987cfb86df87ef887dc8352e372b183db6c06", size = 550997, upload-time = "2026-04-14T01:47:58.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/81/dc7062fc325ebb448ed9311edfde895c009ddf3cbd3d65c815b8a52c4bf8/pydantic_ai_slim-1.81.0-py3-none-any.whl", hash = "sha256:1d3dd19a53bcdcc9baf75d7b60daee87a80f0647df221776a76b177f4526ed2a", size = 705019, upload-time = "2026-04-14T01:47:51.399Z" }, +] + +[package.optional-dependencies] +ag-ui = [ + { name = "ag-ui-protocol" }, + { name = "starlette" }, +] +anthropic = [ + { name = "anthropic" }, +] +bedrock = [ + { name = "boto3" }, +] +cli = [ + { name = "argcomplete" }, + { name = "prompt-toolkit" }, + { name = "pyperclip" }, + { name = "pyyaml" }, + { name = "rich" }, +] +cohere = [ + { name = "cohere", marker = "sys_platform != 'emscripten'" }, +] +evals = [ + { name = "pydantic-evals" }, +] +fastmcp = [ + { name = "fastmcp" }, +] +google = [ + { name = "google-genai" }, +] +groq = [ + { name = "groq" }, +] +huggingface = [ + { name = "huggingface-hub" }, +] +logfire = [ + { name = "logfire", extra = ["httpx"] }, +] +mcp = [ + { name = "mcp" }, +] +mistral = [ + { name = "mistralai" }, +] +openai = [ + { name = "openai" }, + { name = "tiktoken" }, +] +retries = [ + { name = "tenacity" }, +] +spec = [ + { name = "pydantic-handlebars" }, + { name = "pyyaml" }, +] +temporal = [ + { name = "temporalio" }, +] +ui = [ + { name = "starlette" }, +] +vertexai = [ + { name = "google-auth" }, + { name = "requests" }, +] +xai = [ + { name = "xai-sdk" }, +] + [[package]] name = "pydantic-core" version = "2.41.5" @@ -2598,6 +3404,50 @@ wheels = [ { 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, upload-time = "2025-11-04T13:42:59.471Z" }, ] +[[package]] +name = "pydantic-evals" +version = "1.81.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "pydantic-ai-slim" }, + { name = "pyyaml" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/47/8e1bc88e1fce1a636c4d4bc5fe883f37d2737f9c03efcd7aeb9238ebf07c/pydantic_evals-1.81.0.tar.gz", hash = "sha256:70fd1d9a1e8c17b8e45affd635427f05e2d1e92111d98287b3bd1393d32a65b3", size = 65783, upload-time = "2026-04-14T01:48:00.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/8e/9bee73a780b769c1cb0fa4ad88f22f0dc27d2231358e0ad727b0a9c2f036/pydantic_evals-1.81.0-py3-none-any.whl", hash = "sha256:49516ddadd8064365684cdb4bdfda2e5f1d9b786bebae239d84445727d1e3f26", size = 77710, upload-time = "2026-04-14T01:47:53.166Z" }, +] + +[[package]] +name = "pydantic-graph" +version = "1.81.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/69/c38ea1c4c8b9789ce1777b408b9fe0f889638415cd97f53a9f95ffbbb044/pydantic_graph-1.81.0.tar.gz", hash = "sha256:721b33324dc25b2ce5956fed8a362e8c558163b45d39e9f83e8ea7b5d44743c0", size = 59240, upload-time = "2026-04-14T01:48:00.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/bd/9b0561bea26a9918c819b391c3de08cdaa9ef99bab9b4fba8f557df70503/pydantic_graph-1.81.0-py3-none-any.whl", hash = "sha256:9f6256612323d9708b2a3a140db3a3e8ee2fe30c3f1befa897ea473e82cc0faa", size = 73063, upload-time = "2026-04-14T01:47:54.631Z" }, +] + +[[package]] +name = "pydantic-handlebars" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/16/d41768bd3fd77e6250c20be11a3e68fee5fff07c3356455e6708f6a60f2a/pydantic_handlebars-0.1.0.tar.gz", hash = "sha256:1931c54946add1b5e3796c9bf6a005ed7662cef0109bb05c352f0b3d031a1260", size = 159826, upload-time = "2026-03-01T20:00:17.497Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5f/86b1630be61bdebf253c2f953a6c3f073ec21bb0725565ea3896802e1ca3/pydantic_handlebars-0.1.0-py3-none-any.whl", hash = "sha256:8a436fe8bc607295eb04bec58bd6e2c9498c9e069c557ff0b505e3d568c783bc", size = 40890, upload-time = "2026-03-01T20:00:16.106Z" }, +] + [[package]] name = "pydantic-settings" version = "2.13.1" @@ -2679,6 +3529,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cd/9f/5986adadc51c867799bc076431fba64fd8ec45d2e528f974838df8aca9da/pylint-2.7.1-py3-none-any.whl", hash = "sha256:a251b238db462b71d25948f940568bb5b3ae0e37dbaa05e10523f54f83e6cc7e", size = 343009, upload-time = "2021-02-23T20:15:22.983Z" }, ] +[[package]] +name = "pymdown-extensions" +version = "10.21.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/08/f1c908c581fd11913da4711ea7ba32c0eee40b0190000996bb863b0c9349/pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc", size = 853922, upload-time = "2026-03-29T15:01:55.233Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638", size = 268901, upload-time = "2026-03-29T15:01:53.244Z" }, +] + [[package]] name = "pymupdf" version = "1.27.1" @@ -2982,6 +3845,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, +] + [[package]] name = "rank-bm25" version = "0.2.2" @@ -3322,6 +4228,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/50/0a9e7e7afe7339bd5e36911f0ceb15fed51945836ed803ae5afd661057fd/rtree-1.4.1-py3-none-win_arm64.whl", hash = "sha256:3d46f55729b28138e897ffef32f7ce93ac335cb67f9120125ad3742a220800f0", size = 355253, upload-time = "2025-08-13T19:32:00.296Z" }, ] +[[package]] +name = "s3transfer" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/29/af14f4ef3c11a50435308660e2cc68761c9a7742475e0585cd4396b91777/s3transfer-0.16.1.tar.gz", hash = "sha256:8e424355754b9ccb32467bdc568edf55be82692ef2002d934b1311dbb3b9e524", size = 154801, upload-time = "2026-04-22T20:36:06.475Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/19/90d7d4ed51932c022d53f1d02d564b62d10e272692a1f9b76425c1ad2a02/s3transfer-0.16.1-py3-none-any.whl", hash = "sha256:61bcd00ccb83b21a0fe7e91a553fff9729d46c83b4e0106e7c314a733891f7c2", size = 86825, upload-time = "2026-04-22T20:36:04.992Z" }, +] + [[package]] name = "scipy" version = "1.17.1" @@ -3518,6 +4436,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, ] +[[package]] +name = "temporalio" +version = "1.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nexus-rpc" }, + { name = "protobuf" }, + { name = "types-protobuf" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/d4/fa21150a225393f87732ed6fef3cc9735d9e751edc6be415fe6e375105c6/temporalio-1.26.0.tar.gz", hash = "sha256:f4bfb35125e6f5e8c7f7ed1277c7354d812c6fac7ed5f8dbd50536cf289aaaa7", size = 2388994, upload-time = "2026-04-15T23:43:00.911Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/27/8c421c622d18cc8e034247d5d72b89e6456937344b5bec1de40abef3c085/temporalio-1.26.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:5489040c0cf621edeb36984199dd9e4fbd2b3a07d61a4f2a8da1f2cb9820ef26", size = 14221070, upload-time = "2026-04-15T23:42:26.21Z" }, + { url = "https://files.pythonhosted.org/packages/49/7c/d2b691d16ec5db87198c2e08dbfba58e286c096faee15753613a581abdce/temporalio-1.26.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:b18dd85771509c19ef059a31908bcd4e6130d1f67037c4db519702f3f2ad6d4a", size = 13583991, upload-time = "2026-04-15T23:42:34.357Z" }, + { url = "https://files.pythonhosted.org/packages/05/ca/b8728451320ca9d8bb6e1680b9bd23767118f86d5b8644edf2304d533f1b/temporalio-1.26.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46187d5f82ca2ae81f35ea5916a76db0e2f067210dc6b1852c3749475721946e", size = 13808036, upload-time = "2026-04-15T23:42:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/cb/54/3113f5e0ac58655790abac64656373e06191b351d74bfb94692e81bd6784/temporalio-1.26.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03300c3e5237443367ac61bb20bd726c656b3daa50310bdd436599d5bdc7cf97", size = 14336604, upload-time = "2026-04-15T23:42:49.851Z" }, + { url = "https://files.pythonhosted.org/packages/fd/9b/c50840a26af3587c0c8d9af04d9976743e22496996dc1a377efc75dcd316/temporalio-1.26.0-cp310-abi3-win_amd64.whl", hash = "sha256:1c4a0d82f0a3796cbf78864c799f8dca0b94cdaec68e7b8b224c859005686ec4", size = 14525849, upload-time = "2026-04-15T23:42:57.589Z" }, +] + [[package]] name = "tenacity" version = "9.1.4" @@ -3622,6 +4559,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, ] +[[package]] +name = "tomlkit" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, +] + [[package]] name = "tqdm" version = "4.67.3" @@ -3685,6 +4631,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, ] +[[package]] +name = "types-protobuf" +version = "6.32.1.20260221" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/e2/9aa4a3b2469508bd7b4e2ae11cbedaf419222a09a1b94daffcd5efca4023/types_protobuf-6.32.1.20260221.tar.gz", hash = "sha256:6d5fb060a616bfb076cbb61b4b3c3969f5fc8bec5810f9a2f7e648ee5cbcbf6e", size = 64408, upload-time = "2026-02-21T03:55:13.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/e8/1fd38926f9cf031188fbc5a96694203ea6f24b0e34bd64a225ec6f6291ba/types_protobuf-6.32.1.20260221-py3-none-any.whl", hash = "sha256:da7cdd947975964a93c30bfbcc2c6841ee646b318d3816b033adc2c4eb6448e4", size = 77956, upload-time = "2026-02-21T03:55:12.894Z" }, +] + [[package]] name = "types-requests" version = "2.32.4.20260107" @@ -3846,6 +4801,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, ] +[[package]] +name = "wcwidth" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, +] + [[package]] name = "websockets" version = "16.0" @@ -3954,6 +4918,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/da/5a086bf4c22a41995312db104ec2ffeee2cf6accca9faaee5315c790377d/wrapt-2.1.1-py3-none-any.whl", hash = "sha256:3b0f4629eb954394a3d7c7a1c8cca25f0b07cefe6aa8545e862e9778152de5b7", size = 43886, upload-time = "2026-02-03T02:11:45.048Z" }, ] +[[package]] +name = "xai-sdk" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/32/bb8385f7a3b05ce406b689aa000c9a34289caa1526f1c093a1cefc0d9695/xai_sdk-1.11.0.tar.gz", hash = "sha256:ca87a830d310fb8e06fba44fb2a8c5cdf0d9f716b61126eddd51b7f416a63932", size = 404313, upload-time = "2026-03-27T18:23:10.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/76/86d9a3589c725ce825d2ed3e7cb3ecf7f956d3fd015353d52197bb341bcd/xai_sdk-1.11.0-py3-none-any.whl", hash = "sha256:fe58ce6d8f8115ae8bd57ded57bcd847d0bb7cb28bb7b236abefd4626df1ed8d", size = 251388, upload-time = "2026-03-27T18:23:08.573Z" }, +] + [[package]] name = "xxhash" version = "3.6.0"