diff --git a/docs/guides/Agents.md b/docs/guides/Agents.md index fff11f8c..cc4c49a4 100644 --- a/docs/guides/Agents.md +++ b/docs/guides/Agents.md @@ -20,7 +20,10 @@ The agents API is intentionally built as a thin layer over existing Draive runti - `AgentIdentity` describes the agent instance: `uri`, `name`, `description`, `meta`. - `AgentMessage` is the fully prepared input payload: `thread`, `created`, `content`, `meta`. -- `AgentThread` is the scoped runtime state propagated through `ctx.scope(...)`. +- `AgentThread` is the scoped runtime state propagated through `ctx.scope(...)`: its `identifier` + names the conversation thread shared across nested agent calls, while `agent_uri` marks the + URI of the agent currently executing within it - agents bind a thread stamped with their own + URI while handling a message. - `AgentExecuting` is the executor protocol: `AgentMessage -> AsyncIterable[MultimodalContentPart | ProcessingEvent]`. @@ -171,9 +174,13 @@ APIs, or provide the required context explicitly. ### Persist Context Across Turns With `AgentMemory` -`Agent.generative(...)`, `Agent.steps(...)` and `Agent.from_skill(...)` accept a `memory` argument -controlling how model context is recalled before each turn and persisted afterwards. The default is -`AgentMemory.disabled`, which scopes context to a single turn. +`Agent.generative(...)` and `Agent.from_skill(...)` accept a `memory` argument controlling how +model context is prepared and recalled before each turn and persisted afterwards. The default is +`AgentMemory.disabled`, which scopes context to a single turn. `Agent.steps(...)` applies no memory +implicitly - compose the predefined memory steps (`memory.prepare_step(...)`, +`memory.recall_step()`, `memory.remember_step()`) into the pipeline where needed; +`Agent.generative(...)` and `Agent.from_skill(...)` invoke `memory.prepare`, `memory.recall`, and +`memory.remember` directly within their step bodies instead. ```python from draive import Agent, AgentMemory @@ -185,20 +192,32 @@ assistant: Agent = Agent.generative( ) ``` -Memory is keyed by the active `AgentThread`, so one memory instance serves multiple concurrent -conversation threads. Context is stored as the latest snapshot per thread: whatever is remembered -after a turn becomes the next recalled context, exactly as provided, which allows steps to compact, -summarize, or replace the context freely. - -- `AgentMemory.volatile(...)` keeps snapshots in-process, with optional LRU eviction via - `threads_limit`; intended for local development, tests, and single-process deployments. -- `PostgresAgentMemory.prepare(identity)` (from `draive.postgres`) persists immutable snapshots in - PostgreSQL, keyed by agent identity and thread; run `PostgresAgentMemory.migrate()` once to - create its schema. -- `AgentMemory(recalling=..., remembering=...)` wraps custom async callables for any other backend. - -Each memory instance is intended to serve exactly one agent: state is scoped per thread only, so -sharing an instance between agents would mix their contexts within a thread. +Memory operations receive the active `AgentThread` - the executing agent's URI together with the +conversation thread identifier - so one memory instance can serve multiple agents and multiple +concurrent conversation threads, as long as the implementation keys stored context by both (the +built-in ones do). Context is stored as the latest snapshot per agent and thread: whatever is +remembered after a turn becomes the next recalled context, exactly as provided, which allows steps +to compact, summarize, or replace the context freely. + +- `AgentMemory.volatile(...)` keeps snapshots in-process, with optional LRU-like eviction via + `threads_limit`. An entry's usage is refreshed by `prepare` and `remember`, but not by + `recall`; intended for local development, tests, and single-process deployments. +- `PostgresAgentMemory.instance()` (from `draive.postgres`) persists immutable snapshots in + PostgreSQL, keyed by executing agent URI and thread; run `PostgresAgentMemory.migrate()` once + to create its schema. +- `AgentMemory(recalling=..., remembering=...)` wraps custom async callables for any other + backend; each receives the executing `AgentThread`, and an optional `preparing=` callable runs + before each recall to set up state based on the agent's instructions (it must be idempotent + per agent and thread). + +Two behavioral notes: + +- Context is remembered only when a turn completes and its output stream is fully consumed; + turns abandoned mid-stream or failing with an error are not persisted. +- Memory steps (`prepare_step`, `recall_step`, `remember_step`) require an `AgentThread` bound in + the current context and raise `AgentException` otherwise. Agents bind one stamped with their + own URI automatically; when running memory steps outside an agent, enter a scope with an + `AgentThread` instance first. ## 3. Preserve Thread And Metadata @@ -232,7 +251,7 @@ agent: Agent = Agent( async with ctx.scope( "agents.context", - AgentThread.of(identifier=uuid4(), meta={"source": "outer"}), + AgentThread.of(identifier=uuid4(), agent_uri="agent://outer", meta={"source": "outer"}), ): stream: AsyncIterable[MultimodalContentPart | ProcessingEvent] = agent.call( input="hello", @@ -394,6 +413,9 @@ The public agents API exported from `draive` includes: - `AgentExecuting` - `AgentIdentity` - `AgentMemory` +- `AgentMemoryPreparing` +- `AgentMemoryRecalling` +- `AgentMemoryRemembering` - `AgentMessage` - `AgentThread` - `ProcessingEvent` diff --git a/docs/guides/Postgres.md b/docs/guides/Postgres.md index 73757749..c9dcf4ec 100644 --- a/docs/guides/Postgres.md +++ b/docs/guides/Postgres.md @@ -4,8 +4,8 @@ Draive ships with Postgres-backed implementations for common persistence interfa relational storage into your workflows without writing adapters. All helpers live in `draive.postgres` and reuse the shared `haiway.postgres.Postgres` connection states. -Current adapters include `PostgresConfigurationRepository`, `PostgresTemplatesRepository`, and -`PostgresVectorIndex`. +Current adapters include `PostgresConfigurationRepository`, `PostgresTemplatesRepository`, +`PostgresVectorIndex`, and `PostgresAgentMemory`. ## Bootstrapping the Postgres context @@ -211,6 +211,49 @@ payload JSON. Requirements are translated to SQL expressions (for example, `AttributeRequirement.equal` becomes `payload #>> '{text}' = $2`). Unsupported operators raise `NotImplementedError`, ensuring the query surface remains explicit. +## AgentMemory implementation + +`PostgresAgentMemory` persists agent conversation context across turns, keyed by the executing +agent URI and the conversation thread identifier - both carried on the `AgentThread` passed to +each memory operation. See the [Agents](./Agents.md) guide for how `AgentMemory` participates in +agent execution. + +Run the schema migration once with an acquired connection bound in context: + +```python +from draive import ctx +from draive.postgres import Postgres, PostgresAgentMemory + +async with ctx.scope("migration"): + async with Postgres.acquire_connection() as connection: + with ctx.updating(connection): + await PostgresAgentMemory.migrate() +``` + +Then create a memory instance and pass it to an agent: + +```python +from draive import Agent +from draive.postgres import PostgresAgentMemory + +assistant = Agent.generative( + "assistant", + instructions="You are a concise support assistant.", + memory=PostgresAgentMemory.instance(), +) +``` + +Storage semantics: + +- Context is stored as immutable snapshots: every remember inserts the full context as a new row + and recall reads back the latest one whole, so steps may compact, summarize, or replace the + context freely between turns. +- Persistence is lock-free and write-only: previous snapshots are never modified or deleted and + remain available for tracking and verification. Snapshot history grows without bound over the + lifetime of a thread. +- A single memory instance can serve multiple agents; recalled and remembered context is isolated + per agent URI and thread, both taken from the executing `AgentThread` at call time. + ## Putting it together Combine these adapters with higher-level Draive components to centralise operational data in diff --git a/pyproject.toml b/pyproject.toml index 89c3fa8f..f3202302 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "uv_build" [project] name = "draive" description = "Framework designed to simplify and accelerate the development of LLM-based applications." -version = "0.113.1" +version = "0.114.0" readme = "README.md" maintainers = [ { name = "Kacper KaliƄski", email = "kacper.kalinski@miquido.com" }, diff --git a/src/draive/agents/__init__.py b/src/draive/agents/__init__.py index 3764b06f..8ff71685 100644 --- a/src/draive/agents/__init__.py +++ b/src/draive/agents/__init__.py @@ -5,6 +5,9 @@ AgentException, AgentExecuting, AgentIdentity, + AgentMemoryPreparing, + AgentMemoryRecalling, + AgentMemoryRemembering, AgentMessage, AgentThread, AgentUnavailable, @@ -16,6 +19,9 @@ "AgentExecuting", "AgentIdentity", "AgentMemory", + "AgentMemoryPreparing", + "AgentMemoryRecalling", + "AgentMemoryRemembering", "AgentMessage", "AgentThread", "AgentUnavailable", diff --git a/src/draive/agents/agent.py b/src/draive/agents/agent.py index 405eaad3..c7ab33fa 100644 --- a/src/draive/agents/agent.py +++ b/src/draive/agents/agent.py @@ -1,6 +1,6 @@ -from collections.abc import AsyncIterable, Iterable +from collections.abc import AsyncIterable, Iterable, MutableSequence, Sequence from typing import Any, NoReturn, Self, final, overload -from uuid import UUID +from uuid import UUID, uuid4 from haiway import Meta, MetaValues, ctx @@ -12,8 +12,12 @@ AgentThread, ) from draive.models import ( + GenerativeModel, ModelInstructions, + ModelOutput, + ModelOutputBlock, ModelOutputSelection, + ModelReasoning, ModelReasoningChunk, ModelToolRequest, ModelToolResponse, @@ -24,9 +28,10 @@ MultimodalContent, MultimodalContentPart, Template, + TemplatesRepository, ) from draive.skills import Skill -from draive.steps import Step +from draive.steps import Step, StepState, StepStream from draive.tools import Tool, Toolbox, tool from draive.tools.types import ToolOutputChunk from draive.utils import ProcessingEvent @@ -142,7 +147,7 @@ def generative( ) -> Self: ... @classmethod - def generative( + def generative( # noqa: C901, PLR0915 cls, agent: AgentIdentity | str, *, @@ -166,8 +171,11 @@ def generative( tools : Toolbox | Iterable[Tool], default=Toolbox.empty Tools available to the model while handling requests. memory : AgentMemory, default=AgentMemory.disabled - Memory used to recall context before each turn and persist it - afterwards. Defaults to a no-op memory scoped to a single turn. + Memory used to prepare and recall context before each turn and + persist it afterwards. Defaults to a no-op memory scoped to a + single turn. Context is remembered only when the turn completes + and its output stream is fully consumed - turns abandoned + mid-stream or failing with an error are not persisted. output : ModelOutputSelection, default="auto" Output selection mode forwarded to model completion. meta : Meta | MetaValues | None, default=None @@ -189,14 +197,143 @@ def generative( else: identity = agent + toolbox: Toolbox = Toolbox.of(tools) + + async def step( # noqa: C901, PLR0912, PLR0915 + state: StepState, + ) -> StepStream: + async with ctx.scope("agent.generative"): + if isinstance(instructions, Template): + ctx.record_info(attributes={"instructions.template": instructions.identifier}) + resolved_instructions: str = await TemplatesRepository.resolve_str(instructions) + + else: + resolved_instructions = instructions + + if not ctx.contains_state(AgentThread): + raise ValueError("AgentThread not specified") + + thread: AgentThread = ctx.state(AgentThread) + + await memory.prepare( + thread=thread, + instructions=resolved_instructions, + ) + + state = state.replacing_context( + await memory.recall( + thread=thread, + context=state.context, + ) + ) + + iteration: int = 0 + while True: # loop until we get ModelOutput without tools + async with ctx.scope(f"agent.generative.turn_{iteration}"): + ctx.log_debug("Generating completion...") + content_accumulator: MutableSequence[MultimodalContentPart] = [] + reasoning_accumulator: MutableSequence[ModelReasoningChunk] = [] + output_accumulator: MutableSequence[ModelOutputBlock] = [] + + async for chunk in GenerativeModel.completion( + instructions=resolved_instructions, + tools=toolbox.model_tools(iteration=iteration), + context=state.context, + output=output, + ): + yield chunk + + if isinstance(chunk, ModelReasoningChunk): + if content_accumulator: + output_accumulator.append( + MultimodalContent.of(*content_accumulator) + ) + content_accumulator.clear() + + reasoning_accumulator.append(chunk) + + elif isinstance(chunk, ModelToolRequest): + # TODO: start handling immediately + if content_accumulator: + output_accumulator.append( + MultimodalContent.of(*content_accumulator) + ) + content_accumulator.clear() + + if reasoning_accumulator: + output_accumulator.append( + ModelReasoning.of(reasoning_accumulator) + ) + reasoning_accumulator.clear() + + output_accumulator.append(chunk) + + else: + if reasoning_accumulator: + output_accumulator.append( + ModelReasoning.of(reasoning_accumulator) + ) + reasoning_accumulator.clear() + + content_accumulator.append(chunk) + + if content_accumulator: + output_accumulator.append(MultimodalContent.of(*content_accumulator)) + + if reasoning_accumulator: + output_accumulator.append(ModelReasoning.of(reasoning_accumulator)) + + model_output: ModelOutput = ModelOutput.of(*output_accumulator) + + state = state.appending_context(model_output) + yield state + + tool_requests: Sequence[ModelToolRequest] = model_output.tool_requests + if not tool_requests: + break # end of loop + + ctx.log_debug("...handling tool requests...") + + responses: MutableSequence[ModelToolResponse] = [] + tools_output_accumulator: MutableSequence[MultimodalContentPart] = [] + async for chunk in toolbox.handle(*tool_requests): + if isinstance(chunk, ModelToolResponse): + responses.append(chunk) + yield chunk + + elif isinstance(chunk, ProcessingEvent): + yield chunk + + else: + tools_output_accumulator.append(chunk) + yield chunk + + ctx.log_debug("...received tool responses...") + + if tools_output_accumulator: # tools direct result + ctx.log_debug("...tools generated output...") + state = state.appending_context( + ModelInput.of(*responses), + ModelOutput.of(MultimodalContent.of(*tools_output_accumulator)), + ) + yield state + break # end of loop + + else: # regular tools result + state = state.appending_context( + ModelInput.of(*responses), + ) + yield state + iteration += 1 # continue next iteration + + await memory.remember( + thread=thread, + context=state.context, + ) + return cls.steps( - Step.looping_completion( - instructions=instructions, - tools=tools, - output=output, - ), + Step(step), agent=identity, - memory=memory, ) @overload @@ -244,8 +381,11 @@ def from_skill( tools : Toolbox | Iterable[Tool], default=Toolbox.empty Additional tools available while handling requests. memory : AgentMemory, default=AgentMemory.disabled - Memory used to recall context before each turn and persist it - afterwards. Defaults to a no-op memory scoped to a single turn. + Memory used to prepare and recall context before each turn and + persist it afterwards. Defaults to a no-op memory scoped to a + single turn. Context is remembered only when the turn completes + and its output stream is fully consumed - turns abandoned + mid-stream or failing with an error are not persisted. output : ModelOutputSelection, default="auto" Output selection mode forwarded to model completion. meta : Meta | MetaValues | None, default=None @@ -270,16 +410,14 @@ def from_skill( resolved_toolbox = tools.with_tools(skill.resources_tool()) else: - resolved_toolbox = Toolbox.of(skill.resources_tool(), *tools) + resolved_toolbox = Toolbox.of(*tools, skill.resources_tool()) - return cls.steps( - Step.looping_completion( - instructions=skill.instructions, - tools=resolved_toolbox, - output=output, - ), - agent=identity, + return cls.generative( + identity, + instructions=skill.instructions, + tools=resolved_toolbox, memory=memory, + output=output, ) @overload @@ -290,7 +428,6 @@ def steps( step: Step, *steps: Step, agent: AgentIdentity, - memory: AgentMemory = AgentMemory.disabled, ) -> Self: ... @overload @@ -302,7 +439,6 @@ def steps( *steps: Step, agent: str, description: str = "", - memory: AgentMemory = AgentMemory.disabled, meta: Meta | MetaValues | None = None, ) -> Self: ... @@ -314,7 +450,6 @@ def steps( *steps: Step, agent: AgentIdentity | str, description: str = "", - memory: AgentMemory = AgentMemory.disabled, meta: Meta | MetaValues | None = None, ) -> Self: """Create an agent from one or more ``Step`` pipeline stages. @@ -322,17 +457,14 @@ def steps( Parameters ---------- step : Step - First step executed after the incoming message is appended as input. + First step executed on the initial context holding the incoming + message as input. *steps : Step Additional steps executed sequentially after ``step``. agent : AgentIdentity | str Human-readable agent name or full identity. description : str, default="" Short description of the agent's purpose. - memory : AgentMemory, default=AgentMemory.disabled - Memory used to recall context before ``step`` runs and persist the - resulting context after ``*steps`` complete. Defaults to a no-op - memory scoped to a single turn. meta : Meta | MetaValues | None, default=None Additional metadata attached to the agent identity. @@ -343,25 +475,29 @@ def steps( Notes ----- - The wrapped execution recalls context via ``memory.recall_step``, - runs ``step`` and ``*steps``, then persists the resulting context via - ``memory.remember_step``, and filters out reasoning and tool protocol - chunks from the public output stream. + The wrapped execution seeds the pipeline context with the incoming + message as ``ModelInput``, runs ``step`` and ``*steps``, and filters + out reasoning and tool protocol chunks from the public output stream. + Memory is not applied implicitly - compose ``AgentMemory`` steps + (``prepare_step``, ``recall_step``, ``remember_step``) into the + pipeline where needed. ``generative`` and ``from_skill`` invoke + ``memory.prepare``, ``memory.recall``, and ``memory.remember`` + directly within their step bodies instead. """ async def execute( message: AgentMessage, ) -> AsyncIterable[MultimodalContentPart | ProcessingEvent]: async for chunk in Step.sequence( - memory.recall_step( - input=ModelInput.of( - message.content, - meta=message.meta, - ) - ), step, *steps, - memory.remember_step, + ).stream( + ( + ModelInput.of( + message.content, + meta=message.meta, + ), + ) ): if isinstance(chunk, ModelReasoningChunk): continue # skip reasoning @@ -453,15 +589,28 @@ def call( AsyncIterable[MultimodalContentPart | ProcessingEvent] Stream of output chunks emitted by the agent. """ - context: AgentThread = ctx.state( - AgentThread, - default=AgentThread.of(), - ) + current: AgentThread | None + if ctx.contains_state(AgentThread): + current = ctx.state(AgentThread) + + else: + current = None + + identifier: UUID + if thread is not None: + identifier = thread + + elif current is not None: + identifier = current.identifier + + else: + identifier = uuid4() + return self.respond( AgentMessage( - thread=thread if thread is not None else context.identifier, + thread=identifier, content=MultimodalContent.of(input), - meta=context.meta.merged_with(meta), + meta=current.meta.merged_with(meta) if current is not None else Meta.of(meta), ), ) @@ -485,6 +634,7 @@ async def respond( f"agent.{self.identity.name}", AgentThread.of( message.thread, + agent_uri=self.identity.uri, meta=message.meta, ), ): diff --git a/src/draive/agents/state.py b/src/draive/agents/state.py index 3ee2219b..73bb2217 100644 --- a/src/draive/agents/state.py +++ b/src/draive/agents/state.py @@ -6,12 +6,15 @@ from haiway import Disposable, Meta, MetaValues, State, ctx from draive.agents.types import ( + AgentException, + AgentMemoryPreparing, AgentMemoryRecalling, AgentMemoryRemembering, AgentThread, ) from draive.models import ModelContext -from draive.models.types import ModelInput +from draive.models.types import ModelInstructions +from draive.multimodal import Template, TemplatesRepository from draive.steps.state import StepState from draive.steps.step import Step, step @@ -20,18 +23,18 @@ @final class AgentMemory(State): - """Pluggable recall/remember behavior for agent execution. - - An ``AgentMemory`` instance wraps a pair of async callables - one - resolving the model context to use for a new turn (``recall``), and one - persisting the context produced after a turn completes (``remember``). - Both are scoped by ``AgentThread`` so a single memory instance can serve - multiple concurrent conversation threads. - - Each instance is intended to serve exactly one agent. State is scoped - per conversation thread only - not per agent - so sharing one instance - between multiple agents would mix their contexts within a thread. Create - a separate memory instance for each agent. + """Pluggable prepare/recall/remember behavior for agent execution. + + An ``AgentMemory`` instance wraps async callables - one preparing the + memory for a turn based on agent instructions (``prepare``), one + resolving the complete model context to use for a new turn (``recall``), + and one persisting the context produced after a turn completes + (``remember``). All are scoped by ``AgentThread`` - the executing agent + URI together with the conversation thread identifier - so a single memory + instance can serve multiple agents and concurrent conversation threads, + as long as the underlying implementation keys stored context by both. + Built-in implementations do; custom callables ignoring the executing + agent would mix contexts of different agents within a thread. Attributes ---------- @@ -49,66 +52,85 @@ def volatile( threads_limit: int | None = None, meta: Meta | MetaValues | None = None, ) -> Self: - """Create an in-process memory keyed by conversation thread. + """Create an in-process memory keyed by executing agent URI and thread. - Context is stored as the latest snapshot per thread: every remember - replaces the thread's context with the provided one and recall reads - it back whole. Agents may transform their context arbitrarily between - recall and remember (compaction, summarization, replacement) - - whatever is remembered becomes the next recalled context, exactly as - provided. Concurrent remembers within the same thread overwrite each - other; the last writer wins. + Context is stored as the latest snapshot per agent and thread: every + remember replaces the entry's context with the provided one and + recall reads it back whole. Agents may transform their context + arbitrarily between recall and remember (compaction, summarization, + replacement) - whatever is remembered becomes the next recalled + context, exactly as provided. Concurrent remembers within the same + agent and thread overwrite each other; the last writer wins. Parameters ---------- initial: ModelContext, default=() - Initial context for threads. + Initial context for new agent-thread entries. threads_limit : int | None, default=None - Maximum number of threads retained at once. When exceeded, the - least recently used thread is evicted together with its context. - ``None`` disables eviction - memory then grows unboundedly with - the number of threads for the lifetime of the process. + Maximum number of agent-thread entries retained at once. When + exceeded, the least recently prepared or remembered entry is + evicted together with its context. Preparing or remembering an + entry refreshes its recency; recalling it does not. ``None`` + disables eviction - memory then grows unboundedly with the number + of entries for the lifetime of the process. meta : Meta | MetaValues | None, default=None Additional metadata attached to the resulting memory instance. Returns ------- Self - A memory instance accumulating context per ``AgentThread`` in a - plain in-memory mapping. State is lost when the process exits and - is not shared across processes; intended for local development, - tests, and single-process deployments. + A memory instance accumulating context per executing agent URI + and conversation thread in a plain in-memory mapping. State is lost + when the process exits and is not shared across processes; + intended for local development, tests, and single-process + deployments. """ assert threads_limit is None or threads_limit > 0 # nosec: B101 - memory: OrderedDict[UUID, ModelContext] = OrderedDict() + initial_context: ModelContext = tuple(initial) + memory: OrderedDict[tuple[str, UUID], ModelContext] = OrderedDict() + + async def prepare( + thread: AgentThread, + instructions: ModelInstructions, + **extra: Any, + ) -> None: + key: tuple[str, UUID] = (thread.agent_uri, thread.identifier) + if key in memory: + memory.move_to_end(key) + + else: + memory[key] = initial_context + + while threads_limit is not None and len(memory) > threads_limit: + memory.popitem(last=False) async def recall( thread: AgentThread, - input: ModelInput, # noqa: A002 + context: ModelContext, **extra: Any, ) -> ModelContext: - recalled: ModelContext = memory.get(thread.identifier, initial) - if thread.identifier in memory: - memory.move_to_end(thread.identifier) - return (*recalled, input) + return (*memory.get((thread.agent_uri, thread.identifier), initial_context), *context) async def remember( thread: AgentThread, context: ModelContext, **extra: Any, ) -> None: - # the provided context replaces the thread snapshot as-is - memory[thread.identifier] = tuple(context) - memory.move_to_end(thread.identifier) + key: tuple[str, UUID] = (thread.agent_uri, thread.identifier) + memory[key] = tuple(context) + memory.move_to_end(key) + while threads_limit is not None and len(memory) > threads_limit: - memory.popitem(last=False) # evict least recently used thread + memory.popitem(last=False) return cls( + preparing=prepare, recalling=recall, remembering=remember, meta=Meta.of(meta), ) + _preparing: AgentMemoryPreparing _recalling: AgentMemoryRecalling _remembering: AgentMemoryRemembering meta: Meta @@ -117,9 +139,11 @@ def __init__( self, recalling: AgentMemoryRecalling, remembering: AgentMemoryRemembering, + preparing: AgentMemoryPreparing | None = None, meta: Meta = Meta.empty, ) -> None: super().__init__( + _preparing=preparing or _prepare, _recalling=recalling, _remembering=remembering, meta=meta, @@ -130,18 +154,20 @@ def with_ctx( *ctx_state: State, disposables: Collection[Disposable] = (), ) -> Self: - """Bind additional scoped context state and disposables to recall/remember. + """Bind additional scoped context state and disposables to memory operations. Parameters ---------- *ctx_state : State - State instances injected via ``ctx.updating`` for every recall and - remember call, including when invoked through ``recall_step`` and - ``remember_step``. + State instances injected via ``ctx.updating`` for every prepare, + recall, and remember call, including when invoked through + ``prepare_step``, ``recall_step``, and ``remember_step``. disposables : Collection[Disposable], default=() - Disposable resources entered for the duration of each recall and - remember call; any state instances they produce are also made - available in context. + Disposable resources entered for the duration of each prepare, + recall, and remember call; any state instances they produce are + also made available in context. The same instances are re-entered + for every operation, so they must support repeated use - one-shot + disposables would fail on the second memory operation. Returns ------- @@ -155,22 +181,36 @@ def with_ctx( session or model configuration) scoped to memory operations, without introducing global mutable state. Mirrors ``Step.with_ctx``. """ + preparing: AgentMemoryPreparing = self._preparing recalling: AgentMemoryRecalling = self._recalling remembering: AgentMemoryRemembering = self._remembering if not ctx_state and not disposables: return self # nothing to change... + async def prepare_with_ctx( + thread: AgentThread, + instructions: ModelInstructions, + **extra: Any, + ) -> None: + async with ctx.disposables(*disposables): + with ctx.updating(*ctx_state): + await preparing( + thread, + instructions, + **extra, + ) + async def recall_with_ctx( thread: AgentThread, - input: ModelInput, # noqa: A002 + context: ModelContext, **extra: Any, ) -> ModelContext: async with ctx.disposables(*disposables): with ctx.updating(*ctx_state): return await recalling( thread, - input, + context, **extra, ) @@ -188,26 +228,112 @@ async def remember_with_ctx( ) return self.__class__( + preparing=prepare_with_ctx, recalling=recall_with_ctx, remembering=remember_with_ctx, meta=self.meta, ) + async def prepare( + self, + thread: AgentThread, + instructions: ModelInstructions, + **extra: Any, + ) -> None: + """Prepare memory for a turn based on agent instructions. + + Called lazily before each recall. Implementations must be idempotent + per agent and thread - only the memory implementation knows whether + an agent-thread pair is already prepared. + + Parameters + ---------- + thread : AgentThread + Executing agent thread scope the preparation is applied to. + instructions : ModelInstructions + Resolved instructions of the agent utilizing the memory. + **extra : Any + Additional implementation-specific arguments forwarded to the + underlying prepare callable. + + Returns + ------- + None + Completes once the memory is prepared for the agent and thread. + """ + return await self._preparing( + thread, + instructions, + **extra, + ) + + def prepare_step( + self, + instructions: Template | ModelInstructions, + **extra: Any, + ) -> Step: + """Create a ``Step`` that prepares memory for the current turn. + + Parameters + ---------- + instructions : Template | ModelInstructions + Instructions of the agent utilizing the memory. ``Template`` + values are resolved through ``TemplatesRepository`` when the step + executes, independently of the completion using the same + instructions. + **extra : Any + Additional implementation-specific arguments forwarded to the + underlying prepare callable. + + Returns + ------- + Step + A step reading the active ``AgentThread`` from context, preparing + memory with the resolved instructions, and leaving state + unchanged. + + Raises + ------ + AgentException + Raised when the step executes without an ``AgentThread`` bound in + the current context. + """ + + @step + async def prepare( + state: StepState, + ) -> StepState: + resolved_instructions: str + if isinstance(instructions, Template): + resolved_instructions = await TemplatesRepository.resolve_str(instructions) + + else: + resolved_instructions = instructions + + await self._preparing( + _current_thread(), + resolved_instructions, + **extra, + ) + return state + + return prepare + async def recall( self, thread: AgentThread, - input: ModelInput, # noqa: A002 + context: ModelContext, **extra: Any, ) -> ModelContext: - """Resolve the model context to use for a new turn. + """Resolve the complete model context to use for a new turn. Parameters ---------- thread : AgentThread - Conversation thread the recalled context is scoped to. - input : ModelInput - Newly arrived input for the current turn, not yet part of any - stored context. + Executing agent thread scope the recalled context belongs to. + context : ModelContext + Context accumulated so far for the current turn, not yet part of + any stored context. **extra : Any Additional implementation-specific arguments forwarded to the underlying recall callable. @@ -215,27 +341,28 @@ async def recall( Returns ------- ModelContext - Context to use for the upcoming completion, typically prior - history with ``input`` appended. + Complete context to use for the upcoming completion, typically + stored history followed by the provided turn context. """ return await self._recalling( thread, - input, + context, **extra, ) def recall_step( self, - input: ModelInput, # noqa: A002 **extra: Any, ) -> Step: """Create a ``Step`` that replaces state context with recalled context. + The current ``StepState.context`` - the context accumulated so far + for the turn - is passed to ``recall`` and replaced with the result. + Running recall twice within one pipeline duplicates stored history; + that is a composition error. + Parameters ---------- - input : ModelInput - Newly arrived input for the current turn, forwarded to - ``recall``. **extra : Any Additional implementation-specific arguments forwarded to the underlying recall callable. @@ -245,6 +372,12 @@ def recall_step( Step A step reading the active ``AgentThread`` from context and replacing ``StepState.context`` with the recalled context. + + Raises + ------ + AgentException + Raised when the step executes without an ``AgentThread`` bound in + the current context. """ @step @@ -253,8 +386,8 @@ async def recall( ) -> StepState: return state.replacing_context( await self._recalling( - ctx.state(AgentThread), - input, + _current_thread(), + state.context, **extra, ) ) @@ -272,7 +405,7 @@ async def remember( Parameters ---------- thread : AgentThread - Conversation thread the persisted context is scoped to. + Executing agent thread scope the persisted context belongs to. context : ModelContext Full context accumulated for the turn, to be stored for future recall calls. @@ -291,16 +424,30 @@ async def remember( **extra, ) - @property - def remember_step(self) -> Step: + def remember_step( + self, + **extra: Any, + ) -> Step: """Create a ``Step`` that persists the current state context. + Parameters + ---------- + **extra : Any + Additional implementation-specific arguments forwarded to the + underlying remember callable. + Returns ------- Step A step reading the active ``AgentThread`` from context, passing the current ``StepState.context`` to ``remember``, and leaving state unchanged. + + Raises + ------ + AgentException + Raised when the step executes without an ``AgentThread`` bound in + the current context. """ @step @@ -308,8 +455,9 @@ async def remember( state: StepState, ) -> StepState: await self._remembering( - ctx.state(AgentThread), + _current_thread(), state.context, + **extra, ) return state @@ -317,12 +465,32 @@ async def remember( return remember +def _current_thread() -> AgentThread: + # absence is checked explicitly to raise a domain-specific error instead of + # whatever ctx.state raises for a type without defaultable fields + if not ctx.contains_state(AgentThread): + raise AgentException( + "AgentThread is not available in the current context - memory operations require" + " an active agent thread bound in scope, e.g. by running within an Agent." + ) + + return ctx.state(AgentThread) + + +async def _prepare( + thread: AgentThread, + instructions: ModelInstructions, + **extra: Any, +) -> None: + pass + + async def _recall( thread: AgentThread, - input: ModelInput, # noqa: A002 + context: ModelContext, **extra: Any, ) -> ModelContext: - return (input,) + return context async def _remember( @@ -334,6 +502,7 @@ async def _remember( AgentMemory.disabled = AgentMemory( + preparing=_prepare, recalling=_recall, remembering=_remember, ) diff --git a/src/draive/agents/types.py b/src/draive/agents/types.py index c280131b..4da518eb 100644 --- a/src/draive/agents/types.py +++ b/src/draive/agents/types.py @@ -5,7 +5,7 @@ from haiway import Default, Meta, MetaValues, State -from draive.models.types import ModelContext, ModelInput +from draive.models.types import ModelContext, ModelInstructions from draive.multimodal import Multimodal, MultimodalContent, MultimodalContentPart from draive.utils import ProcessingEvent @@ -13,6 +13,7 @@ "AgentException", "AgentExecuting", "AgentIdentity", + "AgentMemoryPreparing", "AgentMemoryRecalling", "AgentMemoryRemembering", "AgentMessage", @@ -153,12 +154,21 @@ def of( @final class AgentThread(State): - """Scoped runtime context shared across nested agent calls. + """Scoped runtime context of the executing agent within a conversation thread. + + The ``identifier`` names the conversation thread - a connected series of + messages shared across nested agent calls - while ``agent_uri`` marks + which agent is currently executing within it. ``Agent.respond`` binds a + thread stamped with its own agent URI in scope for the duration of + message handling; nested agent calls rebind it with the same identifier + and their own URI. Memory operations are scoped by both. Parameters ---------- identifier : UUID Thread identifier propagated through nested calls. + agent_uri : str + URI of the agent currently executing within the thread. created : datetime UTC timestamp recording when the thread context was created. meta : Meta @@ -168,16 +178,20 @@ class AgentThread(State): @classmethod def of( cls, - identifier: UUID | None = None, + identifier: UUID, *, + agent_uri: str, meta: Meta | MetaValues | None = None, ) -> Self: """Create an agent thread execution context. Parameters ---------- - identifier : UUID | None, default=None - Conversation thread identifier. When omitted, a new thread is created. + identifier : UUID + Conversation thread identifier. + agent_uri : str + URI of the agent executing within the thread. Agents stamp their + own URI when binding the thread in scope. meta : Meta | MetaValues | None, default=None Metadata propagated through the active context scope. @@ -187,11 +201,13 @@ def of( New immutable agent context instance. """ return cls( - identifier=identifier if identifier is not None else uuid4(), + identifier=identifier, + agent_uri=agent_uri, meta=Meta.of(meta), ) - identifier: UUID = Default(default_factory=uuid4) + identifier: UUID + agent_uri: str created: datetime = Default(default_factory=lambda: datetime.now(UTC)) meta: Meta = Meta.empty @@ -225,6 +241,50 @@ def __call__( ... +@runtime_checkable +class AgentMemoryPreparing(Protocol): + """Runtime contract implemented by ``AgentMemory`` prepare callables. + + Called lazily before each recall within a turn. Implementations must be + idempotent per agent and thread - preparation may run once per turn and + only the memory implementation knows whether an agent-thread pair is + already prepared. + + Returns + ------- + None + Completes once the memory is prepared for the agent and thread. + """ + + async def __call__( + self, + thread: AgentThread, + instructions: ModelInstructions, + **extra: Any, + ) -> None: + """Prepare memory for the agent based on its instructions. + + Parameters + ---------- + thread : AgentThread + Executing agent thread scope the preparation is applied to - + implementations key state by the executing agent (``agent_uri``) + together with the thread ``identifier``. + instructions : ModelInstructions + Instructions of the agent utilizing the memory, resolved for the + current turn. Resolution happens independently of the completion + using the same instructions. + **extra : Any + Additional implementation-specific arguments. + + Returns + ------- + None + Completes once the memory is prepared for the agent and thread. + """ + ... + + @runtime_checkable class AgentMemoryRecalling(Protocol): """Runtime contract implemented by ``AgentMemory`` recall callables. @@ -232,32 +292,36 @@ class AgentMemoryRecalling(Protocol): Returns ------- ModelContext - Context to use for the upcoming completion, typically prior history - with ``input`` appended. + Complete context to use for the upcoming completion, typically stored + history followed by the provided turn context. """ async def __call__( self, thread: AgentThread, - input: ModelInput, # noqa: A002 + context: ModelContext, **extra: Any, ) -> ModelContext: - """Resolve the model context to use for a new turn. + """Resolve the complete model context to use for a new turn. Parameters ---------- thread : AgentThread - Conversation thread the recalled context is scoped to. - input : ModelInput - Newly arrived input for the current turn, not yet part of any - stored context. + Executing agent thread scope the recalled context belongs to - + implementations key stored context by the executing agent + (``agent_uri``) together with the thread ``identifier``. + context : ModelContext + Context accumulated so far for the current turn, not yet part of + any stored context. Implementations must not assume it is a + single input - preceding steps may have accumulated more. **extra : Any Additional implementation-specific arguments. Returns ------- ModelContext - Context to use for the upcoming completion. + Complete context to use for the upcoming completion, typically + stored history followed by the provided turn context. """ ... @@ -283,7 +347,9 @@ async def __call__( Parameters ---------- thread : AgentThread - Conversation thread the persisted context is scoped to. + Executing agent thread scope the persisted context belongs to - + implementations key stored context by the executing agent + (``agent_uri``) together with the thread ``identifier``. context : ModelContext Full context accumulated for the turn. **extra : Any diff --git a/src/draive/anthropic/config.py b/src/draive/anthropic/config.py index 92867c24..320db74d 100644 --- a/src/draive/anthropic/config.py +++ b/src/draive/anthropic/config.py @@ -9,5 +9,5 @@ class AnthropicConfig(Configuration): model: str temperature: float | Missing = MISSING max_output_tokens: int = 2048 - thinking_budget: int | None | Missing = MISSING + thinking_budget: int | Missing | None = MISSING stop_sequences: Sequence[str] | Missing = MISSING diff --git a/src/draive/anthropic/messages.py b/src/draive/anthropic/messages.py index 50d425e9..803a3f39 100644 --- a/src/draive/anthropic/messages.py +++ b/src/draive/anthropic/messages.py @@ -539,7 +539,7 @@ def _content_elements( def _thinking_budget_config( - budget: int | None | Missing, + budget: int | Missing | None, ) -> ThinkingConfigParam | Omit: if budget is MISSING: return omit diff --git a/src/draive/models/__init__.py b/src/draive/models/__init__.py index 59777318..5b94dd21 100644 --- a/src/draive/models/__init__.py +++ b/src/draive/models/__init__.py @@ -68,7 +68,6 @@ "ModelOutputBlock", "ModelOutputBlocks", "ModelOutputChunk", - "ModelOutputChunk", "ModelOutputFailed", "ModelOutputInvalid", "ModelOutputLimit", @@ -96,7 +95,6 @@ "ModelToolRequest", "ModelToolResponse", "ModelToolSpecification", - "ModelToolSpecification", "ModelToolStatus", "ModelTools", "ModelToolsSelection", diff --git a/src/draive/ollama/config.py b/src/draive/ollama/config.py index 3880c59e..9203e332 100644 --- a/src/draive/ollama/config.py +++ b/src/draive/ollama/config.py @@ -13,7 +13,7 @@ class OllamaChatConfig(Configuration): temperature: float | Missing = MISSING top_k: int | Missing = MISSING top_p: float | Missing = MISSING - seed: int | None | Missing = MISSING + seed: int | Missing | None = MISSING max_output_tokens: int | Missing = MISSING stop_sequences: Sequence[str] | Missing = MISSING diff --git a/src/draive/postgres/agent_memory.py b/src/draive/postgres/agent_memory.py index f78bbbd0..02dfcbe6 100644 --- a/src/draive/postgres/agent_memory.py +++ b/src/draive/postgres/agent_memory.py @@ -5,7 +5,7 @@ from haiway import Meta, MetaValues, ctx from haiway.postgres import Postgres, PostgresConnection, PostgresRow -from draive.agents import AgentIdentity, AgentMemory, AgentThread +from draive.agents import AgentMemory, AgentThread from draive.models import ModelContext, ModelContextElement, ModelInput, ModelOutput __all__ = ("PostgresAgentMemory",) @@ -16,9 +16,10 @@ class PostgresAgentMemory: """PostgreSQL-backed agent memory. This utility exposes static helpers for schema migration and creating - agent-scoped :class:`~draive.agents.state.AgentMemory` instances persisted - in PostgreSQL, keyed by the owning agent identity and the active - conversation thread. + :class:`~draive.agents.state.AgentMemory` instances persisted in + PostgreSQL, keyed by the executing agent URI and thread identifier + carried on the ``AgentThread`` passed to each memory operation. A single + instance can serve multiple agents. Context is stored as immutable snapshots: every remember inserts the full context as a new snapshot row and recall reads back the latest one whole. @@ -28,14 +29,15 @@ class PostgresAgentMemory: Previous snapshots are never modified or deleted and remain available for tracking and verification. Persistence is lock-free and write-only: concurrent remembers within the same thread each store their own - snapshot and the one persisted last wins on recall. Snapshot history - grows without bound over the lifetime of a thread. + snapshot and recall picks the latest one by creation timestamp - + recorded at statement execution time with microsecond resolution, with + the snapshot identifier as a stable tie-break. Snapshot history grows + without bound over the lifetime of a thread. Examples -------- ```python from draive import ctx - from draive.agents import AgentIdentity from haiway.postgres import Postgres from draive.postgres.agent_memory import PostgresAgentMemory @@ -48,9 +50,7 @@ async def bootstrap_memory() -> None: with ctx.updating(connection): await PostgresAgentMemory.migrate() - memory = PostgresAgentMemory.prepare( - AgentIdentity.of(name="assistant"), - ) + memory = PostgresAgentMemory.instance() ``` """ @@ -96,24 +96,26 @@ async def migrate() -> None: ON agent_memory ( agent_uri, thread_id, - created DESC + created DESC, + identifier DESC ); """ ) @staticmethod - def prepare( - identity: AgentIdentity, + def instance( *, meta: Meta | MetaValues | None = None, ) -> AgentMemory: - """Prepare agent-scoped memory operations backed by PostgreSQL. + """Create memory operations backed by PostgreSQL. + + The executing agent is resolved per operation from the provided + ``AgentThread``, so a single memory instance can serve multiple + agents - recalled and remembered context is isolated per agent URI + and thread. Parameters ---------- - identity : AgentIdentity - Identity of the agent owning the persisted memory. Recalled and - remembered context is isolated per agent URI. meta : Meta | MetaValues | None, default=None Additional metadata attached to the resulting memory instance. @@ -121,26 +123,25 @@ def prepare( ------- AgentMemory A configured agent memory instance with recall and remember - handlers bound to the provided agent identity. + handlers scoped by the executing agent context. Raises ------ Exception Raised by memory operations when PostgreSQL interactions fail. """ - agent_uri: str = identity.uri async def recall( thread: AgentThread, - input: ModelInput, # noqa: A002 + context: ModelContext, **extra: Any, ) -> ModelContext: return ( *await _recall( - agent_uri=agent_uri, + agent_uri=thread.agent_uri, thread_id=thread.identifier, ), - input, + *context, ) async def remember( @@ -149,7 +150,7 @@ async def remember( **extra: Any, ) -> None: await _remember( - agent_uri=agent_uri, + agent_uri=thread.agent_uri, thread_id=thread.identifier, context=context, ) @@ -227,7 +228,8 @@ async def _remember( context: ModelContext, ) -> None: # write-only: a new snapshot is inserted as-is, previous snapshots stay - # untouched for tracking and verification; the latest snapshot wins on recall + # untouched for tracking and verification; recall picks the latest snapshot + # by creation timestamp, with the identifier as a stable tie-break await Postgres.execute( """ INSERT INTO @@ -244,7 +246,7 @@ async def _remember( $2::UUID, $3::UUID, $4::JSONB, - CURRENT_TIMESTAMP + clock_timestamp() ); """, # nosec: B608 agent_uri, diff --git a/tests/test_agent_memory.py b/tests/test_agent_memory.py index 01c1ccd7..b63829d0 100644 --- a/tests/test_agent_memory.py +++ b/tests/test_agent_memory.py @@ -4,16 +4,21 @@ import pytest from haiway import State, ctx -from draive.agents import AgentMemory, AgentThread +from draive import Agent +from draive.agents import AgentException, AgentMemory, AgentThread from draive.models import ModelContext, ModelInput, ModelOutput from draive.multimodal import MultimodalContent -from draive.steps import StepState +from draive.steps import StepState, step class _MarkerState(State): label: str +def _thread(agent_uri: str = "agent://agent") -> AgentThread: + return AgentThread.of(uuid4(), agent_uri=agent_uri) + + async def _noop_remembering( thread: AgentThread, context: ModelContext, @@ -26,14 +31,22 @@ async def _noop_remembering( async def test_agent_memory_with_ctx_injects_state_into_recall_and_remember() -> None: captured: dict[str, str] = {} + async def preparing( + thread: AgentThread, + instructions: str, + **extra: object, + ) -> None: + _ = (thread, instructions, extra) + captured["prepare_marker"] = ctx.state(_MarkerState).label + async def recalling( thread: AgentThread, - input: ModelInput, # noqa: A002 + context: ModelContext, **extra: object, ) -> ModelContext: _ = (thread, extra) captured["recall_marker"] = ctx.state(_MarkerState).label - return (input,) + return context async def remembering( thread: AgentThread, @@ -43,47 +56,103 @@ async def remembering( _ = (thread, context, extra) captured["remember_marker"] = ctx.state(_MarkerState).label - memory = AgentMemory(recalling=recalling, remembering=remembering).with_ctx( - _MarkerState(label="injected") - ) + memory = AgentMemory( + recalling=recalling, + remembering=remembering, + preparing=preparing, + ).with_ctx(_MarkerState(label="injected")) - thread = AgentThread.of(uuid4()) + thread = _thread() context: ModelContext = (ModelInput.of(MultimodalContent.of("hi")),) async with ctx.scope("test"): - recalled = await memory.recall(thread=thread, input=context[0]) + await memory.prepare(thread=thread, instructions="instructions") + recalled = await memory.recall(thread=thread, context=context) await memory.remember(thread=thread, context=context) assert recalled == context + assert captured["prepare_marker"] == "injected" assert captured["recall_marker"] == "injected" assert captured["remember_marker"] == "injected" @pytest.mark.asyncio -async def test_agent_memory_with_ctx_applies_to_step_properties_too() -> None: +async def test_agent_memory_with_ctx_applies_to_steps_too() -> None: captured: dict[str, str] = {} async def recalling( thread: AgentThread, - input: ModelInput, # noqa: A002 + context: ModelContext, **extra: object, ) -> ModelContext: _ = (thread, extra) captured["marker"] = ctx.state(_MarkerState).label - return (input,) + return context memory = AgentMemory(recalling=recalling, remembering=_noop_remembering).with_ctx( _MarkerState(label="from-step") ) - thread = AgentThread.of(uuid4()) - model_input = ModelInput.of(MultimodalContent.of("hi")) + state = StepState.of((ModelInput.of(MultimodalContent.of("hi")),)) + + async with ctx.scope("test", _thread()): + await memory.recall_step().process(state) + + assert captured["marker"] == "from-step" + + +@pytest.mark.asyncio +async def test_agent_memory_prepare_step_passes_thread_and_instructions() -> None: + captured: dict[str, object] = {} + + async def preparing( + thread: AgentThread, + instructions: str, + **extra: object, + ) -> None: + _ = extra + captured["thread"] = thread + captured["instructions"] = instructions + + async def recalling( + thread: AgentThread, + context: ModelContext, + **extra: object, + ) -> ModelContext: + _ = (thread, extra) + return context + + memory = AgentMemory( + recalling=recalling, + remembering=_noop_remembering, + preparing=preparing, + ) + + thread = _thread() state = StepState.of(()) async with ctx.scope("test", thread): - await memory.recall_step(input=model_input).process(state) + result = await memory.prepare_step("agent instructions").process(state) - assert captured["marker"] == "from-step" + assert result == state # state passes through unchanged + assert captured["thread"] == thread + assert captured["instructions"] == "agent instructions" + + +@pytest.mark.asyncio +async def test_agent_memory_prepare_defaults_to_noop() -> None: + async def recalling( + thread: AgentThread, + context: ModelContext, + **extra: object, + ) -> ModelContext: + _ = (thread, extra) + return context + + memory = AgentMemory(recalling=recalling, remembering=_noop_remembering) + + # no preparing provided - prepare completes without effect + await memory.prepare(thread=_thread(), instructions="anything") @pytest.mark.asyncio @@ -105,21 +174,22 @@ async def __aexit__( async def recalling( thread: AgentThread, - input: ModelInput, # noqa: A002 + context: ModelContext, **extra: object, ) -> ModelContext: _ = (thread, extra) events.append(f"recall:{ctx.state(_MarkerState).label}") - return (input,) + return context memory = AgentMemory(recalling=recalling, remembering=_noop_remembering).with_ctx( disposables=(_FakeDisposable(),) ) - thread = AgentThread.of(uuid4()) - async with ctx.scope("test"): - await memory.recall(thread=thread, input=ModelInput.of(MultimodalContent.of("hi"))) + await memory.recall( + thread=_thread(), + context=(ModelInput.of(MultimodalContent.of("hi")),), + ) assert events == ["enter", "recall:disposed", "exit"] @@ -133,17 +203,17 @@ def test_agent_memory_with_ctx_without_arguments_returns_self() -> None: @pytest.mark.asyncio async def test_agent_memory_volatile_accumulates_context_across_turns() -> None: memory = AgentMemory.volatile() - thread = AgentThread.of(uuid4()) + thread = _thread() first_input = ModelInput.of(MultimodalContent.of("first")) - first_recalled = await memory.recall(thread=thread, input=first_input) + first_recalled = await memory.recall(thread=thread, context=(first_input,)) assert first_recalled == (first_input,) first_output = ModelOutput.of(MultimodalContent.of("second")) await memory.remember(thread=thread, context=(*first_recalled, first_output)) second_input = ModelInput.of(MultimodalContent.of("third")) - second_recalled = await memory.recall(thread=thread, input=second_input) + second_recalled = await memory.recall(thread=thread, context=(second_input,)) assert len(second_recalled) == 3 assert second_recalled[0].content.to_str() == "first" assert second_recalled[1].content.to_str() == "second" @@ -154,7 +224,7 @@ async def test_agent_memory_volatile_accumulates_context_across_turns() -> None: third_recalled = await memory.recall( thread=thread, - input=ModelInput.of(MultimodalContent.of("fifth")), + context=(ModelInput.of(MultimodalContent.of("fifth")),), ) assert [element.content.to_str() for element in third_recalled] == [ "first", @@ -172,10 +242,25 @@ def test_agent_memory_volatile_accepts_initial_keyword() -> None: assert isinstance(memory, AgentMemory) +@pytest.mark.asyncio +async def test_agent_memory_volatile_snapshots_initial_context() -> None: + initial = [ModelInput.of(MultimodalContent.of("initial"))] + memory = AgentMemory.volatile(initial=initial) + initial.clear() + + incoming = ModelInput.of(MultimodalContent.of("incoming")) + recalled = await memory.recall( + thread=_thread(), + context=(incoming,), + ) + + assert [element.content.to_str() for element in recalled] == ["initial", "incoming"] + + @pytest.mark.asyncio async def test_agent_memory_volatile_stores_remembered_context_as_snapshot() -> None: memory = AgentMemory.volatile() - thread = AgentThread.of(uuid4()) + thread = _thread() await memory.remember( thread=thread, @@ -191,35 +276,113 @@ async def test_agent_memory_volatile_stores_remembered_context_as_snapshot() -> await memory.remember(thread=thread, context=(summary_input,)) incoming = ModelInput.of(MultimodalContent.of("next")) - assert await memory.recall(thread=thread, input=incoming) == (summary_input, incoming) + assert await memory.recall(thread=thread, context=(incoming,)) == (summary_input, incoming) @pytest.mark.asyncio -async def test_agent_memory_volatile_evicts_least_recently_used_threads() -> None: +async def test_agent_memory_volatile_isolates_agents_within_thread() -> None: + memory = AgentMemory.volatile() + identifier = uuid4() + first_thread = AgentThread.of(identifier, agent_uri="agent://first") + second_thread = AgentThread.of(identifier, agent_uri="agent://second") + + await memory.remember( + thread=first_thread, + context=(ModelInput.of(MultimodalContent.of("first agent history")),), + ) + + # the second agent shares the thread yet recalls no foreign context + incoming = ModelInput.of(MultimodalContent.of("hello")) + assert await memory.recall(thread=second_thread, context=(incoming,)) == (incoming,) + + recalled = await memory.recall(thread=first_thread, context=(incoming,)) + assert [element.content.to_str() for element in recalled] == [ + "first agent history", + "hello", + ] + + +@pytest.mark.asyncio +async def test_agent_memory_volatile_prepare_refreshes_entry_recency() -> None: memory = AgentMemory.volatile(threads_limit=2) - first_thread = AgentThread.of(uuid4()) - second_thread = AgentThread.of(uuid4()) - third_thread = AgentThread.of(uuid4()) + first_thread = _thread() + second_thread = _thread() - for thread in (first_thread, second_thread, third_thread): + for thread in (first_thread, second_thread): await memory.remember( thread=thread, context=(ModelInput.of(MultimodalContent.of("stored")),), ) - # first thread exceeded the limit and was evicted - first_input = ModelInput.of(MultimodalContent.of("fresh")) - assert await memory.recall(thread=first_thread, input=first_input) == (first_input,) - - # recalling the second thread marks it as recently used... - second_input = ModelInput.of(MultimodalContent.of("next")) - assert len(await memory.recall(thread=second_thread, input=second_input)) == 2 + # preparing the first entry marks it as recently used... + await memory.prepare(thread=first_thread, instructions="instructions") - # ...so remembering a new thread evicts the third one instead + # ...so remembering a new entry evicts the second one instead await memory.remember( - thread=AgentThread.of(uuid4()), + thread=_thread(), context=(ModelInput.of(MultimodalContent.of("stored")),), ) - third_input = ModelInput.of(MultimodalContent.of("fresh")) - assert await memory.recall(thread=third_thread, input=third_input) == (third_input,) - assert len(await memory.recall(thread=second_thread, input=second_input)) == 2 + first_input = ModelInput.of(MultimodalContent.of("next")) + assert len(await memory.recall(thread=first_thread, context=(first_input,))) == 2 + second_input = ModelInput.of(MultimodalContent.of("fresh")) + assert await memory.recall(thread=second_thread, context=(second_input,)) == (second_input,) + + +@pytest.mark.asyncio +async def test_agent_steps_with_memory_persist_context_across_calls() -> None: + memory = AgentMemory.volatile() + + @step + async def reply( + state: StepState, + ) -> StepState: + return state.appending_context(ModelOutput.of(MultimodalContent.of("reply"))) + + agent = Agent.steps( + memory.recall_step(), + reply, + memory.remember_step(), + agent="worker", + ) + thread = uuid4() + + async with ctx.scope("test.agent.memory"): + _ = [chunk async for chunk in agent.call(thread=thread, input="first")] + _ = [chunk async for chunk in agent.call(thread=thread, input="second")] + + # context is stored under the executing agent's URI stamped by respond + stored = await memory.recall( + thread=AgentThread.of(thread, agent_uri=agent.identity.uri), + context=(), + ) + assert [element.content.to_str() for element in stored] == [ + "first", + "reply", + "second", + "reply", + ] + + # a different agent URI on the same thread sees no stored context + assert ( + await memory.recall( + thread=AgentThread.of(thread, agent_uri="agent://other"), + context=(), + ) + == () + ) + + +@pytest.mark.asyncio +async def test_agent_memory_steps_require_agent_thread_in_scope() -> None: + memory = AgentMemory.volatile() + state = StepState.of(()) + + async with ctx.scope("test"): # no AgentThread bound in scope + with pytest.raises(AgentException): + await memory.prepare_step("instructions").process(state) + + with pytest.raises(AgentException): + await memory.recall_step().process(state) + + with pytest.raises(AgentException): + await memory.remember_step().process(state) diff --git a/tests/test_agents.py b/tests/test_agents.py index 92b918c4..05b83a92 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -82,7 +82,10 @@ async def execute( ) meta = Meta.of({"source": "outer"}) - async with ctx.scope("test.agent.call", AgentThread.of(identifier=thread, meta=meta)): + async with ctx.scope( + "test.agent.call", + AgentThread.of(identifier=thread, agent_uri="agent://outer", meta=meta), + ): chunks = [chunk async for chunk in agent.call(input="hello")] assert _multimodal_text_of(*chunks) == "hello" @@ -115,7 +118,11 @@ async def execute( async with ctx.scope( "test.agent.call.override", - AgentThread.of(identifier=outer_thread, meta={"source": "outer", "scope": "root"}), + AgentThread.of( + identifier=outer_thread, + agent_uri="agent://outer", + meta={"source": "outer", "scope": "root"}, + ), ): chunks = [ chunk diff --git a/tests/test_postgres_agent_memory.py b/tests/test_postgres_agent_memory.py index 4d03b99b..674f5521 100644 --- a/tests/test_postgres_agent_memory.py +++ b/tests/test_postgres_agent_memory.py @@ -6,7 +6,7 @@ import pytest import draive.postgres.agent_memory as postgres_agent_memory -from draive.agents import AgentIdentity, AgentMemory, AgentThread +from draive.agents import AgentMemory, AgentThread from draive.models import ModelContext, ModelInput, ModelOutput from draive.multimodal import MultimodalContent from draive.postgres.agent_memory import PostgresAgentMemory @@ -35,12 +35,15 @@ def _snapshot_row(context: ModelContext) -> _FakeSnapshotRow: return _FakeSnapshotRow(context=f"[{','.join(element.to_json() for element in context)}]") +def _thread(agent_uri: str = "agent://assistant") -> AgentThread: + return AgentThread.of(uuid4(), agent_uri=agent_uri) + + @pytest.mark.asyncio async def test_postgres_agent_memory_recall_returns_latest_snapshot_with_input( monkeypatch: pytest.MonkeyPatch, ) -> None: - identity = AgentIdentity.of(name="assistant") - thread = AgentThread.of(uuid4()) + thread = _thread() stored_context = ( ModelInput.of(MultimodalContent.of("first")), ModelOutput.of(MultimodalContent.of("second")), @@ -53,15 +56,15 @@ async def fake_fetch_one( ) -> _FakeSnapshotRow: assert "ORDER BY" in statement assert "LIMIT 1" in statement - assert args == (identity.uri, thread.identifier) + assert args == (thread.agent_uri, thread.identifier) return _snapshot_row(stored_context) monkeypatch.setattr(postgres_agent_memory.Postgres, "fetch_one", fake_fetch_one) - memory: AgentMemory = PostgresAgentMemory.prepare(identity) + memory: AgentMemory = PostgresAgentMemory.instance() incoming = ModelInput.of(MultimodalContent.of("third")) - recalled = await memory.recall(thread=thread, input=incoming) + recalled = await memory.recall(thread=thread, context=(incoming,)) assert [element.content.to_str() for element in recalled] == ["first", "second", "third"] assert isinstance(recalled[0], ModelInput) @@ -75,9 +78,6 @@ async def fake_fetch_one( async def test_postgres_agent_memory_recall_of_empty_thread_returns_input_only( monkeypatch: pytest.MonkeyPatch, ) -> None: - identity = AgentIdentity.of(name="assistant") - thread = AgentThread.of(uuid4()) - async def fake_fetch_one( statement: str, /, @@ -87,18 +87,17 @@ async def fake_fetch_one( monkeypatch.setattr(postgres_agent_memory.Postgres, "fetch_one", fake_fetch_one) - memory: AgentMemory = PostgresAgentMemory.prepare(identity) + memory: AgentMemory = PostgresAgentMemory.instance() incoming = ModelInput.of(MultimodalContent.of("hello")) - assert await memory.recall(thread=thread, input=incoming) == (incoming,) + assert await memory.recall(thread=_thread(), context=(incoming,)) == (incoming,) @pytest.mark.asyncio async def test_postgres_agent_memory_remember_inserts_snapshot_write_only( monkeypatch: pytest.MonkeyPatch, ) -> None: - identity = AgentIdentity.of(name="assistant") - thread = AgentThread.of(uuid4()) + thread = _thread() fetched: list[tuple[str, tuple[object, ...]]] = [] executed: list[tuple[str, tuple[object, ...]]] = [] @@ -119,7 +118,7 @@ async def fake_execute( monkeypatch.setattr(postgres_agent_memory.Postgres, "fetch_one", fake_fetch_one) monkeypatch.setattr(postgres_agent_memory.Postgres, "execute", fake_execute) - memory: AgentMemory = PostgresAgentMemory.prepare(identity) + memory: AgentMemory = PostgresAgentMemory.instance() context = ( ModelInput.of(MultimodalContent.of("hello")), ModelOutput.of(MultimodalContent.of("world")), @@ -135,7 +134,7 @@ async def fake_execute( assert "DELETE" not in insert_statement assert "pg_advisory" not in insert_statement - assert insert_args[0] == identity.uri + assert insert_args[0] == thread.agent_uri assert insert_args[1] == thread.identifier assert isinstance(insert_args[2], UUID) @@ -149,8 +148,7 @@ async def fake_execute( async def test_postgres_agent_memory_remember_accepts_disjoint_context( monkeypatch: pytest.MonkeyPatch, ) -> None: - identity = AgentIdentity.of(name="assistant") - thread = AgentThread.of(uuid4()) + thread = _thread() snapshots: list[str] = [] async def fake_fetch_one( @@ -172,7 +170,7 @@ async def fake_execute( monkeypatch.setattr(postgres_agent_memory.Postgres, "fetch_one", fake_fetch_one) monkeypatch.setattr(postgres_agent_memory.Postgres, "execute", fake_execute) - memory: AgentMemory = PostgresAgentMemory.prepare(identity) + memory: AgentMemory = PostgresAgentMemory.instance() await memory.remember( thread=thread, context=( @@ -188,7 +186,7 @@ async def fake_execute( # both snapshots retained; the latest one is recalled whole, as provided assert len(snapshots) == 2 incoming = ModelInput.of(MultimodalContent.of("next")) - recalled = await memory.recall(thread=thread, input=incoming) + recalled = await memory.recall(thread=thread, context=(incoming,)) assert recalled == (*summary, incoming) diff --git a/uv.lock b/uv.lock index d03c95fe..a2c92e83 100644 --- a/uv.lock +++ b/uv.lock @@ -13,7 +13,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.14.2" +version = "3.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -24,49 +24,49 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/cc/58f26f118d8099f84e009ce560b9148a3f803e63fa8473b57feb67241875/aiohttp-3.14.2.tar.gz", hash = "sha256:f96821eb2ae2f12b0dfa799eafbf221f5621a9220b457b4744a269a63a5f3a6c", size = 7969860, upload-time = "2026-07-20T19:53:26.881Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/ca/716f720dccc3032e40dc80d0ab2d87a7365270a714976a85bdff73bc7254/aiohttp-3.14.2-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:7719cef2a9dc5e10cd5f476ec1744b25c5ac4da733a9a687d91c42de7d4afe30", size = 508899, upload-time = "2026-07-20T19:51:54.165Z" }, - { url = "https://files.pythonhosted.org/packages/f5/80/786166589e71b3f3cfce56b414170fe5008462d0bff100bd8837285632f6/aiohttp-3.14.2-cp314-cp314-android_24_x86_64.whl", hash = "sha256:3523ec0cc524a413699f25ec8340f3da368484bc9d5f2a1bf87f233ac20599bf", size = 514722, upload-time = "2026-07-20T19:51:56.208Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b7/539961cf3e3d3f179ea4b20d456cbdb67d1163c68f2e95bf220438a5afd5/aiohttp-3.14.2-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:c8ab295ee58332ef8fbd62727df90540836dfcf7a61f545d0f2771223b80bf25", size = 488082, upload-time = "2026-07-20T19:51:58.293Z" }, - { url = "https://files.pythonhosted.org/packages/60/34/c7708eac8edacca9c049a0d6f5fa610005ac3c5634c9ea4751134263bf4e/aiohttp-3.14.2-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:71501bc03ede681401269c569e6f9306c761c1c7d4296675e8e78dd07147070f", size = 494140, upload-time = "2026-07-20T19:52:00.158Z" }, - { url = "https://files.pythonhosted.org/packages/d8/e1/076b88de36b4ebea62d1c0b225e32bc8a452ee71b2aded82878fa5fa7c9e/aiohttp-3.14.2-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:052478c7d01035d805302db50c2ef626b1c1ba0fe2f6d4a22ae6eaeb43bf2316", size = 502670, upload-time = "2026-07-20T19:52:02.097Z" }, - { url = "https://files.pythonhosted.org/packages/a9/71/7b7720ffe7e521aaa79a853ebff4c17937f4b5a3a586e9d13b0f38050b35/aiohttp-3.14.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b0d49be9d9a210b2c993bf32b1eda03f949f7bcda68fc4f718ae8085ae3fb4b8", size = 756406, upload-time = "2026-07-20T19:52:04.082Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9a/c67c7e22fff7d7b17387e718451b30eb89f55df6b8d13d2d7f91daba6505/aiohttp-3.14.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5fe25c4c44ea5b56fd4512e2065e09384987fc8cc98e41bc8749efe12f653abb", size = 510106, upload-time = "2026-07-20T19:52:06.001Z" }, - { url = "https://files.pythonhosted.org/packages/27/2a/3caebd640229663263585d4d31898331a3937752f2e1f5ec0af509fa31be/aiohttp-3.14.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7e254b0d636957174a03ca210289e867a62bb9502081e1b44a8c2bb1f6266ecd", size = 512876, upload-time = "2026-07-20T19:52:08.036Z" }, - { url = "https://files.pythonhosted.org/packages/27/04/86945210e491493f5cd025efe5e6d8fc8a9e8aaf36f943c40f52c34eda7e/aiohttp-3.14.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6b0ce033d49dd3c6a2566b387e322a9f9029110d67902f0d64571c0fd4b73d8", size = 1750050, upload-time = "2026-07-20T19:52:09.998Z" }, - { url = "https://files.pythonhosted.org/packages/11/d8/356c62552b4db60af5ad8cddeafa6f837788918201f1b5f2466565c986b5/aiohttp-3.14.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41b5b66b1ac2c48b61e420691eb9741d17d9068f2bc23b5ee3e750faa564bc8f", size = 1707177, upload-time = "2026-07-20T19:52:12.39Z" }, - { url = "https://files.pythonhosted.org/packages/77/2d/41bb20bed3df1d6dc7fc231ca4ffb72177fd151bf4205a4ade7a93e03501/aiohttp-3.14.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30a5ed81f752f182961237414a3cd0af209c0f74f06d66f66f9fcb8964f4978d", size = 1803795, upload-time = "2026-07-20T19:52:14.705Z" }, - { url = "https://files.pythonhosted.org/packages/63/21/ad9fe6786a1d2758bcfdae4dbc1d6869ffc7b1e0571530a508de3cdde09b/aiohttp-3.14.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b9251f43d78ff675c0ddfcd53ba61abecc1f74eedc6287bb6657f6c6a033fe7", size = 1876539, upload-time = "2026-07-20T19:52:16.965Z" }, - { url = "https://files.pythonhosted.org/packages/0e/cb/d213f4fd7af279d82c4b46d30de22db93a01296d77a8d8e535289637da54/aiohttp-3.14.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf7930e83a12801b2e253d41cc8bf5553f61c0cfabef182a72ae13472cc81803", size = 1761130, upload-time = "2026-07-20T19:52:19.127Z" }, - { url = "https://files.pythonhosted.org/packages/46/4f/0792fe1a24c2b4b506d07350e980b0626c149fe73e68dd37dfb30ed89813/aiohttp-3.14.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:abb33120daba5e5643a757790ece44d638a5a11eb0598312e6e7ec2f1bd1a5a3", size = 1583576, upload-time = "2026-07-20T19:52:21.465Z" }, - { url = "https://files.pythonhosted.org/packages/46/ad/551c302f534f9cd6105bd16902e69dc64c726aa729127203f47ed4bddb2a/aiohttp-3.14.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:983a68048a48f35ed08aadfcc1ba55de9a121aa91be48a764965c9ec532b94b5", size = 1714139, upload-time = "2026-07-20T19:52:23.636Z" }, - { url = "https://files.pythonhosted.org/packages/e3/cf/a3be8601d2e80ccf7bd4f79807629cede5425d72c02636f06b1ad6a8290a/aiohttp-3.14.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:fef094bfc2f4e991a998af066fc6e3956a409ef799f5cbad2365175357181f2e", size = 1724352, upload-time = "2026-07-20T19:52:26.043Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b1/6b55f6448b43a3338749f37fc6a14e7ac7dad121d2d585f22c1dd3d324fe/aiohttp-3.14.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2f7ca81d936d820ae479971a6b6214b1b867420b5b58e54a1e7157716a943754", size = 1770749, upload-time = "2026-07-20T19:52:28.467Z" }, - { url = "https://files.pythonhosted.org/packages/39/58/b3f1cf6af47fa0b0d51c6e2b98b2bf0326b44a932d74915f3c0874413ea8/aiohttp-3.14.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:da4f142fa078fedbdb3f88d0542ad9315656224e167502ae274cbba818b90c90", size = 1577547, upload-time = "2026-07-20T19:52:30.658Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e2/1d79f1ab35593d70dabed98936ae842ac90d12847c9a62bba4a19d6005a4/aiohttp-3.14.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:3d4238e50a378f5ac69a1e0162715c676bd082dede2e5c4f67ca7fd0014cb09d", size = 1781840, upload-time = "2026-07-20T19:52:33.021Z" }, - { url = "https://files.pythonhosted.org/packages/01/73/770be856c6fc861563999081a5fe2d44e16b4255f5fd94b2217a2349ca65/aiohttp-3.14.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03330676d8caa28bb33fa7104b0d542d9aac93350abcd91bf68e64abd531c320", size = 1745757, upload-time = "2026-07-20T19:52:35.541Z" }, - { url = "https://files.pythonhosted.org/packages/bc/0b/861fdbe3ff90b503a4d0a12276623f4afbe54c237ca876fc560125c270a3/aiohttp-3.14.2-cp314-cp314-win32.whl", hash = "sha256:43387429e4f2ec4047aaf9f935db003d4aa1268ea9021164877fd6b012b6396a", size = 455863, upload-time = "2026-07-20T19:52:37.634Z" }, - { url = "https://files.pythonhosted.org/packages/f7/03/cc8234d0f06fe4b0e1777c7ed313cc9014f078f72a9a0cbdc144965d1bd7/aiohttp-3.14.2-cp314-cp314-win_amd64.whl", hash = "sha256:e3a6302f47518dbf2ffd3cd518f02a1fbf53f85ffeed41a224fa4a6f6a62673b", size = 480986, upload-time = "2026-07-20T19:52:39.665Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b0/ae17dd629e22160f453b9041a4aee80a8a917ba2f44d5b3e252cc501ed2c/aiohttp-3.14.2-cp314-cp314-win_arm64.whl", hash = "sha256:8d1f3802887f0e0dc07387a081dca3ad0b5758e32bdf5fb619b12ac22b8e9b56", size = 453588, upload-time = "2026-07-20T19:52:41.634Z" }, - { url = "https://files.pythonhosted.org/packages/30/54/4bbcc00f04dbc380103ee669a56df30dea127c3686c8dc2ab86b05a1387b/aiohttp-3.14.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9094262ae4f2902c7291c14ba915960db5567276690ef9195cdefe8b7cbb3acb", size = 791337, upload-time = "2026-07-20T19:52:43.821Z" }, - { url = "https://files.pythonhosted.org/packages/e7/37/4f95edc1488580c63c0b5baaef429afbacadc7ccd9c47431cdcce90d9e5a/aiohttp-3.14.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:165b0dcc65960ffc9c99aa4ba1c3c76dbc7a34845c3c23a0bd3fbf33b3d12569", size = 526335, upload-time = "2026-07-20T19:52:45.903Z" }, - { url = "https://files.pythonhosted.org/packages/c3/ed/1d7ae73764d135638797a427cb637ae3d98f1fd61208c25891a9f26cdd67/aiohttp-3.14.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f518d75c03cd3f7f125eca1baadb56f8b94db94602278d2d0d19af6e177650a7", size = 532102, upload-time = "2026-07-20T19:52:47.994Z" }, - { url = "https://files.pythonhosted.org/packages/e3/12/da9816eee0d81506560259e7c0af93a4aa7df98acd0fe276958abd42d7c3/aiohttp-3.14.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b937d7864ca68f1e8a1c3a4eb2bac1de86a992f86d36492da10a135a482fab6", size = 1922727, upload-time = "2026-07-20T19:52:50.224Z" }, - { url = "https://files.pythonhosted.org/packages/2f/b9/d7ccbcde223a72c7b8f3458bdb08c2bacf5fc268a1fdd5cc29861eb0e4e2/aiohttp-3.14.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b155df7f572c73c6c4108b67be302c8639b96ae56fb02787eeae8cad0a1baf26", size = 1787202, upload-time = "2026-07-20T19:52:52.825Z" }, - { url = "https://files.pythonhosted.org/packages/c2/7c/39174069b7df18ea22b23a0fa9abffe4c091f0f86d53af83ddcbb5f44c8f/aiohttp-3.14.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0bfea68a48c8071d49aabdf5cd9a6939dcb246db65730e8dc76295fe02f7c73c", size = 1912574, upload-time = "2026-07-20T19:52:55.007Z" }, - { url = "https://files.pythonhosted.org/packages/21/ff/bd2b7473510caf352aef3f52a3d4fe10184ec22747df74ab658b4402020f/aiohttp-3.14.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8241ee6c7fff3ebb1e6b237bccc1d90b46d07c06cf978e9f2ecad43e29dac67a", size = 2005478, upload-time = "2026-07-20T19:52:57.401Z" }, - { url = "https://files.pythonhosted.org/packages/c8/ee/db26af0acc994350b653b3a73dddf9ee9886bf2fdb68b23fc98705e76442/aiohttp-3.14.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec64d1c4605d689ed537ba1e572138e2d4ff603a0cb2bbbfe61d4552c73d19e1", size = 1879709, upload-time = "2026-07-20T19:52:59.778Z" }, - { url = "https://files.pythonhosted.org/packages/6a/4d/592451fd40edca73fcb099d8718668cea36dc1dde873dd0f8e91a4081051/aiohttp-3.14.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0fb26fcc5ebf765095fe0c6ab7501574d3108c57fca9a0d462be15a65c9deb8d", size = 1675699, upload-time = "2026-07-20T19:53:02.11Z" }, - { url = "https://files.pythonhosted.org/packages/2a/3e/c1ce4aa13fd1f910e437684843ea6cf6e0041f91d89a1b0389dce247ee59/aiohttp-3.14.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ef710fbb770aefa4def5484eeddb606e70ab3492aa37390def61b35652f6820a", size = 1843542, upload-time = "2026-07-20T19:53:04.385Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a4/3f9e22fda714cc38ef558166ff3326cd2f6f65c33da92ba00e3641cbea54/aiohttp-3.14.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d813f54560b9e5bce170fff7b0adde54d88253928e4add447c36792f27f92125", size = 1827505, upload-time = "2026-07-20T19:53:07.072Z" }, - { url = "https://files.pythonhosted.org/packages/37/9d/260c7b8a25c7cd74bee63ce957556a2d25839a321e9107a089d4da3a51af/aiohttp-3.14.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1aa4f3b44563a88da4407cef8a13438e9e386967720a826a10a633493f69208f", size = 1853751, upload-time = "2026-07-20T19:53:10.012Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d6/7c3cfa191e666bf7b48b4346815900b35b85b43ffff783fd0be52e45fe09/aiohttp-3.14.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4610638d3135afaefadf179bffd1bbf3434d3dc7a5d0a4c4219b99fa976e944d", size = 1668850, upload-time = "2026-07-20T19:53:12.362Z" }, - { url = "https://files.pythonhosted.org/packages/c4/af/d9b8353aee9506dace04ca22c3c4e93b7375d9343c4780c8b512972fc6f9/aiohttp-3.14.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:6e30743bd3ab6ad98e9abbad6ccb39c52bcf6f11f9e3d4b6df97afffe8df53f3", size = 1883642, upload-time = "2026-07-20T19:53:14.664Z" }, - { url = "https://files.pythonhosted.org/packages/ed/29/ed64c0a013322570a02e5b0e359df2cfd30ae58ba045748dc78175f15826/aiohttp-3.14.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:68a6f7cd8d2c70869a2a5fe97a16e86a4e13a6ed6f0d9e6029aef7573e344cd6", size = 1844092, upload-time = "2026-07-20T19:53:16.995Z" }, - { url = "https://files.pythonhosted.org/packages/3c/74/ce6f229510fdd46ee7365fbb759ffaa3004e0bcc59fbd0ec51d8a8efc775/aiohttp-3.14.2-cp314-cp314t-win32.whl", hash = "sha256:205181d896f73436ac60cf6644e545544c759ab1c3ec8c34cc1e044689611361", size = 474098, upload-time = "2026-07-20T19:53:19.645Z" }, - { url = "https://files.pythonhosted.org/packages/72/4c/877c07a6d97a1a0e5e25a06bab76dd7449b533ff8bb6c281c5af8b09c8d9/aiohttp-3.14.2-cp314-cp314t-win_amd64.whl", hash = "sha256:312d414c294a1e26aa12888e8fd37cd2e1131e9c48ddcf2a4c6b590290d52a49", size = 500480, upload-time = "2026-07-20T19:53:22.227Z" }, - { url = "https://files.pythonhosted.org/packages/42/d8/d4c74bb7990da2ee6116fe262ace41cf376ebc6b0bda694c77eeaab5a509/aiohttp-3.14.2-cp314-cp314t-win_arm64.whl", hash = "sha256:63b840c03979732ec92e570f0bd6beb6311e2b5d19cacbfcd8cc7f6dd2693900", size = 469248, upload-time = "2026-07-20T19:53:24.622Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/20/887fdcf832326571b370ffc347b3e70abe101096f3720126aac161b1d872/aiohttp-3.14.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062", size = 509067, upload-time = "2026-07-23T01:55:42.618Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a3/92cec936f78cc4bf0fa5554ebe593b73459d94e3c62303e1902a4cccb6f7/aiohttp-3.14.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6", size = 514774, upload-time = "2026-07-23T01:55:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/2a0c38df3fc557620b6a5acd98364af050053b6285b4dc7ee74100c63c18/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919", size = 488134, upload-time = "2026-07-23T01:55:47.135Z" }, + { url = "https://files.pythonhosted.org/packages/48/d6/d51b7d4bf309af3693940d8ffd2b9ed0b682434ef85959b7c9c137f60cf8/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7", size = 494201, upload-time = "2026-07-23T01:55:49.451Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5a/8f624384e5f1efabb5229b94157eb966b021e97bdb188c62860c2ae243c2/aiohttp-3.14.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0", size = 502766, upload-time = "2026-07-23T01:55:51.656Z" }, + { url = "https://files.pythonhosted.org/packages/a6/26/4ff0164370deec18fb19254ee4ab10b7a73304ac0c860b13f5f84663759b/aiohttp-3.14.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924", size = 756557, upload-time = "2026-07-23T01:55:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/97/a3/7056b86dc0d9ec709ea9777eae3b0161428f943372f8b98c01c11593b682/aiohttp-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646", size = 510168, upload-time = "2026-07-23T01:55:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/0357a015892fd68058bf2d39d3fd1958e459b997a7db30aaa6aaa434ae96/aiohttp-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b", size = 512957, upload-time = "2026-07-23T01:55:58.437Z" }, + { url = "https://files.pythonhosted.org/packages/47/d1/8aba53f15ccb2238405f5e9d30e2a8ca44f93878c26e7165ade00d374b1c/aiohttp-3.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30", size = 1750149, upload-time = "2026-07-23T01:56:00.856Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/40c3fee327529284375c6701cbb0fa4600cc2e8432af1378f897e2ef7d3a/aiohttp-3.14.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9", size = 1707685, upload-time = "2026-07-23T01:56:03.371Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a3/ca0cc6724cca8114b05694abd916060758c79894c3aa5b012cdadc1bc28e/aiohttp-3.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f", size = 1803911, upload-time = "2026-07-23T01:56:05.817Z" }, + { url = "https://files.pythonhosted.org/packages/95/b5/85b099c299c3ffd38ad9b3e43694c8a346934e4a30c88c4fd5a841234f77/aiohttp-3.14.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d", size = 1876929, upload-time = "2026-07-23T01:56:08.413Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b7/1da684a04175473fa4cddbf9a2f572e79514c3fd27a74597f43057d4f3da/aiohttp-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147", size = 1761112, upload-time = "2026-07-23T01:56:10.918Z" }, + { url = "https://files.pythonhosted.org/packages/d1/16/bc4b55e3e5cb175fd69c53c90d60d2f47797cb343da5106e23863dc4dba4/aiohttp-3.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c", size = 1583500, upload-time = "2026-07-23T01:56:13.613Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e8/13a9d957a1ee40837f46aa30f0f4c657e673ad86a2e6362a9f9be20d26d9/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a", size = 1713940, upload-time = "2026-07-23T01:56:15.969Z" }, + { url = "https://files.pythonhosted.org/packages/38/05/d33c680c1bcf1c7e130f9cbfc1fc02fe8bb0c4af2a94a53dd5fb56131e5c/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0", size = 1724413, upload-time = "2026-07-23T01:56:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/85/1d/af798d306f7a74b6a632dbcabcf62a4c91391b7582d2a8c6d7712e2cc54e/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661", size = 1770748, upload-time = "2026-07-23T01:56:21.074Z" }, + { url = "https://files.pythonhosted.org/packages/a8/92/ad720d472556a995049206867765e9410969684f86ee09423ff9969044c1/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22", size = 1577564, upload-time = "2026-07-23T01:56:23.475Z" }, + { url = "https://files.pythonhosted.org/packages/60/ad/0ed7586cbef7a884e23a752fa2bb987a122e6a5dd50dab109258d0a95193/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41", size = 1782080, upload-time = "2026-07-23T01:56:25.994Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/dbaed0d73e8a69aad653b045dab451c67c2454bb731a37b45a86593e9422/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf", size = 1745813, upload-time = "2026-07-23T01:56:28.604Z" }, + { url = "https://files.pythonhosted.org/packages/81/1b/6893d4bc57e434fc93a6c9217c637d967a0b651d989f6e3265179375754a/aiohttp-3.14.3-cp314-cp314-win32.whl", hash = "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da", size = 455872, upload-time = "2026-07-23T01:56:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8b/c7baa1ba1eda4db6989baefe5de6d99834921b84ebd7918624febcb9f290/aiohttp-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100", size = 481030, upload-time = "2026-07-23T01:56:33.365Z" }, + { url = "https://files.pythonhosted.org/packages/22/8c/c29d067df825a2df88ca432db848aa2fe8199598359cc06c12b09320cac9/aiohttp-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc", size = 453669, upload-time = "2026-07-23T01:56:35.731Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a4/9c033beb355d39b6147980597ec9645e4729243f686ee4dc73945de72030/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b", size = 791403, upload-time = "2026-07-23T01:56:37.972Z" }, + { url = "https://files.pythonhosted.org/packages/80/ca/87c32a0a7704583cfc49660bd817889bae5b830bf53b5dcb4e92145ac2da/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0", size = 526413, upload-time = "2026-07-23T01:56:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d8/8ec0e471248c500acdce2be3f46db8fb62b5eb60efef072529cc85ee1d26/aiohttp-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e", size = 532135, upload-time = "2026-07-23T01:56:42.876Z" }, + { url = "https://files.pythonhosted.org/packages/fe/45/f8919fd936e8b79fcd9bda7b6d8e62613462a713f4f17987fd7c34399142/aiohttp-3.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716", size = 1922742, upload-time = "2026-07-23T01:56:45.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ec/9ca76b28a27525b0cc53e20842e0228b022f301ce1f436b7d814b4aaf2df/aiohttp-3.14.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f", size = 1787371, upload-time = "2026-07-23T01:56:48.045Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/6acdbf17315f7b55f1937e3387acb89a3cddeb4995689553d064af8e92ab/aiohttp-3.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553", size = 1912623, upload-time = "2026-07-23T01:56:50.605Z" }, + { url = "https://files.pythonhosted.org/packages/86/e6/438b0c79ca6f45eb9fd9817dd4c01a91919a38c0de5ee9e05e2b4dc0ece7/aiohttp-3.14.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100", size = 2005515, upload-time = "2026-07-23T01:56:53.153Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/62cbd6577758699525f5c712d1ddef57d9875fbab0ae8d5f5a202fd598f8/aiohttp-3.14.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85", size = 1879906, upload-time = "2026-07-23T01:56:55.818Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/18bcbf830a21dc3aae24d8f6b6feaf3db1d2090242d00a7868db2ffb0b67/aiohttp-3.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33", size = 1675849, upload-time = "2026-07-23T01:56:58.861Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/47f4968659c5e23606c3790c80fc624e691c153d036148449ee84d31b287/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f", size = 1843496, upload-time = "2026-07-23T01:57:01.591Z" }, + { url = "https://files.pythonhosted.org/packages/64/af/38c33c4dd82fddcb4e56c4653b6f1072a8edbc6b7fa15809f14932c41e2d/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0", size = 1827746, upload-time = "2026-07-23T01:57:05.131Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9d/0537cda4885ac8f5b7053d164dd06312f4c483a4edcb8ee5b8aaf2a989bf/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098", size = 1853810, upload-time = "2026-07-23T01:57:08.043Z" }, + { url = "https://files.pythonhosted.org/packages/19/fe/26f9c5e6458385aa86497836b0dea6fb2f027827d63f37c7856cce9286ee/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25", size = 1668895, upload-time = "2026-07-23T01:57:10.837Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4c/618b1db9b9ba079b8875d2cdf78e7c4a3bf72903bd5850fee7dd9544600a/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9", size = 1883833, upload-time = "2026-07-23T01:57:13.672Z" }, + { url = "https://files.pythonhosted.org/packages/94/c6/bd959bd1e4771f9fd944e9e436224c48c77b018b73b519b5aad346335bcc/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb", size = 1844251, upload-time = "2026-07-23T01:57:16.593Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/08d41839658bdd44a0ed2480f3891705ecb487ce28c0dde62c9040c997e0/aiohttp-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963", size = 474180, upload-time = "2026-07-23T01:57:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/3cd6ef0a2b2851f7ab913b5b079334781bd50ff56a323e4454063377a080/aiohttp-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b", size = 500528, upload-time = "2026-07-23T01:57:21.762Z" }, + { url = "https://files.pythonhosted.org/packages/a4/37/cfd1ed540a4d318da025590d96b728e63713c09e9377950fc655dadeb856/aiohttp-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7", size = 469280, upload-time = "2026-07-23T01:57:24.241Z" }, ] [[package]] @@ -83,16 +83,16 @@ wheels = [ [[package]] name = "annotated-types" -version = "0.7.0" +version = "0.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, ] [[package]] name = "anthropic" -version = "0.117.1" +version = "0.120.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -104,9 +104,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/97/7f03aa611b0005044e65290a9591f9fa73ac32d68499f52c322f836ba8ed/anthropic-0.117.1.tar.gz", hash = "sha256:e015a2d5b99fdf0aecac246e24ddb40d7bf93f9fe5b198771fab1559fb0101b9", size = 992232, upload-time = "2026-07-21T22:27:37.323Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/10/4ca013cb166f226bd89e0aeb0fcaff94f45ddf716d4925ce89475d3c587b/anthropic-0.120.2.tar.gz", hash = "sha256:9722efc10c27a30a69f5338ddacdb35bc6a64297a4e4ba729bf83af873d5fb3a", size = 1008421, upload-time = "2026-07-28T17:38:26.986Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ea/36a068fa0b1761425cfdb16aeb799575e5846e6fdbdc7f73a10512f6e5a5/anthropic-0.117.1-py3-none-any.whl", hash = "sha256:7ab79e2c33c3dbde54c5800c4d535821e1c6d80bdf7de83ae1c0328bae563b76", size = 999820, upload-time = "2026-07-21T22:27:35.85Z" }, + { url = "https://files.pythonhosted.org/packages/63/af/0f5db57b9397a0f3b7fc204cbef143401a7cadaf982330f97f1ce3d39f34/anthropic-0.120.2-py3-none-any.whl", hash = "sha256:0f0bc2b381dc0eb41c8d886b815d79c2041cd2374f83aed36f574b6dc9c579c1", size = 1022851, upload-time = "2026-07-28T17:38:25.466Z" }, ] [package.optional-dependencies] @@ -199,15 +199,15 @@ wheels = [ [[package]] name = "backrefs" -version = "7.0" +version = "8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/a7dd63622beef68cc0d3c3c36d472e143dd95443d5ebf14cd1a5b4dfbf11/backrefs-7.0.tar.gz", hash = "sha256:4989bb9e1e99eb23647c7160ed51fb21d0b41b5d200f2d3017da41e023097e82", size = 7012453, upload-time = "2026-04-28T16:28:04.215Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/56/4744bcd0c82184e80c52b0ac4076c261a8ffa1f1b343ff2f6e89ce0e1cef/backrefs-8.0.tar.gz", hash = "sha256:b556cd7d36c3a3a2f256b89590b176b8eddfb73bcfaee3a3ddd84ea66d21ce50", size = 7013081, upload-time = "2026-07-26T19:54:24.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/39/39a31d7eae729ea14ed10c3ccef79371197177b9355a86cb3525709e8502/backrefs-7.0-py310-none-any.whl", hash = "sha256:b57cd227ea556b0aed3dc9b8da4628db4eabc0402c6d7fcfc69283a93955f7e9", size = 380824, upload-time = "2026-04-28T16:27:55.647Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl", hash = "sha256:a0fa7360c63509e9e077e174ef4e6d3c21c8db94189b9d957289ae6d794b9475", size = 392626, upload-time = "2026-04-28T16:27:57.42Z" }, - { url = "https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-none-any.whl", hash = "sha256:ca42ce6a49ace3d75684dfa9937f3373902a63284ecb385ce36d15e5dcb41c12", size = 398537, upload-time = "2026-04-28T16:27:58.913Z" }, - { url = "https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl", hash = "sha256:f2c52955d631b9e1ac4cd56209f0a3a946d592b98e7790e77699339ae01c102a", size = 400491, upload-time = "2026-04-28T16:28:00.928Z" }, - { url = "https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl", hash = "sha256:a6448b28180e3ca01134c9cf09dcebafad8531072e09903c5451748a05f24bc9", size = 412349, upload-time = "2026-04-28T16:28:02.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/fd/9bf53b6a6f6f519ffaac765df2f2a25e5c2fc6d32cfd2b2747099e72c911/backrefs-8.0-py310-none-any.whl", hash = "sha256:4a627b817fd2dce43b79ab48da63613340509381cd8ce0897078a0bce79a2ab8", size = 380377, upload-time = "2026-07-26T19:54:17.457Z" }, + { url = "https://files.pythonhosted.org/packages/e1/29/4bd7ae72a2634da00379c2b3bcc5439e7c94620235c6afea8af15229a973/backrefs-8.0-py311-none-any.whl", hash = "sha256:f0c35cf0102ba6b6070c12a492be3c1c1d3f5839529784b9a9565d6d04569a01", size = 392169, upload-time = "2026-07-26T19:54:18.782Z" }, + { url = "https://files.pythonhosted.org/packages/29/13/232505664e8e2a0c7a2eb0c505cfade9d715538f89a5d62bc4c272968f62/backrefs-8.0-py312-none-any.whl", hash = "sha256:87f0fae8c5f207fe9f4b2887efc71d42f4900ac78faa1af08d675ef303692dc5", size = 398084, upload-time = "2026-07-26T19:54:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/8a/69/47a3dc20abc4fa5486655fde681bd55e63211b46c886d8c02223d6468431/backrefs-8.0-py313-none-any.whl", hash = "sha256:601ce68ca12385dbda06ce264406b4c4210cf5b79fd0fd627592365c92f29a88", size = 400040, upload-time = "2026-07-26T19:54:21.194Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cf/e5f9b68a5b0e939a2fb933a66c20180d0c9241bf8927f7a47fa48c1675e9/backrefs-8.0-py314-none-any.whl", hash = "sha256:9ec96efa080938be92323e8e730e57718c9c88eb15ad70bbef4e1766df591408", size = 411903, upload-time = "2026-07-26T19:54:23.221Z" }, ] [[package]] @@ -227,30 +227,30 @@ wheels = [ [[package]] name = "boto3" -version = "1.43.53" +version = "1.43.58" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/da/0e90eab875f2eb4b8708fef2c198f2559ed1e451a1016f1cd4fcdcfbfbe3/boto3-1.43.53.tar.gz", hash = "sha256:c80425acab314d7af09609562053f565139e1fe49108eacfcc1601ebfaee235b", size = 112678, upload-time = "2026-07-21T19:28:53.002Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/95/bd6276870084c9c1a94e17d1dc73b2de275e59bd7a0ad8bfff0a1598cec4/boto3-1.43.58.tar.gz", hash = "sha256:12871fb50c383f1b9aa4ed6dd386ba689062baef730552e79d5a9cd782b53058", size = 112685, upload-time = "2026-07-28T19:35:09.336Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/27/7e72d25fdde77668b7bd4fa47381192dd2aa64fb77265e4bab786fd9fe2a/boto3-1.43.53-py3-none-any.whl", hash = "sha256:5383e705d8a976a14f23bb8c113c07a396931a019db98fce4cdc68650ec6e4d8", size = 140025, upload-time = "2026-07-21T19:28:50.957Z" }, + { url = "https://files.pythonhosted.org/packages/1c/85/b0709066efb4ce7b86aba4092c739ad0e458be9d62cf32550fc4c130c93f/boto3-1.43.58-py3-none-any.whl", hash = "sha256:ce1a20cbcfaa1d0b3c8f568e2b6c7fbd34b842ea00e729d49a4b4de522828db9", size = 140026, upload-time = "2026-07-28T19:35:06.993Z" }, ] [[package]] name = "botocore" -version = "1.43.53" +version = "1.43.58" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/6b/ebcefacc4de3cd4f1c449540d86877f76c2f5e586a620831012decbb2b2c/botocore-1.43.53.tar.gz", hash = "sha256:36d93dd8db68ee75f6b61ca9f775161b8168844e4601698701530e6efdded141", size = 15720336, upload-time = "2026-07-21T19:28:41.547Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/64/5cd46a7e72b0647e6d78fc8da016259ad66b9ae0818f4c5d629c75e7ca49/botocore-1.43.58.tar.gz", hash = "sha256:e110ca53f65c128fe98df4d6d36a459b1db17c6114671c8a18becf151bf20909", size = 15742412, upload-time = "2026-07-28T19:34:57.468Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/e5/1b60e394f0fff97ee70dd16913382b7dffda85b98e639c0c9e8ff56cfaa7/botocore-1.43.53-py3-none-any.whl", hash = "sha256:b7ee9a70d187e5348883c820990ccd9436ab14e2bd6622741fc96fe561e816b8", size = 15404628, upload-time = "2026-07-21T19:28:37.475Z" }, + { url = "https://files.pythonhosted.org/packages/3d/82/6f8fbbea47b773734ba0199643d2d851e5c2f75bc3699fe99db8af344d96/botocore-1.43.58-py3-none-any.whl", hash = "sha256:f516159f0732da8249206163ccea3bd1f82ad2a9d184fe6ed447e1abdba4330e", size = 15426503, upload-time = "2026-07-28T19:34:53.508Z" }, ] [[package]] @@ -518,7 +518,7 @@ wheels = [ [[package]] name = "draive" -version = "0.113.1" +version = "0.114.0" source = { editable = "." } dependencies = [ { name = "haiway" }, @@ -741,11 +741,11 @@ wheels = [ [[package]] name = "fsspec" -version = "2026.6.0" +version = "2026.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/a1/ae4e3e5003468d6391d2c77b6fa1cd73bd5d13511d81c642d7b28ac90ed4/fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a", size = 313646, upload-time = "2026-06-16T01:57:28.105Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/78/f34251dadb8f3921264a1d9b8946f5e542014ee2614b285261b4e40e6775/fsspec-2026.7.0.tar.gz", hash = "sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88", size = 317040, upload-time = "2026-07-28T16:34:51.052Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583, upload-time = "2026-07-28T16:34:49.538Z" }, ] [[package]] @@ -762,7 +762,7 @@ wheels = [ [[package]] name = "google-api-core" -version = "2.32.0" +version = "2.33.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, @@ -771,9 +771,9 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/33/00277be1305fd68355d08197f05e22db259c0cff49a10c8590a1869ade9b/google_api_core-2.32.0.tar.gz", hash = "sha256:2b33aad226b19272458c46abfe5c5a38d9531ece0c44502129a1463ce83674ac", size = 177659, upload-time = "2026-07-16T20:36:07.717Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/62/8fb1fb647d2788c950d69d6a769cd9d55c918ac1fc57be2f90b7e4029787/google_api_core-2.33.0.tar.gz", hash = "sha256:3a36bcc3e319783f4c97da41f6f45ea6ffcaa55848e341de16e09cb70243c2bb", size = 181607, upload-time = "2026-07-22T16:28:28.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/44/5018c5ac1526c98169db98d87a6ff7d5508f5246621c3ee1a046fdd5e0a6/google_api_core-2.32.0-py3-none-any.whl", hash = "sha256:ae1f0d58a6c8869350bf469f8eb3092e7f8c494a942d9525494afb6c162b0904", size = 174198, upload-time = "2026-07-16T20:35:41.865Z" }, + { url = "https://files.pythonhosted.org/packages/89/31/5056a347bb934ea04583c8b27916ef1501729c72638629545bce26ff4223/google_api_core-2.33.0-py3-none-any.whl", hash = "sha256:a2e22a0c1d0f03eafff1858b38cf46f832d5902b0c052235bf0ab8402929fbdc", size = 176462, upload-time = "2026-07-22T16:28:22.447Z" }, ] [[package]] @@ -838,23 +838,23 @@ wheels = [ [[package]] name = "grpcio" -version = "1.82.1" +version = "1.83.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/bc/656b89387d6f4ed7e0686c7b64c2ae7e554a759aa58122c8e5fb99392c32/grpcio-1.82.1.tar.gz", hash = "sha256:707b24abd90fcb1e45bcc080577da1dbf9971d107490589b9539af8e1e77b4b5", size = 13187300, upload-time = "2026-07-08T12:36:16.588Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/98/304898ac4e04e2d5e4e4c2eadc178b1f2a16d5f4bc2f91306c87d64680b9/grpcio-1.83.0.tar.gz", hash = "sha256:7674587248fbbb2ac6e4eecf83a8a0f3d91a928f941de571acfd3a2f007fbc24", size = 13428824, upload-time = "2026-07-23T15:20:37.759Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/cb/cf9ae9e164c6e6dc8a494faa9771763df9da150eefe19671009624d1559f/grpcio-1.82.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:35f990f7784c8fd2872644f07f96ebb4d9e48e145a190ab80d0280af91a1bfb2", size = 6146901, upload-time = "2026-07-08T12:35:49.261Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/eccf26dbcfb7f7cab8027c5490a16c8937c5aa7a2ec20a3eab2cf7a43165/grpcio-1.82.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:46536a4a1f4434df3c851b9254ff6fc7df5705b273681a15ca277d5921c178a0", size = 11954756, upload-time = "2026-07-08T12:35:52.196Z" }, - { url = "https://files.pythonhosted.org/packages/ad/75/3b3b4a3cc9f084b026af96e1d3e539b1af29ec7f41ed0dfff3cb99cc8626/grpcio-1.82.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d6650a7c1ebb7921c70e12a385439a8118efb99e669fa9ed31cf25db1843937c", size = 6723087, upload-time = "2026-07-08T12:35:54.973Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8b/b0f0c9b1400a99a4da4c09b114f101b192f8f11192e76f620b8962f5d90b/grpcio-1.82.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b8e110c66df5204c0506d6c8787b35d48b8b699ef5aa366d6c4d67325c67fe9a", size = 7454542, upload-time = "2026-07-08T12:35:57.586Z" }, - { url = "https://files.pythonhosted.org/packages/b7/bd/428e38868382aa193697a5aa53973f29c58e58ba4268aa0c86a2715ee58b/grpcio-1.82.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f853eae07235a51a27bb5d6a9a175a59ca55dc9b99edc6ce2f76f07332d333ae", size = 6889588, upload-time = "2026-07-08T12:36:00.012Z" }, - { url = "https://files.pythonhosted.org/packages/49/ce/03e01d5e10259bf5c08ee50570cc94724e79c956f61fd2f09b341af0956c/grpcio-1.82.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:60b0f2c95337694fc094b77d9f60f50566c84b5677393e342eb98daeee242d98", size = 7514166, upload-time = "2026-07-08T12:36:02.693Z" }, - { url = "https://files.pythonhosted.org/packages/ff/59/278b4b600329e2ba3849f3c1ea3c820b3a01b38a7ad184ba09595e8d2733/grpcio-1.82.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:b064fc444812bdaa9825d33c26f8d732d63ee6a5d78557c1faf92c98687fed27", size = 8536166, upload-time = "2026-07-08T12:36:05.349Z" }, - { url = "https://files.pythonhosted.org/packages/44/27/7ccf2ef00f27a8e47a79d641c8ceaf7d3028c7a03d9a97b4c8a9a783c086/grpcio-1.82.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d7ede11d747b4e1bd05e3bc0260e155b65a88735a895a10f6521f19b889511e", size = 7912572, upload-time = "2026-07-08T12:36:08.393Z" }, - { url = "https://files.pythonhosted.org/packages/0d/be/33742482d2753f2d3a1b7641664b6622262d44f2f3b609f13425dd86d36f/grpcio-1.82.1-cp314-cp314-win32.whl", hash = "sha256:3d21f19838dc255ecbb79321b15ae9b98fbddff4c3d4aedb0a81bdd7f4ab572a", size = 4321856, upload-time = "2026-07-08T12:36:10.899Z" }, - { url = "https://files.pythonhosted.org/packages/cc/67/03329c847172c78ddeb1eb9be6b444fdbc12775a84c958b27e427e7b926d/grpcio-1.82.1-cp314-cp314-win_amd64.whl", hash = "sha256:e20f1edbb15f99e3128ec86433f9785fd5a451d8f115e74fe0056134f092a9d5", size = 5141114, upload-time = "2026-07-08T12:36:13.595Z" }, + { url = "https://files.pythonhosted.org/packages/9c/60/f2cca8147ea213d3e43ae9158d03ad04e020fdf32ff027253e1fe93f921d/grpcio-1.83.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3f351629f6ae16ecc0ec3553e586a6763ffd9f6114044286d0cbec3e09241bfa", size = 6305607, upload-time = "2026-07-23T15:20:15.353Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ab/d3874931d123a95e83a3ebf8aa04537988fb62425cedb8bf3cefc5ad41b2/grpcio-1.83.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d05ff664100d429335b93c91b8b34ddf9e94a112205e7fa06dede309e44a4e4c", size = 12166617, upload-time = "2026-07-23T15:20:17.435Z" }, + { url = "https://files.pythonhosted.org/packages/92/ff/6f18f9426b69306f4e00a9add3b0ee2748da8aad53836ef80cab0d62d04f/grpcio-1.83.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7936f2a56cf04f6514705c0fedf400971de01b6aa1719327e4718f410a765e2b", size = 6880213, upload-time = "2026-07-23T15:20:19.98Z" }, + { url = "https://files.pythonhosted.org/packages/70/21/706d1147c6b93b98f179240c13991fbcc56880eba0c868abb1ad40d8a0a6/grpcio-1.83.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b0a0be840e51b6b7ee9df9269770faf77bdf4b771053c257c21d12bad607714c", size = 7618335, upload-time = "2026-07-23T15:20:22.161Z" }, + { url = "https://files.pythonhosted.org/packages/74/04/1a8443c889115ec9e213a213e86bc93a71ee9088027e5befa09aaa0edd9d/grpcio-1.83.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:009667eaf3dcd5224c713589cdc98e7ca4ed0ff0b61132c6b276e930eb83a2df", size = 7043416, upload-time = "2026-07-23T15:20:24.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/c6/94e0fee5b12bc1da1370185b680988db6f739d19b42d9959db01a7ea50bf/grpcio-1.83.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bb669918fd88936b15599caff4160a77ab74bdeb25f2231f6e45b61282d6107b", size = 7583253, upload-time = "2026-07-23T15:20:26.313Z" }, + { url = "https://files.pythonhosted.org/packages/a0/97/de1ccb671fb85575bc5192faedf9ecdbdf5b390d2e6584dcf552bcbd370e/grpcio-1.83.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c19b454d3d3f28db81f2c7c4dbaee96e7f6fd149721733ffe79d6bc530f17404", size = 8605102, upload-time = "2026-07-23T15:20:28.437Z" }, + { url = "https://files.pythonhosted.org/packages/17/0f/0e0ec749a7034ffcbaa050e39779872950ead90c22e7e0116be3f28b2b46/grpcio-1.83.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:61007cd08640abc5c54547ee32505474c482cd733a53cb87551ea81faa6350af", size = 7979826, upload-time = "2026-07-23T15:20:31.182Z" }, + { url = "https://files.pythonhosted.org/packages/83/fa/c3fda157287f64bc65acee6c5aa90c41acf9e0d3a8e69a265eecff6d00a1/grpcio-1.83.0-cp314-cp314-win32.whl", hash = "sha256:32e11c37f5285b0c6fa3042c05fe06903696689749833fc64e67dec71b9bbe33", size = 4471765, upload-time = "2026-07-23T15:20:33.195Z" }, + { url = "https://files.pythonhosted.org/packages/a1/00/b1b26431c9d54eee11724fd6e5585473a2ed47fbc1fb95e5204906a642ce/grpcio-1.83.0-cp314-cp314-win_amd64.whl", hash = "sha256:2bb48cb5e6dd005ca12b89ce4b6ac0b48ff3112c747542ee7986ef611a8ca6d9", size = 5298932, upload-time = "2026-07-23T15:20:35.48Z" }, ] [[package]] @@ -868,15 +868,15 @@ wheels = [ [[package]] name = "h2" -version = "4.3.0" +version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "hpack" }, { name = "hyperframe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/d4/a7d6fb3f58be99d65cbf2d3f766896217a2921d0f3ab10711c45dc1519ee/h2-4.4.0.tar.gz", hash = "sha256:46b551bdcdc7e83cf5c04d0bf93badb8a939bd2287d9fee1abb23a445b9e0580", size = 2156691, upload-time = "2026-07-23T19:14:19.442Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, + { url = "https://files.pythonhosted.org/packages/f6/df/5b14a118322d6097cb9bb30ec6bacad268e546a8ecfcb1f6d0de618dac2f/h2-4.4.0-py3-none-any.whl", hash = "sha256:6acffe1aeab79098d7eb0f8385c1add11f2c7a94815f6fa2b7060eeddee3d87c", size = 62368, upload-time = "2026-07-23T19:14:16.143Z" }, ] [[package]] @@ -996,7 +996,7 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "1.24.0" +version = "1.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1009,9 +1009,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/9b/d3bb4e7d792835daf34dd7091bbc7d7b4e0437d9388f1ea7239cce49f478/huggingface_hub-1.24.0.tar.gz", hash = "sha256:18431ff4daae0749aa9ba102fc952e314c98e1d30ebdec5319d85ca0a83e1ae5", size = 921848, upload-time = "2026-07-17T09:54:01.022Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/50/db3771a6e4fad4bd28fb055d4363b51cb0ae98c1aa504b79d41fdcab5483/huggingface_hub-1.25.1.tar.gz", hash = "sha256:21129595ca7a753be479b319913e22cc8808361ac118bd76cc413db831b28a99", size = 928426, upload-time = "2026-07-27T09:24:10.117Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/c3/aeaaf3911d2529614be18d1c8b5496afc185560e76568063d517283318af/huggingface_hub-1.24.0-py3-none-any.whl", hash = "sha256:6ed4120a84a6beec900640aa7e346bd766a6b7341e41526fef5dc8bd81fb7d59", size = 771904, upload-time = "2026-07-17T09:53:59.106Z" }, + { url = "https://files.pythonhosted.org/packages/f7/3f/21e816831c6d16f88a6c784974413fa0421ce8a5d04380c2666ed5b503e5/huggingface_hub-1.25.1-py3-none-any.whl", hash = "sha256:004d4e70350517e24c68a7dbb7dc5e40b2b6aefef8f94bf7a85f6f9835102ea5", size = 774909, upload-time = "2026-07-27T09:24:08.079Z" }, ] [[package]] @@ -1187,7 +1187,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.28.1" +version = "1.29.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1205,9 +1205,9 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/77/9450b8f251a13affb6281997d0523c4615f8a8b35d0b21ff30db3a5aac9d/mcp-1.28.1.tar.gz", hash = "sha256:d51e36a5f5644faea4f85ea649bfffa6bc6c26770d42798ad6a3de3d2ba69683", size = 638501, upload-time = "2026-06-26T12:57:29.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/d3/f9acc21dfc886e4f78e2add1a47db46ce16884346afde53f8a064c02c891/mcp-1.29.0.tar.gz", hash = "sha256:52d01f334de1868cc3bb2d6604931126a67631f99a6c5d3b82ba47290315ec36", size = 643148, upload-time = "2026-07-28T13:41:41.939Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/5e/d118fce19f87a2e7d8101c35c8ae0ec289098a4df0ff244cec23e415aca0/mcp-1.28.1-py3-none-any.whl", hash = "sha256:2726bca5e7193f61c5dde8b12500a6de2d9acf6d1a1c0be9e8c2e706437991df", size = 222620, upload-time = "2026-06-26T12:57:27.218Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/248b201f6d753d69fd5d6506011abbb35a946d9142b2ae311a948fd0be3d/mcp-1.29.0-py3-none-any.whl", hash = "sha256:f5a075bb611f23d6f4d080c6a1699fa62772eebc562ba9e66b306ddde1c755f7", size = 223436, upload-time = "2026-07-28T13:41:40.337Z" }, ] [[package]] @@ -1555,7 +1555,7 @@ wheels = [ [[package]] name = "openai" -version = "2.46.0" +version = "2.50.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1567,9 +1567,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/ac/f725c4efbda8657d02be684607e5a2e5ce362e4790fdbcbdfb7c15018647/openai-2.46.0.tar.gz", hash = "sha256:0421e0735ac41451cad894af4cddf0435bfbf8cbc538ac0e15b3c062f2ddc06a", size = 1114628, upload-time = "2026-07-17T02:48:06.05Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/f5/e7735f2af272ee179a287911a698b3cbdb59d7a4ac4874571363adf1e4de/openai-2.50.0.tar.gz", hash = "sha256:5128f7caf4a6b01aefd6e7e93efe170a2c3427b8de286b9af5cdff3aa47e02c8", size = 1081965, upload-time = "2026-07-28T22:16:31.674Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/7b/206238ebcb50b235942b1c66dba4974776f2057402a8d91c399be587d66a/openai-2.46.0-py3-none-any.whl", hash = "sha256:672381db55efb3a1e2610f29304c130cccdd0b319bace4d492b2443cb64c1e7c", size = 1637556, upload-time = "2026-07-17T02:48:03.695Z" }, + { url = "https://files.pythonhosted.org/packages/00/ca/db315b3bb748c26c644a3f85b7d509e774354d6518d47080b1446005ee41/openai-2.50.0-py3-none-any.whl", hash = "sha256:90bdddcc5a2fa529b350fac9c5780d87e5c361dcc6090ab57b0d470b0d7af7fa", size = 1650721, upload-time = "2026-07-28T22:16:29.48Z" }, ] [package.optional-dependencies] @@ -1705,11 +1705,11 @@ wheels = [ [[package]] name = "pika" -version = "1.4.1" +version = "1.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/09/8c42dd00c4b3e09ebd174455d30823c261eefef9d2b7e94ae9ca779704c1/pika-1.4.1.tar.gz", hash = "sha256:e851f3e4992adfbf8eb64e9b86d94e3382f92ba0200055abedbb29676b8e713b", size = 154268, upload-time = "2026-05-22T18:01:24.855Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/06/a5e4589eccb22be5790bf1327e20c5387b64327daa40c4d857cf9265ec3c/pika-1.4.2.tar.gz", hash = "sha256:48d1f50297e76be4fc798fd5232d4d532d7a4758e51f7c0ae6c4004b9808a26b", size = 154365, upload-time = "2026-07-23T02:11:26.003Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/7b/a09c0d378ee8604220902c58103104b2304ef99744c4b7fc45312d280484/pika-1.4.1-py3-none-any.whl", hash = "sha256:2daae7bd422a0fc4f4879fd48c9a1932ed74a0bc7172e1e5f9bde63a101ed074", size = 164962, upload-time = "2026-05-22T18:01:23.429Z" }, + { url = "https://files.pythonhosted.org/packages/d0/82/3b047c707700e08539cde489475eb5eaca13550599baaa17a8da038003e7/pika-1.4.2-py3-none-any.whl", hash = "sha256:b1df7389cdffaa45856bd01ade4e81bd488e0e199c443faf0c7bea89015f373f", size = 165087, upload-time = "2026-07-23T02:11:24.712Z" }, ] [[package]] @@ -1787,14 +1787,14 @@ wheels = [ [[package]] name = "proto-plus" -version = "1.28.1" +version = "1.28.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/44/767757fd2cdd4a60d7e4440d9f7b491d6131103d313638d2c03e06c268fb/proto_plus-1.28.1.tar.gz", hash = "sha256:832e68e7fe064cf90ab153b6e5eb935b27891bb89aaeb68b115e9b702f6cb168", size = 57166, upload-time = "2026-07-08T17:04:02.367Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/3e/29e0d6a2c5adde6ab5772253fd16ab346324026b89a66e354689c86d0584/proto_plus-1.28.2.tar.gz", hash = "sha256:26d843eb99c1e32fdf1d20ff0faae56607f7748fe774acf9ecd5cfe6c6472501", size = 58063, upload-time = "2026-07-22T16:28:29.119Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/34/2f2b57dbfd145b995a29847a16b0903fce5ef6ad3c7aad740a609c5d3678/proto_plus-1.28.1-py3-none-any.whl", hash = "sha256:6660f5f1970874bdcfc3088b435188a36a37bd3596668f7d726417c4ae8cfbed", size = 50408, upload-time = "2026-07-08T17:03:34.532Z" }, + { url = "https://files.pythonhosted.org/packages/9d/84/4e9a53a062d4073c74897a6bd20fff74d55307341b3e85c081002462b3ef/proto_plus-1.28.2-py3-none-any.whl", hash = "sha256:b874236fcac2358f601e4330bcb76cb8b89c851303ccf4078408b3d4774d1c52", size = 50693, upload-time = "2026-07-22T16:28:24.059Z" }, ] [[package]] @@ -2296,39 +2296,39 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/06/ae069393fc66e8ff33036d4b368003833bf6e88ccf182e17e7a2f1c754fd/ruff-0.15.22.tar.gz", hash = "sha256:3f15175b1fb580126f58285a5dae6b2ea89000136d980c64499211f116b54809", size = 4785063, upload-time = "2026-07-16T15:14:13.244Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/18/ee54b7ae1e121be7a28ea6da4b67564ebb0530e183a54415ab7e3bcd2c4e/ruff-0.15.22-py3-none-linux_armv6l.whl", hash = "sha256:44423e73493737f5e7c5b41d475483898ff37afcdae38bc3da5085e29af1c2d8", size = 10781258, upload-time = "2026-07-16T15:13:19.452Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d2/2520cb14761ddbeaf57642a76942fc36adcbdbe53b4532241995f6fc485c/ruff-0.15.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b82c6482946e9eda7ff2e091d25b8bad3f718684e1916d41bd56873cee05b697", size = 10999477, upload-time = "2026-07-16T15:13:23.318Z" }, - { url = "https://files.pythonhosted.org/packages/c9/10/74e53572aa758dfaa678c2a2646b5c5515d884b7ca56be4d2ce03ca4b560/ruff-0.15.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:11c1c715af53a09f714e011106bffc419751ec8232fcb5da42173284ea3fec6f", size = 10466716, upload-time = "2026-07-16T15:13:26.162Z" }, - { url = "https://files.pythonhosted.org/packages/1e/cc/44eaaf0844e028182f2d0a8f2190d0f359159aed0a9e5ab861d892f1ae2a/ruff-0.15.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742a29cf29bddb7c8327895d6a10e0e6c5b38a96dd407af9b5d0857f809c0576", size = 10892644, upload-time = "2026-07-16T15:13:29.229Z" }, - { url = "https://files.pythonhosted.org/packages/9f/21/8edf559014d2b0f82beea19cfb713993ad802ccda16868769979c6090a84/ruff-0.15.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72af58b951b0ae395935ae79763dc349bc0eb706319d28f7a33ad2cfb3cfc178", size = 10576719, upload-time = "2026-07-16T15:13:32.35Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1e/3a13abd392a3b50b62e5938a831f9ab6e588358cacad5c18545b716d2182/ruff-0.15.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d425005c1835eb24e2ee4161cb90e8db263415f4a71c8c72c33abaa6c0c224", size = 11376494, upload-time = "2026-07-16T15:13:35.958Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3e/422d3d95bcf04dd78e1aeac22184d4f9a8fb2c01865d39d44618484a0317/ruff-0.15.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b9b3f8779a4f08c969defc3c8c35abffaa757e601ed5ae66d6d1db6519969a", size = 12208370, upload-time = "2026-07-16T15:13:39.185Z" }, - { url = "https://files.pythonhosted.org/packages/1e/91/5d065a0e0a02bf4813f5119ad278462eed081d2b832eb7c021ade0ec9e65/ruff-0.15.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e0dd1b2e4d3d585f897a0d137cbf4eaf6223bef4e8ce34d6bb12556c5f9249e", size = 11581098, upload-time = "2026-07-16T15:13:42.132Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f9/a0d4871d12fae702eb1f41b686caf05f1f8b124dc6db6f784f53d74918fa/ruff-0.15.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365523eb91d9224e1bcb03b022fbf0facb8f9e23792a2c53d9d4b3924bdbdebb", size = 11399422, upload-time = "2026-07-16T15:13:45.2Z" }, - { url = "https://files.pythonhosted.org/packages/18/80/c843a5176cddbceb0b7e8dd41cf9993490796c1c469348d384f5a5c13c56/ruff-0.15.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fabfd168afdf29fee5be98b831efa9683c94d7c5a3b58b9ce5a2e38444589a74", size = 11381683, upload-time = "2026-07-16T15:13:48.46Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/8485de0ae92239438a36cfc51350db9b9e85c9ebdfaea91b18e422706662/ruff-0.15.22-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:225dbf095a87f1d9f90f5fd7924d2613ee452a75a4308c63a8f50f761787aa7c", size = 10850295, upload-time = "2026-07-16T15:13:51.655Z" }, - { url = "https://files.pythonhosted.org/packages/fa/91/24977ec2ec72eaf15e4394ace2959fdff2dd1e14f03e005e838023407169/ruff-0.15.22-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1877d63b9d24ed278744f1523fd11b85540566d54641f97c566d7d9dc5ca5296", size = 10579640, upload-time = "2026-07-16T15:13:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/9c/47/9b51216951974df1f263ac19da550d34252e0ed7218c25f10c5ef9ed7517/ruff-0.15.22-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1606c510bd7215680d32efab38965f7cdec3ef69f5170a3f4791404ffdd5262", size = 11105077, upload-time = "2026-07-16T15:13:57.915Z" }, - { url = "https://files.pythonhosted.org/packages/c2/47/20e9d4a3b8016778acea5fc32bb50d35d207500a17ddb529ffa6996feef8/ruff-0.15.22-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:630479b18625f5ffc373f77603a22a9f8ac0acd7ff0501178b5db28ec71e9c64", size = 11490980, upload-time = "2026-07-16T15:14:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/4d/76/3f72d8fc38c1cb77b38c56a70da9d0c17700cc1cc50f9649c9d3c8f5ba71/ruff-0.15.22-py3-none-win32.whl", hash = "sha256:e5ba0e4a13fd14abbed2a77b517a3911290c6c6c59ef67784328d1668fab76cf", size = 10789165, upload-time = "2026-07-16T15:14:04.16Z" }, - { url = "https://files.pythonhosted.org/packages/cb/46/4965251734c2b6fcdca1b1b187d20bcac3af0ee5b083b89c910bb961ce3a/ruff-0.15.22-py3-none-win_amd64.whl", hash = "sha256:9be63ba1eb936acd2d1342fb8337c356353706fce233b2a15a09a97037e6acde", size = 11938297, upload-time = "2026-07-16T15:14:07.316Z" }, - { url = "https://files.pythonhosted.org/packages/57/c9/e69b1ff4c8b69093ef08b8919ab767af0569666865b39c30a8795d88d3c6/ruff-0.15.22-py3-none-win_arm64.whl", hash = "sha256:e1168075b72158510839f250027659cdd78476f40507dd517892304c41318661", size = 11298172, upload-time = "2026-07-16T15:14:10.51Z" }, +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/94/1e5e4967626faf12fa56999cd6222dff6992ceb086ad7945756baf70c7a7/ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982", size = 4790557, upload-time = "2026-07-23T19:11:30.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/81/1c8818fee7ce1a04cd7d1b3172e0a8f8e4f1dc4feb7fc390e16daa8af323/ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e", size = 10754633, upload-time = "2026-07-23T19:10:46.415Z" }, + { url = "https://files.pythonhosted.org/packages/23/df/beaf59c09d68db84304d555f188b276a77132a5d5b0b67a5c762aa143628/ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522", size = 10969164, upload-time = "2026-07-23T19:10:50.271Z" }, + { url = "https://files.pythonhosted.org/packages/42/ce/741cd197496a1abbf51352710fd15ed995d2a2be87189c1da26a450d6e83/ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81", size = 10488846, upload-time = "2026-07-23T19:10:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/a2db8e88cade358f5cdcb05674a917751074109315d014eb6352d9a893f7/ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9", size = 10889729, upload-time = "2026-07-23T19:10:54.89Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/62a771694ebd63029dc953e27dbad40e1588bd4860ff9fe881018fddaa49/ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a", size = 10568275, upload-time = "2026-07-23T19:10:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e2/ced249fe8af5f086c5c58cc21cc3356d50f32f7401c5df87050c999620a7/ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717", size = 11385112, upload-time = "2026-07-23T19:10:59.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/05154977a8fd69eeb6c103271f55403bfd8711f5c0f8ed07489d95a504e7/ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17", size = 12207008, upload-time = "2026-07-23T19:11:02.154Z" }, + { url = "https://files.pythonhosted.org/packages/fb/29/98225831a3a1eab0e02f4acc6ca6559a98611dcc68b6965ff4b7234627c1/ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4", size = 11650842, upload-time = "2026-07-23T19:11:04.557Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/6bd3cf90500653d55dc0ffc8507aa8300bd49d0214b2e8cb4d3fef2943ba/ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b", size = 11400718, upload-time = "2026-07-23T19:11:09.233Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a2/a54eb4eae05d66364050a5d3b8a9c5ef88196531b3cbe7109d873f87f819/ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0", size = 11426177, upload-time = "2026-07-23T19:11:11.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/16e3eea4b2a478a496919f5e36f17c4559e54620bd3bbac5d6affa068006/ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213", size = 10856126, upload-time = "2026-07-23T19:11:14.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/252eb8b868a16eec7257c14f504f77537e734b2d69c762e639e588e304a3/ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af", size = 10571208, upload-time = "2026-07-23T19:11:16.378Z" }, + { url = "https://files.pythonhosted.org/packages/21/09/817a482f542f7570cbb4554b26e896610c7114f539b1d9e2d2145bf6bef6/ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09", size = 11063329, upload-time = "2026-07-23T19:11:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/2e/23/9403c180ca1cb9b1f7335f5c3e5305c09d49ea5b345196682a36028bde4a/ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed", size = 11489751, upload-time = "2026-07-23T19:11:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/1b2ef7bcde851c78d7f17f1cca13fd6dc695fc4b3d6197941e72cae5b132/ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb", size = 10785885, upload-time = "2026-07-23T19:11:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/d5e4ef7a56be3f928ffb90b94c25ba7d3cb9c7fe0736aeaaedf361770712/ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472", size = 11923141, upload-time = "2026-07-23T19:11:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9a/8415f2657cbe200f41a4531ccededf135505a92d4a012229121f885b26f9/ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d", size = 11273407, upload-time = "2026-07-23T19:11:28.705Z" }, ] [[package]] name = "s3transfer" -version = "0.19.1" +version = "0.19.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/65/da/4bef7ce7bb989b222aa4785a413896dbec53306dfc59c6ce7d16a7ffbd6a/s3transfer-0.19.1.tar.gz", hash = "sha256:d3d6371dc3f1e5c5427b2b457bcf13bcf87bec334c95aed18642eae61f6926f3", size = 165354, upload-time = "2026-07-10T19:32:04.849Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/43/35e4d8aa320bffe8287fe8f65f578fa2d2db0a64212f0e710dce58267854/s3transfer-0.19.2.tar.gz", hash = "sha256:ba0309fd86be3c27dbf78cdd813c13c5e1df16e5874b99d2535ebbdfb9892993", size = 165592, upload-time = "2026-07-22T19:30:44.432Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/24/23/e84c64ad0e8bc59cd1b2ef98def848deff0ef3456c542afe74d51e9e8c85/s3transfer-0.19.1-py3-none-any.whl", hash = "sha256:d5fd7005ee39307455ad5f310b5ea67f4b1960d7fed5b3671ee50c249de675de", size = 90072, upload-time = "2026-07-10T19:32:03.673Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e7/5c595c75e9f41a44f30e526eda465ea0b4eec93470e074e4a111b253f13a/s3transfer-0.19.2-py3-none-any.whl", hash = "sha256:d8168eccca828cbb2cd573675333f3bddd254313a9c42494b84c76b539e8ba25", size = 90216, upload-time = "2026-07-22T19:30:43.251Z" }, ] [[package]] @@ -2487,14 +2487,14 @@ wheels = [ [[package]] name = "tqdm" -version = "4.69.0" +version = "4.70.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/69/40407dfc835517f058b603dbf37a6df094d8582b015a51eddc988febbcb7/tqdm-4.69.0.tar.gz", hash = "sha256:700c5e85dcd5f009dd6222588a29180a193a748247a5d855b4d67db93d79a53b", size = 792569, upload-time = "2026-07-17T18:09:06.2Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/3b/6c24bec5be5e743ffd99576daa5cc077722fc7d5bbc00bd133fa0c698dc6/tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220", size = 795438, upload-time = "2026-07-27T11:33:15.271Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/21/99a0cdaf54eb35e77623c41b5a2c9472ee4404bba687052791fe2aba6773/tqdm-4.69.0-py3-none-any.whl", hash = "sha256:9979978912be667a6ef21fd5d8abf54e324e63d82f7f43c360792ebc2bc4e622", size = 676680, upload-time = "2026-07-17T18:09:04.172Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1c/01bfd571a64e7f270e6bab5e33777debe0edc56759233ce84f27dec92d14/tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953", size = 80184, upload-time = "2026-07-27T11:33:13.167Z" }, ] [[package]] @@ -2541,15 +2541,15 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.51.0" +version = "0.52.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/65/b7c6c443ccc58678c91e1e973bbe2a878591538655d6e1d47f24ba1c51f3/uvicorn-0.51.0.tar.gz", hash = "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0", size = 94412, upload-time = "2026-07-08T10:59:05.962Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/c8/2d307868453a4bca6e64fa3581d122ae0748a0869c53f159339def179c7c/uvicorn-0.52.0.tar.gz", hash = "sha256:ca8876ad6c1983f394157c168b39d52f6dd56dabf5602fa0982751cffc2293ae", size = 97504, upload-time = "2026-07-29T08:45:34.065Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z" }, + { url = "https://files.pythonhosted.org/packages/39/e6/b5c0630ace9757232aec07112be8146b812787db52141ff9d50674aa7634/uvicorn-0.52.0-py3-none-any.whl", hash = "sha256:3d887809810b89ed33501bcf0a9aba469b06ecd608158efce04bd6b48d8c9b08", size = 79058, upload-time = "2026-07-29T08:45:32.492Z" }, ] [[package]]