main: 增强 Agent Run 调度可靠性与幂等性

- 默认切换 AgentProvider 为 HttpAgentProvider,增强网络请求的容错和重试机制
- 优化 Run 逻辑,支持多场景去重与并发保护
- 添加 Redis 发布失败的日志记录以提升问题排查效率
- 扩展 OpenAPI 规范,新增 Error 和 Run 状态相关模型
- 增强测试覆盖,验证调度策略和重复请求的幂等性
- 增加数据库索引以优化查询性能
- 更新所有相关文档和配置文件
This commit is contained in:
2025-12-18 17:41:42 +08:00
parent 2ad101c297
commit 6d934f4e34
16 changed files with 634 additions and 118 deletions

View File

@@ -8,6 +8,14 @@
- 2025-02-14新增 ChatSession 创建、消息追加、增量查询接口;支持状态门禁与 dedupe 幂等。
- 2025-02-14MVP-1.1 增加会话列表、会话更新(重命名/状态变更),列表附带最后一条消息摘要。
- 2025-02-15Agent Run MVP-0 —— RunDispatcher + AgentRunJob + DummyProvider自动在 user.prompt 后触发一次 Run落地 run.status / agent.message。
- 2025-12-18Agent Run 可靠性增强 —— 并发幂等、终态去重、取消语义加强、Provider 超时/重试/错误归一SSE gap 回补与心跳。
## 本次变更摘要2025-12-18
- RunDispatcher 并发幂等:同 trigger_message_id 只产生一个 RUNNING且仅新建时 dispatch。
- RunLoop/OutputSink 幂等agent.message 与 run.status 采用 dedupe_key重复执行不重复写。
- Cancel 强化:多检查点取消,确保不落 agent.message 且落 CANCELED 终态。
- Provider 可靠性:超时/重试/429/5xx错误落库包含 retryable/http_status/provider/latency_ms。
- SSE 可靠性gap 触发回补心跳保活publish 异常不影响主流程。
## 领域模型
- `ChatSession``session_id`(UUID)、`session_name``status`(`OPEN`/`LOCKED`/`CLOSED`)、`last_seq`
@@ -141,7 +149,8 @@
- `id` 为消息 `seq`,便于续传;`data` 为消息 JSON同追加消息响应字段
- Backlog建立连接后先补发 `seq > after_seq` 的消息order asc最多 `limit` 条),再进入实时订阅。
- 实时Redis channel `session:{session_id}:messages` 发布消息 IDSSE 侧读取后按 seq 去重、推送。
- 心跳:周期输出 `: ping` 保活(生产环境)
- Gap 回补:若订阅推送的 seq 与 last_sent_seq 存在缺口,会主动回补 backlog
- 心跳:周期输出 `: ping` 保活。
- 错误401 未授权404 session 不存在。
## Agent Run MVP-0RunDispatcher + AgentRunJob
@@ -149,13 +158,14 @@
1. 用户追加 `role=USER && type=user.prompt`Controller 自动调用 `RunDispatcher->dispatchForPrompt`
2. 并发保护:同会话只允许一个 RUNNING同一个 `trigger_message_id` 幂等复用已有 `run_id`
3. 立即写入 `run.status`SYSTEM/run.statuspayload `{run_id,status:'RUNNING',trigger_message_id}`dedupe_key=`run:trigger:{message_id}`)。
4. 推送 `AgentRunJob(session_id, run_id)` 到队列(测试环境 QUEUE=sync 会同步执行)。
5. RunLoop使用 DummyAgentProvider
4. 仅在新建 RUNNING 时推送 `AgentRunJob(session_id, run_id)` 到队列(测试环境 QUEUE=sync 会同步执行)。
5. RunLoop默认 HttpAgentProvider未配置 endpoint 时回退 DummyAgentProvider
- 终态检测:若已 DONE/FAILED/CANCELED 则直接返回。
- Cancel 检查:存在 `run.cancel.request`(payload.run_id) 则写入 `run.status=CANCELED`,不产出 agent.message。
- ContextBuilder提取最近 20 条 USER/AGENT 消息type in user.prompt/agent.messageseq 升序提供给 Provider。
- Provider 返回一次性文本回复。
- OutputSink 依次写入:`agent.message`payload 含 run_id, provider`run.status=DONE`dedupe_key=`run:{run_id}:status:DONE`)。
6. 异常:AgentRunJob 捕获异常后写入 `error` + `run.status=FAILED`dedupe
- Provider 返回一次性文本回复(内置超时/重试/退避)
- OutputSink 依次写入:`agent.message`payload 含 run_id, providerdedupe_key=`run:{run_id}:agent:message`)、`run.status=DONE`dedupe_key=`run:{run_id}:status:DONE`)。
6. 异常:ProviderException 写入 `error` + `run.status=FAILED`dedupeerror payload 包含 retryable/http_status/provider/latency_ms
### Run 相关消息类型(落库即真相源)
| type | role | payload 关键字段 | 说明 |
@@ -163,7 +173,7 @@
| run.status | SYSTEM | run_id, status(RUNNING/DONE/CANCELED/FAILED), trigger_message_id?, error? | Run 生命周期事件CLOSED 状态下允许写入 |
| agent.message | AGENT | run_id, provider | Provider 的一次性回复 |
| run.cancel.request | USER/SYSTEM | run_id | CancelChecker 依据该事件判断是否中止 |
| error | SYSTEM | run_id, message | 任务异常时落库 |
| error | SYSTEM | run_id, message, retryable?, http_status?, provider?, latency_ms?, raw_message? | 任务异常时落库 |
### 触发 Run调试入口
- `POST /sessions/{session_id}/runs`

View File

@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: ChatSession & Message API
version: 1.1.0
version: 1.1.1
description: |
ChatSession & Message API含 Archive/GetMessage/SSE 与 Run 调度)。自然语言:中文。
servers:
@@ -15,6 +15,13 @@ tags:
- name: Run
description: Agent Run 调度
paths:
/test:
summary: 测试接口
get:
tags: [Test]
responses:
"200":
description: 成功
/sessions:
post:
tags: [ChatSession]
@@ -422,8 +429,13 @@ components:
type: string
nullable: true
payload:
type: object
nullable: true
oneOf:
- $ref: '#/components/schemas/RunStatusPayload'
- $ref: '#/components/schemas/AgentMessagePayload'
- $ref: '#/components/schemas/RunCancelPayload'
- $ref: '#/components/schemas/RunErrorPayload'
- type: object
reply_to:
type: string
format: uuid
@@ -470,6 +482,59 @@ components:
message:
type: string
example: Session is closed
RunStatusPayload:
type: object
properties:
run_id:
type: string
format: uuid
status:
type: string
enum: [RUNNING, DONE, FAILED, CANCELED]
trigger_message_id:
type: string
format: uuid
nullable: true
error:
type: string
nullable: true
AgentMessagePayload:
type: object
properties:
run_id:
type: string
format: uuid
provider:
type: string
RunCancelPayload:
type: object
properties:
run_id:
type: string
format: uuid
RunErrorPayload:
type: object
properties:
run_id:
type: string
format: uuid
message:
type: string
retryable:
type: boolean
nullable: true
http_status:
type: integer
nullable: true
provider:
type: string
nullable: true
latency_ms:
type: integer
nullable: true
raw_message:
type: string
nullable: true
PaginationLinks:
type: object
properties: