125 lines
4.5 KiB
Python
125 lines
4.5 KiB
Python
"""框架端口对象。
|
|
|
|
这些对象只描述一次 Agent 执行所需的通用材料,不加载 Muse 角色合同、数据库或业务状态。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Mapping
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FrameworkExecutionRequest:
|
|
"""已由业务侧解析完成的框架执行请求。"""
|
|
|
|
system_prompt: str
|
|
user_content: str
|
|
tool_allowlist: tuple[str, ...] = ()
|
|
timeout_seconds: float = 600.0
|
|
request_id: str | None = None
|
|
session_mode: str = "fresh"
|
|
|
|
def __post_init__(self) -> None:
|
|
if not isinstance(self.system_prompt, str) or not self.system_prompt.strip():
|
|
raise ValueError("system_prompt 必须是非空字符串")
|
|
if not isinstance(self.user_content, str) or not self.user_content.strip():
|
|
raise ValueError("user_content 必须是非空字符串")
|
|
if self.session_mode not in {"fresh", "continue"}:
|
|
raise ValueError("session_mode 必须是 fresh 或 continue")
|
|
if self.timeout_seconds <= 0:
|
|
raise ValueError("timeout_seconds 必须大于 0")
|
|
if any(not isinstance(name, str) or not name.strip() for name in self.tool_allowlist):
|
|
raise ValueError("tool_allowlist 必须是非空工具名数组")
|
|
|
|
def as_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"systemPrompt": self.system_prompt,
|
|
"userContent": self.user_content,
|
|
"toolAllowlist": list(self.tool_allowlist),
|
|
"timeoutSeconds": self.timeout_seconds,
|
|
"requestId": self.request_id,
|
|
"sessionMode": self.session_mode,
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FrameworkEvent:
|
|
"""框架原生事件的通用观察材料。"""
|
|
|
|
framework: str
|
|
framework_version: str
|
|
session_id: str | None
|
|
source_seq: int
|
|
kind: str
|
|
phase: str
|
|
safe_details: Mapping[str, Any] = field(default_factory=dict)
|
|
payload_sha256: str | None = None
|
|
source_event_id: str | None = None
|
|
run_id: str | None = None
|
|
observed_at: str = ""
|
|
|
|
def __post_init__(self) -> None:
|
|
if not self.framework or not self.framework_version:
|
|
raise ValueError("FrameworkEvent 缺少 framework/framework_version")
|
|
if self.source_seq < 1:
|
|
raise ValueError("FrameworkEvent source_seq 必须从 1 开始")
|
|
if self.kind not in {"session", "turn", "step", "model", "tool", "agent", "transport", "unknown"}:
|
|
raise ValueError(f"未知 FrameworkEvent kind: {self.kind}")
|
|
if self.phase not in {"started", "progress", "completed", "failed"}:
|
|
raise ValueError(f"未知 FrameworkEvent phase: {self.phase}")
|
|
if not self.observed_at:
|
|
raise ValueError("FrameworkEvent observed_at 不能为空")
|
|
|
|
def as_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"framework": self.framework,
|
|
"frameworkVersion": self.framework_version,
|
|
"sessionId": self.session_id,
|
|
"sourceSeq": self.source_seq,
|
|
"sourceEventId": self.source_event_id,
|
|
"runId": self.run_id,
|
|
"kind": self.kind,
|
|
"phase": self.phase,
|
|
"safeDetails": dict(self.safe_details),
|
|
"payloadSha256": self.payload_sha256,
|
|
"observedAt": self.observed_at,
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FrameworkExecutionResult:
|
|
"""框架执行结果;业务层仍需运行自己的 Schema 与主权门。"""
|
|
|
|
status: str
|
|
final_text: str | None
|
|
requested_model: str
|
|
actual_models: tuple[str, ...] = ()
|
|
session_id: str | None = None
|
|
artifact_locator: str | None = None
|
|
trace_digest: str | None = None
|
|
|
|
def __post_init__(self) -> None:
|
|
if self.status not in {"completed", "failed", "cancelled", "timed_out", "invalid_output"}:
|
|
raise ValueError(f"未知框架结果状态: {self.status}")
|
|
if not self.requested_model:
|
|
raise ValueError("FrameworkExecutionResult requested_model 不能为空")
|
|
|
|
def as_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"status": self.status,
|
|
"finalText": self.final_text,
|
|
"requestedModel": self.requested_model,
|
|
"actualModels": list(self.actual_models),
|
|
"sessionId": self.session_id,
|
|
"artifactLocator": self.artifact_locator,
|
|
"traceDigest": self.trace_digest,
|
|
}
|
|
|
|
|
|
__all__ = [
|
|
"FrameworkEvent",
|
|
"FrameworkExecutionRequest",
|
|
"FrameworkExecutionResult",
|
|
]
|