Skip to content

feat(agent): support structured output in streamEvents - #2919

Open
Letter2025 wants to merge 1 commit into
agentscope-ai:mainfrom
Letter2025:feat/streamEvents-structured-output
Open

feat(agent): support structured output in streamEvents#2919
Letter2025 wants to merge 1 commit into
agentscope-ai:mainfrom
Letter2025:feat/streamEvents-structured-output

Conversation

@Letter2025

Copy link
Copy Markdown

Problem

The fine-grained streaming API (ReActAgent#streamEvents(...)) — the recommended replacement for the deprecated coarse-grained stream(...) — has no structured-output overloads. Only call(msgs, schema/class, ctx) (non-streaming) and the deprecated stream(msgs, options, schema/class, ctx) support outputSchema.

buildAgentStream(...) is private, so downstream code cannot inject a schema variant of streamEvents without forking the framework or relying on the deprecated API (which returns the legacy io.agentscope.core.agent.Event instead of the fine-grained io.agentscope.core.event.AgentEvent).

Closes #1717.

Change

Add the missing fine-grained overloads, reusing the existing buildAgentStream core so streaming and blocking calls share identical lifecycle / middleware / hook semantics:

  • ReActAgent#streamEvents(List<Msg>, Class<?>, RuntimeContext)
  • ReActAgent#streamEvents(List<Msg>, JsonNode, RuntimeContext)
  • HarnessAgent#streamEvents(List<Msg>, Class<?>, RuntimeContext)
  • HarnessAgent#streamEvents(List<Msg>, JsonNode, RuntimeContext)

Each overload simply routes m -> doCall(m, schema/class) through buildAgentStream, exactly mirroring call(msgs, schema/class, ctx)callInternal(msgs, ctx, m -> doCall(m, schema/class)). This means:

  • native path: the model's response_format (strict json_schema) is preferred when supportsNativeStructuredOutput() is true; the JSON text surfaces as TextBlockDeltaEvents.
  • fallback path: when native is unsupported, the synthetic generate_response tool is injected and its ToolCallStartEvent / ToolCallDeltaEvent / tool-result events surface in the stream.
  • the final AgentResultEvent carries a Msg with hasStructuredData() == true plus aggregated token usage, semantically identical to the blocking call(...) result.

Tests

  • ReActAgentStreamEventsStructuredOutputTest (core): class/JsonNode overloads, fallback tool events + final structured result + usage, native TextBlockDeltaEvent JSON deltas, and semantic equivalence with call(...).
  • HarnessAgentStructuredOutputStreamEventsTest (harness): both overloads delegate structured output through the harness sandbox-lifecycle wrapper.

Notes

Add fine-grained structured-output overloads to the v2 streaming API so callers can stream validated structured JSON without depending on the deprecated coarse-grained stream(...) methods.

- ReActAgent.streamEvents(List, Class, RuntimeContext)
- ReActAgent.streamEvents(List, JsonNode, RuntimeContext)
- HarnessAgent mirrors both overloads with sandbox-lifecycle semantics

Both reuse the existing buildAgentStream core with doCall(m, schema/class), so the native response_format path and the synthetic generate_response fallback behave identically to call(msgs, schema/class, ctx). The final AgentResultEvent carries the validated structured Msg (hasStructuredData() == true) plus aggregated token usage.

Closes agentscope-ai#1717
@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@dailingtao dailingtao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2.0版本streamEvents增加结构化输出参数

2 participants