agentscope-ai / agentscope-ai/agentscope-java

对AgentEvent解析的封装

未关闭
#1,643 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
area/core/agent enhancement
主要语言
Java
星标
5.6k
派生
1.3k
平均合并
4 天 12 小时
30 天内合并 PR
77

描述

v2.0.0-RC1的版本,没有找到对AgentEvent的解析封装。
python版本的Msg这个类提供了append_event这个方法,这点很方便人使用。

```python
def append_event(self, event: AgentEvent) -> Self:
"""Update the message by applying a streaming event.

Mutates ``self.content``, ``self.finished_at``, and ``self.usage``:
content blocks are appended/updated by block-level events,
``finished_at`` is stamped by ``REPLY_END``, and ``usage`` is
initialized then accumulated across each ``MODEL_CALL_END``.
Events whose ``reply_id`` does not match ``self.id`` are skipped with
a warning. Block-level delta/end events whose target block cannot be
found are also skipped with a warning.

Args:
event (`AgentEvent`):
The event to apply.
"""
from ..event import EventType # local import to avoid circular dep

if event.reply_id != self.id:
logger.warning(
"Event %s with reply_id %r does not match message id %r, "
"skipping.",
event.__class__.__name__,
event.reply_id,
self.id,
)
return self

match event.type:
case EventType.REPLY_END:
self.finished_at = event.created_at

case EventType.MODEL_CALL_END:
if self.usage is None:
self.usage = Usage(
input_tokens=event.input_tokens,
output_tokens=event.output_tokens,
)
else:
self.usage.input_tokens += event.input_tokens
self.usage.output_tokens += event.output_tokens

case EventType.TEXT_BLOCK_START:
self.content.append(TextBlock(id=event.block_id, text=""))

case EventType.TEXT_BLOCK_DELTA:
block = self._find_block("text", event.block_id)
if block is None:
logger.warning(
"TextBlock %r not found, skipping.",
event.block_id,
)
else:
block.text += event.delta

case EventType.TEXT_BLOCK_END:
pass

case EventType.DATA_BLOCK_START:
self.content.append(
DataBlock(
id=event.block_id,
source=Base64Source(
data="",
media_type=event.media_type,
),
),
)

case EventType.DATA_BLOCK_DELTA:
block = self._find_block("data", event.block_id)
if block is None:
logger.warning(
"DataBlock %s not found, skipping.",
event.block_id,
)
elif event.data:
block.source.data += event.data

case EventType.DATA_BLOCK_END:
pass

case EventType.THINKING_BLOCK_START:
self.content.append(
ThinkingBlock(id=event.block_id, thinking=""),
)

case EventType.THINKING_BLOCK_DELTA:
block = self._find_block("thinking", event.block_id)
if block is None:
logger.warning(
"ThinkingBlock %r not found, skipping.",
event.block_id,
)
else:
block.thinking += event.delta

case EventType.THINKING_BLOCK_END:
pass

case EventType.HINT_BLOCK:
# One-shot event — the full HintBlock content arrives in
# a single event, so just append it to ``content`` for
# persistence and replay.
self.content.append(
HintBlock(
id=event.block_id,
source=event.source,
hint=event.hint,
),
)

case EventType.TOOL_CALL_START:
self.content.append(
ToolCallBlock(
id=event.tool_call_id,
name=event.tool_call_name,
input="",
),
)

case EventType.TOOL_CALL_DELTA:
block = self._find_block("tool_call", event.tool_call_id)
if block is None:
logger.warning(
"ToolCallBlock %r not found, skipping.",
event.tool_call_id,
)
else:
assert isinstance(block, ToolCallBlock)
block.input += event.delta

case EventType.TOOL_CALL_END:
pass

case EventType.TOOL_RESULT_START:
self.content.append(
ToolResultBlock(
id=event.tool_call_id,
name=event.tool_call_name,
output=[],
state=ToolResultState.RUNNING,
),
)

case EventType.TOOL_RESULT_TEXT_DELTA:
block = self._find_block("tool_result", event.tool_call_id)
if block is None:
logger.warning(
"ToolResultBlock %r not found, skipping.",
event.tool_call_id,
)
else:
assert isinstance(block, ToolResultBlock)
if isinstance(block.output, str):
block.output = [TextBlock(text=block.output)]
# Append the text
if not block.output or block.output[-1].type != "text":
block.output.append(TextBlock(text=event.delta))
else:
block.output[-1].text += event.delta

case EventType.TOOL_RESULT_DATA_DELTA:
block = self._find_block("tool_result", event.tool_call_id)
if block is None:
logger.warning(
"ToolResultBlock %r not found, skipping.",
event.tool_call_id,
)
else:
assert isinstance(block, ToolResultBlock)
if isinstance(block.output, str):
block.output = [TextBlock(text=block.output)]
src = (
Base64Source(
data=event.data,
media_type=event.media_type,
)
if event.data is not None
else URLSource(
url=str(event.url),
media_type=event.media_type,
)
)
block.output.append(
DataBlock(id=event.block_id, source=src),
)

case EventType.TOOL_RESULT_END:
block = self._find_block("tool_result", event.tool_call_id)
if block is None:
logger.warning(
"ToolResultBlock %r not found, skipping.",
event.tool_call_id,
)
else:
assert isinstance(block, ToolResultBlock)
block.state = event.state
# The paired ToolCallBlock's lifecycle ends with its
# result — flip it to FINISHED here so the SSE-rebuilt
# reply_msg matches ``agent.state.context``, which
# ``_update_tool_call_state`` mutates directly.
call_block = self._find_block("tool_call", event.tool_call_id)
if call_block is not None:
assert isinstance(call_block, ToolCallBlock)
call_block.state = ToolCallState.FINISHED

case EventType.REQUIRE_USER_CONFIRM:
for tool_call in event.tool_calls:
b = self._find_block("tool_call", tool_call.id)
if b is not None:
assert isinstance(b, ToolCallBlock)
# Update the state
b.state = ToolCallState.ASKING
# Record the suggestions
b.suggested_rules = tool_call.suggested_rules

case EventType.USER_CONFIRM_RESULT:
for result in event.confirm_results:
b = self._find_block("tool_call", result.tool_call.id)
if b is not None:
assert isinstance(b, ToolCallBlock)
b.state = (
ToolCallState.ALLOWED
if result.confirmed
else ToolCallState.FINISHED
)

case EventType.REQUIRE_EXTERNAL_EXECUTION:
for tool_call in event.tool_calls:
b = self._find_block("tool_call", tool_call.id)
if b is not None:
assert isinstance(b, ToolCallBlock)
b.state = ToolCallState.SUBMITTED

case EventType.EXTERNAL_EXECUTION_RESULT:
for result in event.execution_results:
self.content.append(result)

return self
```

后面会出类似这种封装吗?还是说现在就有呢?

贡献指南

打开贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。