Skip to content

AG-UI - How to force a StateSnapshotEvent at the end of the agent execution? #2906

@danielb-gyde

Description

@danielb-gyde

Question

I'm defining a test state and a route inside our FastAPI backend via pydantic_ai.ag_ui.run_ag_ui

class TestState(BaseModel):
    userName: str
    counter: int


class TestStateSnapshot(BaseModel):
    testState: TestState = Field(
        default_factory=lambda: TestState(userName="Test User", counter=1),
        description="The current test state",
    )
@router.post("/chat")
async def run_agent(request: Request) -> Response:
    # [boilerplate]

    state = (
        TestStateSnapshot.model_validate(run_input.state)
        if run_input.state
        else TestStateSnapshot()
    )

    event_stream = run_ag_ui(
        main_agent,
        run_input,
        accept=accept,
        on_complete=on_request_complete,
        deps=StateDeps(state),
    )

    return StreamingResponse(event_stream, media_type=accept)

and I have a dummy-agent defining some tools and a sub-agent for agent orchestration:

main_agent = Agent(
    name="Routing Agent",
    model=model,
    system_prompt=MAIN_AGENT_PROMPT,
    deps_type=StateDeps[TestStateSnapshot],
)


@main_agent.instructions
def beg_llm_to_update_state(ctx: RunContext[StateDeps[TestStateSnapshot]]) -> str:
    return "always call update_state when you're done"


@main_agent.tool
async def roll_dice(ctx: RunContext[StateDeps[TestStateSnapshot]]) -> DiceAgentResponse:
    """Roll a six-sided die and return the random result."""
    result = 6
    ctx.deps.state.testState.counter = result
    return DiceAgentResponse(roll=result)


@main_agent.tool
async def update_state(
    ctx: RunContext[StateDeps[TestStateSnapshot]],
) -> StateSnapshotEvent:
    return StateSnapshotEvent(type=EventType.STATE_SNAPSHOT, snapshot=ctx.deps.state)


sub_agent = Agent(
    name="Pink Fluffy Unicorn agent",
    model=model, 
    deps_type=StateDeps[TestStateSnapshot],
)

@main_agent.tool
async def pink_fluffy_unicorns(ctx: RunContext[StateDeps[TestStateSnapshot]]):
'''Test agent that changes the count to 42 in one of it's tools'''
    response = await sub_agent.run(
        message_history=ctx.messages,
        deps=ctx.deps,
    )
    return response


@sub_agent.tool
async def get_pink_fluffy_unicorn_info(
    ctx: RunContext[StateDeps[TestStateSnapshot]], increment: int
):
    state = ctx.deps.state
    state.testState.counter = 42

In order to update the state and send it back to my frontend, main_agent needs to call the tool update_state at the end of it's execution. Here's the correct call stack:

 Routing Agent run
   chat gpt-4o
   running 2 tools
     running tool: roll_dice
     running tool: update_state
   chat gpt-4o

However, the update_state call is missing more often than not, even if I prompt the agent to do it. It seems to work more or less consistently if I specifically ask it in the chat. Here are some example screenshots:

Asking the agent to roll the dice, update_state is not called:

   running 1 tool
     running tool: roll_dice
   chat gpt-4o
Image

Asking the agent to roll the dice and then update the state:

 Routing Agent run
   chat gpt-4o
   running 2 tools
     running tool: roll_dice
     running tool: update_state
   chat gpt-4o
Image Obviously not what you'd want for a user-facing application

It gets even flimsier when I ask it to call a sub-agent. In this example update_state is called before the tool call that changes the state to 42.

 Routing Agent run
   chat gpt-4o
   running 2 tools
     running tool: pink_fluffy_unicorns
       Pink Fluffy Unicorn agent run
     running tool: update_state
       running tool: pink_fluffy_unicorns
         Pink Fluffy Unicorn agent run
         running 2 tools
           running tool: pink_fluffy_unicorns
           running tool: update_state
         chat gpt-4o
         running 1 tool
           running tool: get_pink_fluffy_unicorn_info
         chat gpt-4o
   chat gpt-4o

My Question(s):

  • Is updating the state via a tool call the only way to do it? Couldn't I send a custom event via run_ag_ui's on_complete callback parameter? Send custom events from tool function, for AG-UI and event_stream_handler #2382 (comment) <- this workaround seems to be the closest thing I've found so far
  • CopilotKit Inserts a SystemMessage at the beginning of the message history. How does that interact with Agents (and sub-agents)?
  • What's the best practice for this type of problem? (I don't really mind highly-opinionated takes on this)

If there's any input you could give me on this issue, I'd be stoked.

Additional Context

No response

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions