feat: 实现 AgentScope ReAct Runner 两阶段执行并重构事件处理

This commit is contained in:
zl-q
2026-03-16 09:01:01 +08:00
parent 072c09d99d
commit dcceb48d84
51 changed files with 5015 additions and 5663 deletions
+6 -2
View File
@@ -14,7 +14,9 @@ from schemas.agent.runtime_models import (
from schemas.agent.system_agent import AgentType, SystemAgentLLMConfig
from schemas.agent.ui_hints import (
UiHintAction,
UiHintBlock,
UiHintIntent,
UiHintSection,
UiHintStatus,
UiHintsPayload,
)
@@ -29,7 +31,9 @@ __all__ = [
"ToolStatus",
"UiMode",
"UiHintAction",
"UiHintBlock",
"UiHintIntent",
"UiHintSection",
"UiHintStatus",
"UiHintsPayload",
"WorkerAgentOutputLite",
"WorkerAgentOutputRich",
+183 -438
View File
@@ -1,10 +1,26 @@
"""
UiHints - 描述性 UI 提示
设计原则:
- 描述性而非渲染性: 告诉编译器“要展示什么”,而不是“如何渲染”
- 最小化 token: 保持字段简洁
- 可编译: 可机械转换为 UiSchemaRenderer
- 尽量无损: hints 中的主要内容字段应尽量被保留到 renderer 中
Version: 2.1
"""
from __future__ import annotations
from enum import Enum
from typing import Annotated, Any, Literal
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field
# ============================================================
# Enums
# ============================================================
class UiHintStatus(str, Enum):
INFO = "info"
@@ -14,6 +30,17 @@ class UiHintStatus(str, Enum):
PENDING = "pending"
class UiHintIntent(str, Enum):
"""主要展示意图(弱提示,不应决定字段生死)"""
MESSAGE = "message" # 普通消息/说明
DATA = "data" # 数据/结果摘要
LIST = "list" # 列表为主
STATUS = "status" # 状态结果为主
FORM = "form" # 结构化内容(当前不表示真实输入表单)
MIXED = "mixed" # 混合内容
class UiHintActionStyle(str, Enum):
PRIMARY = "primary"
SECONDARY = "secondary"
@@ -26,520 +53,238 @@ class UiHintTextFormat(str, Enum):
MARKDOWN = "markdown"
class UiHintContainerDirection(str, Enum):
VERTICAL = "vertical"
HORIZONTAL = "horizontal"
class UiHintActionType(str, Enum):
NAVIGATION = "navigation"
URL = "url"
EVENT = "event"
TOOL = "tool"
COPY = "copy"
PAYLOAD = "payload"
class UiHintKvLayout(str, Enum):
VERTICAL = "vertical"
HORIZONTAL = "horizontal"
GRID = "grid"
class UiHintIconSource(str, Enum):
ICON = "icon"
EMOJI = "emoji"
URL = "url"
class UiHintOperationType(str, Enum):
CREATE = "create"
UPDATE = "update"
DELETE = "delete"
EXECUTE = "execute"
# ============================================================
# Base Config
# ============================================================
class UiHintOperationResult(str, Enum):
SUCCESS = "success"
FAILURE = "failure"
PARTIAL = "partial"
class UiHintConfirm(BaseModel):
model_config = ConfigDict(extra="forbid")
title: str | None = Field(
default=None,
description="Optional confirmation dialog title.",
)
message: str | None = Field(
default=None,
description="Optional confirmation message shown before action execution.",
)
confirm_label: str | None = Field(
default=None,
alias="confirmLabel",
description="Optional confirm button label, e.g. 'Delete'.",
)
cancel_label: str | None = Field(
default=None,
alias="cancelLabel",
description="Optional cancel button label, e.g. 'Cancel'.",
class UiHintBaseModel(BaseModel):
model_config = ConfigDict(
extra="forbid",
populate_by_name=True,
)
class UiHintActionNavigation(BaseModel):
model_config = ConfigDict(extra="forbid")
# ============================================================
# Action Targets
# ============================================================
class UiHintActionNavigation(UiHintBaseModel):
type: Literal["navigation"]
path: str = Field(
...,
description="Internal route path to navigate to.",
)
params: dict[str, Any] | None = Field(
default=None,
description="Optional route params for internal navigation.",
)
path: str = Field(..., description="Internal route path.")
params: dict[str, Any] | None = Field(default=None, description="Route params.")
class UiHintActionUrl(BaseModel):
model_config = ConfigDict(extra="forbid")
class UiHintActionUrl(UiHintBaseModel):
type: Literal["url"]
url: str = Field(..., description="External URL to open.")
target: Literal["_self", "_blank"] | None = Field(
default=None,
description="Optional browser target for URL action.",
)
url: str = Field(..., description="External URL.")
target: Literal["_self", "_blank"] | None = Field(default=None)
class UiHintActionEvent(BaseModel):
model_config = ConfigDict(extra="forbid")
class UiHintActionEvent(UiHintBaseModel):
type: Literal["event"]
event: str = Field(
...,
description="Frontend domain event name, e.g. 'chat.retry'.",
)
payload: dict[str, Any] | None = Field(
default=None,
description="Optional event payload for frontend event handling.",
)
event: str = Field(..., description="Frontend event name.")
payload: dict[str, Any] | None = Field(default=None)
class UiHintActionTool(BaseModel):
model_config = ConfigDict(extra="forbid")
class UiHintActionTool(UiHintBaseModel):
type: Literal["tool"]
tool_id: str = Field(
alias="toolId",
description="Tool identifier used to trigger another tool execution.",
)
params: dict[str, Any] | None = Field(
default=None,
description="Optional parameters for tool re-execution.",
)
tool_id: str = Field(alias="toolId", description="Tool identifier.")
params: dict[str, Any] | None = Field(default=None)
class UiHintActionCopy(BaseModel):
model_config = ConfigDict(extra="forbid")
class UiHintActionCopy(UiHintBaseModel):
type: Literal["copy"]
content: str = Field(..., description="Text content to copy to clipboard.")
success_message: str | None = Field(
default=None,
alias="successMessage",
description="Optional user-facing success message after copy.",
)
content: str = Field(..., description="Content to copy.")
success_message: str | None = Field(alias="successMessage", default=None)
class UiHintActionPayload(BaseModel):
model_config = ConfigDict(extra="forbid")
class UiHintActionPayload(UiHintBaseModel):
type: Literal["payload"]
payload: dict[str, Any] = Field(
...,
description="Structured payload to submit to frontend or gateway.",
)
submit_to: str | None = Field(
default=None,
alias="submitTo",
description="Optional submit target path or endpoint key.",
)
payload: dict[str, Any] = Field(..., description="Structured payload.")
submit_to: str | None = Field(alias="submitTo", default=None)
UiHintActionTarget = Annotated[
(
UiHintActionNavigation
| UiHintActionUrl
| UiHintActionEvent
| UiHintActionTool
| UiHintActionCopy
| UiHintActionPayload
),
Field(discriminator="type"),
]
UiHintActionTarget = (
UiHintActionNavigation
| UiHintActionUrl
| UiHintActionEvent
| UiHintActionTool
| UiHintActionCopy
| UiHintActionPayload
)
class UiHintAction(BaseModel):
model_config = ConfigDict(
extra="forbid",
json_schema_extra={
"examples": [
{
"id": "action-open-calendar",
"label": "Open calendar",
"style": "primary",
"action": {"type": "navigation", "path": "/calendar"},
}
]
},
)
id: str | None = Field(
default=None,
description="Optional stable action id for tracking and targeting.",
)
label: str = Field(
...,
description="User-facing action label shown on button/link.",
)
style: UiHintActionStyle | None = Field(
default=None,
description="Optional semantic button style.",
)
disabled: bool = Field(
default=False,
description="Whether this action should be rendered as disabled.",
)
action: UiHintActionTarget = Field(
...,
description="Executable action target definition.",
)
confirm: UiHintConfirm | None = Field(
default=None,
description="Optional confirmation requirement before execution.",
)
class UiHintAction(UiHintBaseModel):
label: str = Field(..., description="Button label.")
style: UiHintActionStyle | None = Field(default=None, description="Button style.")
disabled: bool = Field(default=False, description="Disabled state.")
action: UiHintActionTarget = Field(..., description="Action to execute.")
class UiHintIcon(BaseModel):
model_config = ConfigDict(extra="forbid")
source: Literal["icon", "emoji", "url"] = Field(
...,
description="Icon source type.",
)
value: str = Field(
...,
description="Icon identifier, emoji text, or image URL based on source.",
)
color: str | None = Field(
default=None,
description="Optional semantic color hint. Do not encode pixel-level style rules.",
)
size: int | None = Field(
default=None,
description="Optional icon size hint in abstract UI units.",
)
# ============================================================
# Small Descriptive Models
# ============================================================
class UiHintBadge(BaseModel):
model_config = ConfigDict(extra="forbid")
label: str = Field(..., description="Badge text label.")
variant: Literal["default", "success", "warning", "error", "info"] = Field(
default="default",
description="Semantic badge variant.",
)
class UiHintIcon(UiHintBaseModel):
source: UiHintIconSource = Field(default=UiHintIconSource.ICON)
value: str = Field(..., description="Icon identifier / emoji / url.")
color: str | None = Field(default=None)
size: int | None = Field(default=None)
class UiHintKeyValuePair(BaseModel):
model_config = ConfigDict(extra="forbid")
key: str = Field(..., description="Stable key identifier for this pair.")
label: str | None = Field(
default=None,
description="Optional user-facing label. Fallback to key when missing.",
)
value: str | int | bool | None = Field(
default=None,
description="Scalar value for this key-value pair.",
)
copyable: bool = Field(
default=False,
description="Whether frontend may offer copy interaction for this value.",
)
class UiHintKvItem(UiHintBaseModel):
key: str = Field(..., description="Key identifier.")
label: str | None = Field(default=None, description="Display label.")
value: Any = Field(default=None, description="Value.")
copyable: bool = Field(default=False, description="Allow copy.")
class UiHintListItem(BaseModel):
model_config = ConfigDict(extra="forbid")
id: str | None = Field(
default=None,
description="Optional stable list item id.",
)
title: str = Field(..., description="Primary list item title.")
subtitle: str | None = Field(
default=None,
description="Optional short secondary text.",
)
description: str | None = Field(
default=None,
description="Optional detailed description for this item.",
)
icon: UiHintIcon | None = Field(
default=None,
description="Optional semantic icon metadata.",
)
badge: UiHintBadge | None = Field(
default=None,
description="Optional semantic badge metadata.",
)
metadata: dict[str, Any] = Field(
default_factory=dict,
description="Optional non-visual metadata for analytics or interactions.",
)
actions: list[UiHintAction] = Field(
default_factory=list,
description="Optional per-item actions, recommended up to 3.",
)
class UiHintListItem(UiHintBaseModel):
id: str | None = Field(default=None)
title: str = Field(..., description="Item title.")
subtitle: str | None = Field(default=None)
description: str | None = Field(default=None)
icon: UiHintIcon | None = Field(default=None)
status: UiHintStatus | None = Field(default=None)
actions: list[UiHintAction] = Field(default_factory=list)
class UiHintPagination(BaseModel):
model_config = ConfigDict(extra="forbid")
class UiHintSection(UiHintBaseModel):
title: str | None = Field(default=None, description="Section title.")
description: str | None = Field(default=None, description="Section description.")
icon: UiHintIcon | None = Field(default=None, description="Section icon.")
page: int = Field(..., description="Current page number starting from 1.")
page_size: int = Field(
alias="pageSize",
description="Page size used for this list page.",
)
total: int = Field(..., description="Total number of records.")
has_more: bool = Field(
alias="hasMore",
description="Whether there are more pages after current page.",
)
class UiHintBaseBlock(BaseModel):
model_config = ConfigDict(extra="forbid")
id: str | None = Field(
default=None,
description="Optional stable block id.",
)
title: str | None = Field(
default=None,
description="Optional block title.",
)
description: str | None = Field(
default=None,
description="Optional block description.",
)
status: UiHintStatus | None = Field(
default=None,
description="Optional semantic status for this block.",
)
actions: list[UiHintAction] = Field(
default_factory=list,
description="Optional block-level actions, recommended up to 3.",
)
class UiHintTextBlock(UiHintBaseBlock):
kind: Literal["text"]
content: str = Field(
...,
description="Main text content to present.",
)
format: UiHintTextFormat = Field(
content: str | None = Field(default=None, description="Main text content.")
content_format: UiHintTextFormat = Field(
default=UiHintTextFormat.PLAIN,
description="Text format: plain or markdown.",
alias="contentFormat",
description="Section content text format.",
)
class UiHintCardBlock(UiHintBaseBlock):
kind: Literal["card"]
children: list["UiHintBlock"] = Field(
items: list[UiHintKvItem] = Field(default_factory=list, description="KV items.")
list_items: list[UiHintListItem] = Field(
default_factory=list,
description="Nested child blocks grouped under this card.",
alias="listItems",
description="List items.",
)
actions: list[UiHintAction] = Field(default_factory=list, description="Actions.")
class UiHintKvBlock(UiHintBaseBlock):
kind: Literal["kv"]
pairs: list[UiHintKeyValuePair] = Field(
default_factory=list,
description="Key-value pairs to display.",
)
layout: UiHintKvLayout = Field(
default=UiHintKvLayout.VERTICAL,
description="Preferred semantic layout for key-value content.",
)
# ============================================================
# Root Payload
# ============================================================
class UiHintListBlock(UiHintBaseBlock):
kind: Literal["list"]
items: list[UiHintListItem] = Field(
default_factory=list,
description="List items to present.",
)
pagination: UiHintPagination | None = Field(
default=None,
description="Optional pagination metadata.",
)
empty_text: str | None = Field(
default=None,
alias="emptyText",
description="Optional message shown when list items are empty.",
)
class UiHintsPayload(UiHintBaseModel):
"""
描述性 UI 提示
设计目标:
- agent 输出尽可能短
- 不表达布局细节
- 编译器负责转换为完整 UiSchemaRenderer
"""
class UiHintOperationBlock(UiHintBaseBlock):
kind: Literal["operation"]
operation: UiHintOperationType = Field(
...,
description="Operation category: create/update/delete/execute.",
)
result: UiHintOperationResult = Field(
...,
description="Operation result: success/failure/partial.",
)
message: str | None = Field(
default=None,
description="Optional operation summary message.",
)
affected_count: int | None = Field(
default=None,
alias="affectedCount",
description="Optional affected record count.",
)
details: dict[str, Any] | None = Field(
default=None,
description="Optional machine-readable operation details.",
)
class UiHintErrorBlock(UiHintBaseBlock):
kind: Literal["error"]
error_code: str = Field(
alias="errorCode",
description="Stable error code for categorization.",
)
message: str = Field(
...,
description="Human-readable error message.",
)
retryable: bool = Field(
default=False,
description="Whether retry is likely to succeed.",
)
details: str | None = Field(
default=None,
description="Optional plain-text diagnostic details.",
)
suggestions: list[str] = Field(
default_factory=list,
description="Optional actionable suggestions, recommended up to 3.",
)
class UiHintContainerBlock(UiHintBaseBlock):
kind: Literal["container"]
direction: UiHintContainerDirection = Field(
default=UiHintContainerDirection.VERTICAL,
description="Child block layout direction.",
)
gap: int | None = Field(
default=None,
description="Optional semantic spacing hint between children.",
)
children: list["UiHintBlock"] = Field(
default_factory=list,
description="Nested child blocks in this container.",
)
class UiHintCustomBlock(UiHintBaseBlock):
kind: Literal["custom"]
renderer_key: str = Field(
alias="rendererKey",
description=(
"Custom semantic renderer key. Use only when standard block kinds "
"cannot represent the intent."
),
)
payload: dict[str, Any] = Field(
default_factory=dict,
description="Structured custom payload consumed by the renderer.",
)
UiHintBlock = Annotated[
(
UiHintTextBlock
| UiHintCardBlock
| UiHintKvBlock
| UiHintListBlock
| UiHintOperationBlock
| UiHintErrorBlock
| UiHintContainerBlock
| UiHintCustomBlock
),
Field(discriminator="kind"),
]
class UiHintsPayload(BaseModel):
model_config = ConfigDict(
extra="forbid",
populate_by_name=True,
json_schema_extra={
"examples": [
{
"version": "1.0",
"status": "info",
"title": "Schedule update",
"blocks": [
{
"kind": "text",
"content": "Your meeting is moved to 3:00 PM.",
"format": "plain",
},
{
"kind": "list",
"title": "Next steps",
"items": [
{"title": "Open calendar"},
{"title": "Notify attendees"},
],
},
"intent": "status",
"status": "success",
"title": "日程已创建",
"body": "本次创建已成功完成。",
"items": [
{"key": "title", "label": "主题", "value": "Q1 规划会议"},
{"key": "time", "label": "时间", "value": "2026-03-15 14:00"},
],
"actions": [
{
"label": "Open calendar",
"label": "查看详情",
"style": "primary",
"action": {"type": "navigation", "path": "/calendar"},
}
"action": {
"type": "navigation",
"path": "/calendar/evt_123",
},
},
{
"label": "删除",
"style": "danger",
"action": {
"type": "tool",
"toolId": "calendar.delete",
"params": {"eventId": "evt_123"},
},
},
],
"meta": {"source": "worker"},
}
]
},
)
version: str = Field(
default="1.0",
description="Ui hints payload version.",
version: str = Field(default="2.1")
intent: UiHintIntent = Field(
default=UiHintIntent.MESSAGE,
description="Primary display intent.",
)
status: UiHintStatus = Field(
default=UiHintStatus.INFO,
description="Overall semantic status for the full ui_hints payload.",
description="Overall status.",
)
title: str | None = Field(
default=None,
description="Optional top-level semantic title.",
title: str | None = Field(default=None, description="Top-level title.")
description: str | None = Field(default=None, description="Top-level description.")
body: str | None = Field(default=None, description="Top-level main body text.")
body_format: UiHintTextFormat = Field(
default=UiHintTextFormat.PLAIN,
alias="bodyFormat",
description="Body text format.",
)
description: str | None = Field(
default=None,
description="Optional top-level semantic description.",
)
blocks: list[UiHintBlock] = Field(
items: list[UiHintKvItem] = Field(
default_factory=list,
description="Main semantic content blocks.",
description="Top-level key-value items.",
)
list_items: list[UiHintListItem] = Field(
default_factory=list,
alias="listItems",
description="Top-level list items.",
)
sections: list[UiHintSection] = Field(
default_factory=list,
description="Grouped sections.",
)
actions: list[UiHintAction] = Field(
default_factory=list,
description="Optional top-level actions, recommended up to 3.",
description="Top-level actions.",
)
icon: UiHintIcon | None = Field(
default=None,
description="Top-level icon.",
)
meta: dict[str, Any] = Field(
default_factory=dict,
description="Optional non-visual metadata for tracing and integration.",
description="Extra meta, e.g. requestId/toolId/traceId/userId.",
)
UiHintCardBlock.model_rebuild()
UiHintContainerBlock.model_rebuild()
File diff suppressed because it is too large Load Diff