ARCHIVE / INITIALIZING000%
正在载入档案界面SYS.07
Interview Prep

Agent 运行时

结合 AgentOps 项目回答长任务状态、SSE 补放、Redis/SQLite 分工、取消语义与可审计 Trace。

Agent 运行时

当Agent任务变成长时间运行的异步任务时,连接、任务、事件和审计必须解耦。浏览器断线不应终止Worker;Redis消息已发布不表示事件可回放;普通日志也不足以完整复现一次回答的形成过程。

项目背景

AgentOps使用Go / Gin + CloudWeGo Eino编排故障诊断:Metrics Agent查Prometheus,Log Agent查Loki,Experience Agent查Neo4j、Milvus和CodeGraph MCP,Synthesizer基于Evidence Board生成RCA。任务通过Redis Stream调度,进度通过SSE推送,事实和审计数据落持久库。

AgentOps 多 Agent 故障诊断总链路

SSE断联处理

Agent诊断进度是Server → Browser单向流,因此SSE比WebSocket更符合需求:它复用HTTP语义,浏览器原生支持EventSource、文本事件和自动重连。但自动重连只能恢复连接,不能自动补回断线期间丢失的事件。

SSE 断联、补放与恢复实时流

每个事件至少包含:

id: 43
event: agent_done
data: {"incident_id":"inc-7","agent":"metrics","status":"done"}

客户端重连时发送Last-Event-ID。服务端正确恢复顺序是:

  1. 先订阅实时通道,将新事件暂存;
  2. 读取持久事件表当前Watermark;
  3. 补放(last_id, watermark];
  4. 按Event ID合并暂存事件并去重;
  5. 切入实时转发。

需要先订阅再补历史。如果先查历史、再订阅实时,两步之间会形成短暂的事件丢失窗口;该问题通常具有低频和时序相关特征,因而容易只在生产环境出现。

心跳用于尽早发现TCP已经断开,并推动代理层Flush;超过事件保留期则返回resync_required,让客户端调用GET /incidents/{id}拿当前快照。

连接生命周期不能绑定任务生命周期

SSE断开只表示用户暂时看不到进度,不等于用户取消任务。显式取消另走POST /incidents/{id}/cancel,API更新任务状态并通过context cancellation通知Worker。Worker在每个长工具调用和阶段边界检查Context,最终再发布cancelled事件。

Pub/Sub的可靠性边界

Redis Pub/Sub只管直播,不管回放;订阅者离线期间错过就是错过。可以采用三类设计:

  • SQLite/PostgreSQL Event Table + Pub/Sub:持久表补历史,Pub/Sub推实时;
  • Redis Stream:事件本身可按ID回放,并有Consumer Group;
  • Kafka:更长保留、多消费者与高吞吐,但运维成本更高。

无论哪种,客户端按Event ID幂等处理;服务端发布事件也要考虑数据库成功但实时通知失败,可用Outbox或让Stream成为单一事件源。

小规模Agent服务中的Redis与SQLite分工

Agent 服务 Redis 与 SQLite 状态边界

基本原则是:Redis保存短生命周期、高频访问且可重建的状态;SQLite保存需要持久化、查询和审计的事实。

Redis:热状态与协调

  • Session热状态、最近对话摘要、当前阶段,带TTL;
  • Redis Stream任务队列与Pending状态;
  • SSE实时Pub/Sub;
  • Model/Embedding缓存;
  • Rate Limit、Idempotency Key、短Lease;
  • 多实例需要共享但可由持久库恢复的运行态。

SQLite:事实、查询与审计

  • Incident、Task、Agent Step、Tool Call、Approval;
  • Trace、事件补放、Prompt和Model版本;
  • Eval Dataset、Gold Answer、Gold Evidence、评测结果;
  • 用户与工具配置、Manifest和索引Version;
  • 小规模文档Metadata与FTS5索引。

SQLite开WAL Mode后适合单机读多写少,但仍是单写者模型。多实例、高写并发和强HA需求出现后迁PostgreSQL。Blob、大段原始模型输出、图片放对象存储,数据库只存URI、Hash和Metadata。

Trace Recorder设计

普通日志记录离散事件,Trace用于还原一次RCA的完整因果链。每个Incident分配一个trace_id,并为Agent Step、Model Call、Tool Call和Retrieval分别建立Span。

Agent Trace Recorder 的 Span 与持久化路径

推荐字段:

trace_id, span_id, parent_span_id
incident_id, agent_name, step_index, span_type
tool_name, arguments_redacted, status, error_code
started_at, ended_at, latency_ms
model, prompt_version, input_tokens, output_tokens
retriever, raw_score, fused_rank, chunk_id, version_id
request_hash, response_hash, payload_uri

实现上通过Eino Callback或统一Tool Wrapper,在before / after / error三个阶段开闭Span。Trace Context放在context.Context传播,不使用全局变量;Tool无论成功、超时、拒绝还是异常都必须闭合Span。

Observability与Audit可靠性不同

普通观测Span可进入有界内存队列异步批量落库,队列满时允许采样或丢弃并报警;审批、外部写操作、权限拒绝和最终Evidence属于关键审计事件,不能只放进程内队列,应通过同事务Outbox或可靠消息源持久化。

大Response存入对象存储,Trace表保留Hash和URI。Prompt、Authorization、Cookie、Secret和用户隐私需要先脱敏。在线上下文只传递Summary与Evidence Pointer,避免将完整审计数据重新放入模型上下文。

最终应能完成:

Citation → Evidence → Retrieval Hit → Tool Call → Raw Data

故障场景测试

  • SSE在补放期间再次断线,客户端是否仍能按Event ID收敛;
  • Pub/Sub发布失败但事件表成功,重连能否拿到结果;
  • Worker重启后能否从持久状态恢复,而不是根据Prompt推断执行进度;
  • Tool超时、Context Cancel时Span是否闭合;
  • Trace队列满时是否影响主链路,关键Audit是否仍不丢;
  • 重复事件和乱序到达时前端状态机是否幂等。

项目回答模板

SSE只承载进度,不承载任务生命周期。每个事件有单调ID并先持久化,客户端用Last-Event-ID重连;服务端先订阅实时流,再按Watermark补历史并去重,避免补放与订阅之间的空窗。Redis保存热状态、队列和实时通知,SQLite保存Incident、Step、Trace与评测事实。Trace通过统一Wrapper记录Model、Tool、Retrieval Span,普通观测异步写,关键审批和副作用Audit走Outbox可靠持久化。

高频追问

  1. 为什么EventSource自动重连仍会丢事件?
  2. 先Replay再Subscribe有什么Race Condition?
  3. SSE断开为什么不能直接Cancel Worker?
  4. SQLite WAL Mode改善什么,又没有解决什么?
  5. Trace与普通业务日志的字段和用途有何区别?
  6. 为什么关键审计事件不能只写异步内存队列?