mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 22:11:58 +00:00
155 lines
9.0 KiB
Python
155 lines
9.0 KiB
Python
from __future__ import annotations
|
||
|
||
import time
|
||
import uuid
|
||
from enum import StrEnum
|
||
from typing import Any
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
class EventType(StrEnum):
|
||
"""前端与后端之间的实时协议。画布高亮、时间线、token 流全部基于这套事件。"""
|
||
|
||
# run 级
|
||
RUN_STARTED = "run.started"
|
||
RUN_FINISHED = "run.finished"
|
||
RUN_FAILED = "run.failed"
|
||
RUN_CANCELLED = "run.cancelled"
|
||
RUN_INTERRUPTED = "run.interrupted" # 等待人工介入
|
||
RUN_RESUMED = "run.resumed"
|
||
|
||
# 节点级
|
||
NODE_STARTED = "node.started"
|
||
NODE_FINISHED = "node.finished"
|
||
NODE_FAILED = "node.failed"
|
||
NODE_SKIPPED = "node.skipped"
|
||
EDGE_TAKEN = "edge.taken" # 条件分支实际走了哪条边
|
||
|
||
# 模型级
|
||
LLM_START = "llm.start"
|
||
LLM_TOKEN = "llm.token"
|
||
# 思考的流式增量:只推给画布做实时显示,不落库
|
||
LLM_THINKING_DELTA = "llm.thinking.delta"
|
||
# 一次完整思考:调用结束时落一条,这才是进轨迹的事件
|
||
LLM_THINKING = "llm.thinking"
|
||
LLM_END = "llm.end"
|
||
|
||
# 工具 / 沙箱
|
||
TOOL_START = "tool.start"
|
||
TOOL_END = "tool.end"
|
||
TOOL_ERROR = "tool.error"
|
||
# 信任档是「始终允许 · 门控把关」的工具,门控模型对这一次调用的判定(tools/trust.py)。
|
||
# allow 之后是正常的 tool.start;escalate 之后是人工审批,协作团队里则是不执行
|
||
TOOL_GATED = "tool.gated"
|
||
# 知识检索。以前只发一条 info 日志,于是"这句结论依据的是哪份文档的哪一段"
|
||
# 在轨迹里查不到——而 SQL 取数那条路早就有工件快照可以下钻
|
||
RETRIEVE_END = "retrieve.end"
|
||
# 记忆读写。以前发的是 info 日志,而解码器丢弃所有 info——于是往长期记忆里
|
||
# 写东西这件事在界面上是隐形的。让 Copilot 主动记之后,这条不可接受
|
||
MEMORY_END = "memory.end"
|
||
# 合并查询做完了:哪几个输入(别名、节点、行数、数据源)、哪条合并 SQL、得到几行、有哪些警告。
|
||
# 运行详情据此展示这一步;完整结果在 query_artifact 指向的查询快照里
|
||
MERGE_END = "merge.end"
|
||
# 分析卡里的一项做完了(engine/nodes/analysis.py):哪一项、什么分析、状态、判定、判定语、结论工件(analysis_artifact)。
|
||
# 运行详情据此展示这一步,运行对比按项对齐两次的判定;整份结论在工件里。agent 分析工具的结论记在 tool.end 上
|
||
ANALYSIS_END = "analysis.end"
|
||
SANDBOX_START = "sandbox.start"
|
||
SANDBOX_END = "sandbox.end"
|
||
|
||
# 多 agent 协作中的单个 agent 步骤
|
||
AGENT_STEP_START = "agent.step.start"
|
||
AGENT_STEP_END = "agent.step.end"
|
||
# 调度者的一次决策。它是一次结构化模型调用,常常占掉团队节点大半的时间;
|
||
# 以前前后都没有事件,界面上那几十秒是黑盒
|
||
AGENT_ROUTE_START = "agent.route.start"
|
||
AGENT_ROUTE_END = "agent.route.end"
|
||
|
||
# 人工介入
|
||
HUMAN_REQUESTED = "human.requested"
|
||
HUMAN_RESOLVED = "human.resolved"
|
||
# 审批质询:运行停在审批时,审批人就待审的报告问的一个问题和得到的回答(engine/run_append.py 追加,
|
||
# node_id 为空)。这次问答的用量写在事件数据的 usage 里,计进运行用量(runner._usage_of_events)。
|
||
# 停在审批时追加的随运行结束一起封存;封存之后追加的带 post_seal: true,是批注不是证据
|
||
APPROVAL_INQUIRY = "approval.inquiry"
|
||
|
||
# 出具与口径治理
|
||
ISSUANCE = "issuance" # 出具契约的判定结果(档位 / 缺数据 / 未回指数字)
|
||
CALIBER_UPGRADE = "caliber.upgrade" # 钉住的方法卡有新版本,按声明的策略处置
|
||
# 从发布版本发起的正式运行:发布之后这一版用到的表的数据目录有变化(data/catalog_impact.catalog_drift)。
|
||
# 每张表带 impact:direct 写死的 SQL 用到的,possible Agent 可能查询的。
|
||
# 只提醒、不拦运行;落在封存范围内,出具物上看得到「当时的目录和发布时不一样」
|
||
CATALOG_DRIFT = "catalog.drift"
|
||
# 从发布版本发起的正式运行:发布之后这一版直接引用的表的结构版本变了(data/catalog_impact.schema_drift,next-round
|
||
# 03 B1)。每张表带发布时和现在的结构版本、是否已删除、还没处理的结构变化。和 catalog.drift 分开记(老的渲染只认整数的
|
||
# 目录版本),界面上合到同一块横幅里。只提醒、不拦运行;落在封存范围内
|
||
SCHEMA_DRIFT = "schema.drift"
|
||
# 报告撰写节点的自查结果:报告文档工件 id、统计、违规清单。落在封存范围内,
|
||
# 证据接口靠它找到文档——不去翻不受封存保护的 artifacts 表
|
||
REPORT_CHECKED = "report.checked"
|
||
# 探索运行里按需裁判的结论句判定:点开哪句才判哪句,追加在封存之后(post_seal),是批注不是证据。
|
||
# 封存核对只覆盖 manifest_seq 之前的事件,追加它不影响 verify;封存的报告文档一个字都不改
|
||
EVIDENCE_JUDGED = "evidence.judged"
|
||
# 口径账本(docs/design/next-round/02-口径账本.md 做法 2):探索运行封存之后,后台拿这次执行的 SQL 和已发布口径的取数比对,
|
||
# 结果存成内容寻址工件(kind=caliber_match,格式 agentlab.caliber_match/1),再追加这一条:{artifact, manifest_hash,
|
||
# algo, counts, trigger}。追加在封存之后(post_seal),是批注不是证据:比对依据的已发布口径在运行之外,不能混进运行
|
||
# 自身的封存事实;封存核对照旧一致
|
||
LEDGER_MATCHED = "ledger.matched"
|
||
# 口径召回(02 做法 7):口径发布新版本后,在同一份数据上把新旧两版各算一次(召回运行,origin_kind=recall)。召回运行
|
||
# 结束时在它自己身上记一条结论 {role: recall, changed, workflow_id, from_version, to_version, metrics, answers, tasks};
|
||
# 数没变的那几个原答案的运行上各记一条 {role: answer, changed: false, recall_run, workflow_id, from_version,
|
||
# to_version},界面上写「已按 vN 核对,不影响」(数变了的开 recall 待办,不在原答案上记)。同 ledger.matched:追加在
|
||
# 封存之后(post_seal),是批注不是证据,不进封存范围
|
||
LEDGER_RECALLED = "ledger.recalled"
|
||
# 答案依据的东西变了(02 做法 7 二、三):kind=catalog 目录里影响数字的一项被驳回或改了(冻结的目录里看得到这一项);
|
||
# structure 用到的表或列被删、列的类型变了;values 触及有效记录条件的新码值;catalog_edit 处理变化时改了目录。带数据源、
|
||
# 表、哪几项(路径和叫法),按新目录跑 SQL 检查新出现的错误级问题条数。同上,封存之后追加,不进封存范围
|
||
LEDGER_BASIS_CHANGED = "ledger.basis_changed"
|
||
# 对账结果上的「说明有用 / 看不懂」(02 做法 10 的 E2,ledger/feedback):追加在对账运行(origin_kind=reconcile)上,
|
||
# {verdict: useful | unclear, by, verified, source_run}。同一个人改主意再记一条,统计只算他最后一次。同上,封存之后
|
||
# 追加,是批注不是证据
|
||
LEDGER_FEEDBACK = "ledger.feedback"
|
||
|
||
# 应用平台(docs/design/app-platform-and-look.md §3.11)
|
||
# 运行来源:发起时写进 launch_run 的 notes,在封存范围内(engine/origin.py)
|
||
RUN_ORIGIN = "run.origin"
|
||
# 待办:一次中断面向人的那一面(app/process/tasks.py)
|
||
TASK_OPENED = "task.opened"
|
||
TASK_RESPONDED = "task.responded" # 谁、怎么决定的、第几人 / 共几人
|
||
TASK_REASSIGNED = "task.reassigned"
|
||
TASK_REMINDED = "task.reminded"
|
||
TASK_ESCALATED = "task.escalated"
|
||
TASK_EXPIRED = "task.expired"
|
||
TASK_RESOLVED = "task.resolved"
|
||
TASK_CANCELLED = "task.cancelled"
|
||
# 动作:写外部系统(app/process/actions.py)。prepared 带 payload_hash、环境、遮罩后的预览
|
||
ACTION_PREPARED = "action.prepared"
|
||
ACTION_SENT = "action.sent"
|
||
ACTION_CONFIRMED = "action.confirmed"
|
||
ACTION_FAILED = "action.failed"
|
||
ACTION_UNKNOWN = "action.unknown"
|
||
ACTION_RECONCILED = "action.reconciled"
|
||
ACTION_SIMULATED = "action.simulated"
|
||
|
||
# 普通用户建应用(docs/design/normal-user-apps.md N9、§3.8)
|
||
# 读入一个附件的文字(模板函数 file_text,app/files/text.py):哪个文件、读入多少字、全文多少字、是否截断,
|
||
# 读入的那一段存成工件(artifact)。抽不出时 ok=false、reason 写原因
|
||
FILE_EXTRACTED = "file.extracted"
|
||
|
||
# 其他
|
||
LOG = "log"
|
||
USAGE = "usage" # token / 成本累计
|
||
|
||
|
||
class RunEventModel(BaseModel):
|
||
id: str = Field(default_factory=lambda: uuid.uuid4().hex)
|
||
run_id: str
|
||
seq: int = 0
|
||
type: EventType
|
||
node_id: str | None = None
|
||
ts: float = Field(default_factory=time.time)
|
||
data: dict[str, Any] = Field(default_factory=dict)
|
||
|
||
def to_wire(self) -> dict[str, Any]:
|
||
return self.model_dump(mode="json")
|