Summary
With RestateAgent(..., event_stream_handler=<async fn>, auto_wrap_tools=True), the first tool call of a run fails the invocation with:
TypeError: Object of type coroutine is not JSON serializable
Text-only runs work fine — the crash only happens once the model calls a tool, because that's when wrapped_event_stream_handler journals tool-side events.
Environment
- restate-sdk 1.0.3 (bug also present on current
main) - pydantic-ai-slim 2.4.0
- Python 3.12, Restate server 1.7.2
Root cause
restate/ext/pydantic/_agent.py (line 171 on main), in wrapped_event_stream_handler:
awaitcontext.run_typed("run event", lambda: fn(ctx, single_event()))fn is the user's event_stream_handler, which is an async function. The lambda wrapping it is a plain sync callable, so in server_context.create_run_coroutine:
ifinspect.iscoroutinefunction(action): # False for the lambdaaction_result=awaitaction()
else:
...runinexecutor...
action_result=awaitaction_result_future# <- the un-awaited coroutine objectbuffer=serde.serialize(action_result) # TypeError: coroutine is not JSON serializable
The handler coroutine is never awaited (so the user's handler never runs for tool events), and Restate then tries to JSON-serialize the coroutine object itself.
Reproduction
importrestatefrompydantic_aiimportAgentfromrestate.ext.pydanticimportRestateAgentagent=Agent("openai:gpt-4o")
@agent.tool_plaindefget_weather(city: str) ->str:
return"sunny"svc=restate.Service("Repro")
@svc.handler()asyncdefrun(ctx: restate.Context, prompt: str) ->str:
asyncdefon_events(run_ctx, events):
asyncfor_inevents:
passragent=RestateAgent(agent, event_stream_handler=on_events, auto_wrap_tools=True)
result=awaitragent.run(prompt)
returnresult.outputapp=restate.app([svc])Invoke with a prompt that triggers the tool, e.g. "What's the weather in Hanoi? Use the tool." → the run event journal entry fails with the TypeError above and the invocation retries forever.
Traceback
File ".../restate/server_context.py", line 857, in create_run_coroutine
buffer = serde.serialize(action_result)
File ".../restate/serde.py", line 317, in serialize
return json.dumps(obj).encode("utf-8")
...
TypeError: Object of type coroutine is not JSON serializable
Suggested fix
Use an async closure so create_run_coroutine awaits it:
asyncforeventinstream:
asyncdefsingle_event():
yieldeventasyncdefdeliver() ->None:
awaitfn(ctx, single_event())
awaitcontext.run_typed("run event", deliver)Related
Summary
With
RestateAgent(..., event_stream_handler=<async fn>, auto_wrap_tools=True), the first tool call of a run fails the invocation with:Text-only runs work fine — the crash only happens once the model calls a tool, because that's when
wrapped_event_stream_handlerjournals tool-side events.Environment
main)Root cause
restate/ext/pydantic/_agent.py(line 171 on main), inwrapped_event_stream_handler:fnis the user'sevent_stream_handler, which is an async function. Thelambdawrapping it is a plain sync callable, so inserver_context.create_run_coroutine:The handler coroutine is never awaited (so the user's handler never runs for tool events), and Restate then tries to JSON-serialize the coroutine object itself.
Reproduction
Invoke with a prompt that triggers the tool, e.g.
"What's the weather in Hanoi? Use the tool."→ therun eventjournal entry fails with the TypeError above and the invocation retries forever.Traceback
Suggested fix
Use an async closure so
create_run_coroutineawaits it:Related
request_stream), reported separately.auto_wrap_tools+ an actual tool call) seems to not be covered by tests yet.