From 803166aae776e0a01512e108113c7b24fed0e975 Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Tue, 16 Jun 2026 15:40:09 +0200 Subject: [PATCH] use augmented messages for stateless runs --- src/_ravnar/api/agents.py | 3 +-- src/_ravnar/api/threads.py | 15 +++++++-------- src/_ravnar/core.py | 2 +- src/_ravnar/events.py | 5 +++-- src/_ravnar/schema/__init__.py | 2 ++ src/_ravnar/schema/api.py | 7 +++++++ tests/api/test_agents.py | 16 ++++++++++++++++ 7 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/_ravnar/api/agents.py b/src/_ravnar/api/agents.py index 5f529cd..a5fcab7 100644 --- a/src/_ravnar/api/agents.py +++ b/src/_ravnar/api/agents.py @@ -3,7 +3,6 @@ from collections.abc import AsyncIterator, Callable from typing import TYPE_CHECKING, Annotated, Any -import ag_ui.core import fastsse from fastapi import Depends, Path @@ -28,7 +27,7 @@ async def list_agents( async def create_stateless_run( *, agent_id: Annotated[str, Path(alias="agentId")], - run_agent_input: ag_ui.core.RunAgentInput, + run_agent_input: schema.AugmentedRunAgentInput, user: User = Depends(authorized_user_with("agents:read")), # noqa: B008 ) -> fastsse.Response: return await agent_handler.run(agent_id, run_agent_input, user=user) diff --git a/src/_ravnar/api/threads.py b/src/_ravnar/api/threads.py index af076e8..5600749 100644 --- a/src/_ravnar/api/threads.py +++ b/src/_ravnar/api/threads.py @@ -119,20 +119,19 @@ async def create_run( user_id=user.id, thread_id=thread_id, run_id=data.parent_run_id ) - augmented_messages_ta = pydantic.TypeAdapter(list[schema.AugmentedMessage]) - augmented_messages = augmented_messages_ta.validate_python(parent_messages, from_attributes=True) - augmented_messages.extend(data.messages) + messages = [ + *pydantic.TypeAdapter(list[schema.AugmentedMessage]).validate_python(parent_messages, from_attributes=True), + *data.messages, + ] - await hydrate_files(augmented_messages, user=user, file_handler=file_handler) + await hydrate_files(messages, user=user, file_handler=file_handler) - run_agent_input = ag_ui.core.RunAgentInput( + run_agent_input = schema.AugmentedRunAgentInput( thread_id=thread_id, run_id=data.id, parent_run_id=parent_run.id if parent_run is not None else None, state=parent_run.state if parent_run is not None else None, - messages=pydantic.TypeAdapter(list[ag_ui.core.Message]).validate_python( - augmented_messages_ta.dump_python(augmented_messages) - ), + messages=messages, tools=data.tools, context=data.context, forwarded_props=data.forwarded_props, diff --git a/src/_ravnar/core.py b/src/_ravnar/core.py index bd9b97b..b671263 100644 --- a/src/_ravnar/core.py +++ b/src/_ravnar/core.py @@ -203,7 +203,7 @@ def _sse_encoder(self, data: fastsse.Data) -> bytes: async def run( self, agent_id: str, - run_agent_input: ag_ui.core.RunAgentInput, + run_agent_input: schema.AugmentedRunAgentInput, *, user: User, callback: Callable[[EventProcessor], Awaitable[None]] | None = None, diff --git a/src/_ravnar/events.py b/src/_ravnar/events.py index 0847da5..10f1e29 100644 --- a/src/_ravnar/events.py +++ b/src/_ravnar/events.py @@ -15,6 +15,7 @@ import structlog from opentelemetry import trace +from _ravnar import schema from _ravnar.file_storage import WrappedMetadata from _ravnar.observability import LazyValue @@ -74,7 +75,7 @@ class ReasoningData: class EventProcessor: - def __init__(self, *, run_agent_input: ag_ui.core.RunAgentInput): + def __init__(self, *, run_agent_input: schema.AugmentedRunAgentInput): self._run_agent_input = run_agent_input self._state = run_agent_input.state @@ -94,7 +95,7 @@ def __init__(self, *, run_agent_input: ag_ui.core.RunAgentInput): parent_run_id=run_agent_input.parent_run_id, ) - def _convert_input_messages(self, messages: list[ag_ui.core.Message]) -> dict[str, orm.Message]: + def _convert_input_messages(self, messages: list[schema.AugmentedMessage]) -> dict[str, orm.Message]: message_uids = {m.id: uuid.uuid4() for m in messages} tool_calls = { diff --git a/src/_ravnar/schema/__init__.py b/src/_ravnar/schema/__init__.py index 151ee3a..abc041a 100644 --- a/src/_ravnar/schema/__init__.py +++ b/src/_ravnar/schema/__init__.py @@ -7,6 +7,7 @@ "AugmentedDeveloperMessage", "AugmentedMessage", "AugmentedReasoningMessage", + "AugmentedRunAgentInput", "AugmentedSystemMessage", "AugmentedToolMessage", "AugmentedUserMessage", @@ -34,6 +35,7 @@ AugmentedDeveloperMessage, AugmentedMessage, AugmentedReasoningMessage, + AugmentedRunAgentInput, AugmentedSystemMessage, AugmentedToolMessage, AugmentedUserMessage, diff --git a/src/_ravnar/schema/api.py b/src/_ravnar/schema/api.py index 5275d5f..a422ec7 100644 --- a/src/_ravnar/schema/api.py +++ b/src/_ravnar/schema/api.py @@ -8,6 +8,7 @@ "AugmentedDeveloperMessage", "AugmentedMessage", "AugmentedReasoningMessage", + "AugmentedRunAgentInput", "AugmentedSystemMessage", "AugmentedToolMessage", "AugmentedUserMessage", @@ -16,6 +17,7 @@ "DeleteThreadsData", "Event", "QuickPrompt", + "RegisterAgentData", "RenameThreadData", "Run", "Thread", @@ -176,6 +178,11 @@ class AugmentedReasoningMessage(AugmentedMessageMixin, ag_ui.core.ReasoningMessa Field(discriminator="role"), ] + +class AugmentedRunAgentInput(ag_ui.core.RunAgentInput): + messages: list[AugmentedMessage] # type: ignore[assignment] + + Event = Annotated[ag_ui.core.Event, Field(title="Event")] diff --git a/tests/api/test_agents.py b/tests/api/test_agents.py index cef4bb6..0e62e6b 100644 --- a/tests/api/test_agents.py +++ b/tests/api/test_agents.py @@ -281,3 +281,19 @@ def test_register_agent_with_denied_env_var(self, client): ) assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "Invalid configuration" + + +class TestStatelessRun: + def test_smoke(self, app_client): + app_client.post( + f"/api/agents/{app_client.any_agent_id}/run", + json={ + "threadId": "thread-id", + "runId": "run-id", + "state": {}, + "messages": [{"id": "message-id", "role": "user", "content": "hello"}], + "tools": [], + "context": [], + "forwardedProps": {}, + }, + ).raise_for_status()