Files
EvoScientist-Multi/EvoScientist/stream/emitter.py
T
Ziheng Zhang a7d3ef8087 fix: subagent summerize (#83)
* fix: subagent summerize

* fix: group subagent text by agent name for parallel fallback

The flat subagent_text_buffer list would interleave text from parallel
sub-agents into incoherent output. Replace with a dict grouped by
agent name so each sub-agent's text stays coherent, with [name]:
attribution when multiple agents contribute.

Add comprehensive tests for the new behavior (24 tests).

* fix: group subagent text by agent name for parallel fallback

The flat subagent_text_buffer list would interleave text from parallel
sub-agents into incoherent output. Replace with a dict grouped by
agent name so each sub-agent's text stays coherent, with [name]:
attribution when multiple agents contribute.

Add comprehensive tests for the new behavior (24 tests).

* fix: group subagent text by agent name for parallel fallback

The flat subagent_text_buffer list would interleave text from parallel
sub-agents into incoherent output. Replace with a dict grouped by
agent name so each sub-agent's text stays coherent, with [name]:
attribution when multiple agents contribute.

Add comprehensive tests for the new behavior (24 tests).

* chore: fix multiple agent

* test: add test

* fix linter

* remove redundant
2026-03-20 17:13:27 +00:00

183 lines
5.2 KiB
Python

"""
StreamEventEmitter - standardized event format.
All events contain a type and associated data dict.
"""
from dataclasses import dataclass
from typing import Any
@dataclass
class StreamEvent:
"""Unified stream event."""
type: str
data: dict[str, Any]
class StreamEventEmitter:
"""Stream event emitter - creates standardized event dicts."""
@staticmethod
def thinking(content: str, thinking_id: int = 0) -> StreamEvent:
"""Thinking content event."""
return StreamEvent(
"thinking", {"type": "thinking", "content": content, "id": thinking_id}
)
@staticmethod
def text(content: str) -> StreamEvent:
"""Text content event."""
return StreamEvent("text", {"type": "text", "content": content})
@staticmethod
def tool_call(name: str, args: dict[str, Any], tool_id: str = "") -> StreamEvent:
"""Tool call event."""
return StreamEvent(
"tool_call",
{"type": "tool_call", "name": name, "args": args, "id": tool_id},
)
@staticmethod
def tool_result(name: str, content: str, success: bool = True) -> StreamEvent:
"""Tool result event."""
return StreamEvent(
"tool_result",
{
"type": "tool_result",
"name": name,
"content": content,
"success": success,
},
)
@staticmethod
def subagent_start(name: str, description: str) -> StreamEvent:
"""Sub-agent delegation started."""
return StreamEvent(
"subagent_start",
{
"type": "subagent_start",
"name": name,
"description": description,
},
)
@staticmethod
def subagent_tool_call(
subagent: str, name: str, args: dict[str, Any], tool_id: str = ""
) -> StreamEvent:
"""Tool call from inside a sub-agent."""
return StreamEvent(
"subagent_tool_call",
{
"type": "subagent_tool_call",
"subagent": subagent,
"name": name,
"args": args,
"id": tool_id,
},
)
@staticmethod
def subagent_tool_result(
subagent: str, name: str, content: str, success: bool = True
) -> StreamEvent:
"""Tool result from inside a sub-agent."""
return StreamEvent(
"subagent_tool_result",
{
"type": "subagent_tool_result",
"subagent": subagent,
"name": name,
"content": content,
"success": success,
},
)
@staticmethod
def subagent_text(
subagent: str, content: str, instance_id: str = ""
) -> StreamEvent:
"""Text content from a sub-agent (for fallback extraction)."""
return StreamEvent(
"subagent_text",
{
"type": "subagent_text",
"subagent": subagent,
"content": content,
"instance_id": instance_id,
},
)
@staticmethod
def subagent_end(name: str) -> StreamEvent:
"""Sub-agent delegation completed."""
return StreamEvent("subagent_end", {"type": "subagent_end", "name": name})
@staticmethod
def done(response: str = "") -> StreamEvent:
"""Done event."""
return StreamEvent(
"done", {"type": "done", "content": response, "response": response}
)
@staticmethod
def usage_stats(input_tokens: int, output_tokens: int) -> StreamEvent:
"""Token usage statistics event."""
return StreamEvent(
"usage_stats",
{
"type": "usage_stats",
"input_tokens": input_tokens,
"output_tokens": output_tokens,
},
)
@staticmethod
def interrupt(
interrupt_id: str,
action_requests: list,
review_configs: list | None = None,
) -> StreamEvent:
"""Human-in-the-loop interrupt event."""
return StreamEvent(
"interrupt",
{
"type": "interrupt",
"interrupt_id": interrupt_id,
"action_requests": action_requests,
"review_configs": review_configs or [],
},
)
@staticmethod
def ask_user_interrupt(
interrupt_id: str,
questions: list,
tool_call_id: str = "",
) -> StreamEvent:
"""Agent-initiated ask_user interrupt event."""
return StreamEvent(
"ask_user",
{
"type": "ask_user",
"interrupt_id": interrupt_id,
"questions": questions,
"tool_call_id": tool_call_id,
},
)
@staticmethod
def summarization(content: str) -> StreamEvent:
"""Context summarization event."""
return StreamEvent(
"summarization", {"type": "summarization", "content": content}
)
@staticmethod
def error(message: str) -> StreamEvent:
"""Error event."""
return StreamEvent("error", {"type": "error", "message": message})