Files
ars-backend/docs/ChatSession/chat-session-api.md
Roog 6d934f4e34 main: 增强 Agent Run 调度可靠性与幂等性
- 默认切换 AgentProvider 为 HttpAgentProvider,增强网络请求的容错和重试机制
- 优化 Run 逻辑,支持多场景去重与并发保护
- 添加 Redis 发布失败的日志记录以提升问题排查效率
- 扩展 OpenAPI 规范,新增 Error 和 Run 状态相关模型
- 增强测试覆盖,验证调度策略和重复请求的幂等性
- 增加数据库索引以优化查询性能
- 更新所有相关文档和配置文件
2025-12-18 17:41:42 +08:00

11 KiB
Raw Blame History

ChatSession & Message APIMVP-1 + Agent Run MVP-0

基地址:http://localhost:8000/apiFrankenPHP 容器 8000 端口)
认证方式JWTAuthorization: Bearer {token}
自然语言:中文

变更记录

  • 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 异常不影响主流程。

领域模型

  • ChatSessionsession_id(UUID)、session_namestatus(OPEN/LOCKED/CLOSED)、last_seq
  • Messagemessage_id(UUID)、session_idrole(USER/AGENT/TOOL/SYSTEM)、type(字符串)、contentpayload(json)、seq(会话内递增)、reply_to(UUID)、dedupe_key
  • 幂等:UNIQUE (session_id, dedupe_key);同一 dedupe_key 返回已有消息。
  • 状态门禁:CLOSED 禁止追加,例外 role=SYSTEM && type in [run.status, error]LOCKED 禁止 role=USER && type=user.prompt
  • 会话缓存:chat_sessions.last_message_id 记录最后一条消息;appendMessage 事务内同步更新 last_seqlast_message_idupdated_at

接口

创建会话

  • POST /sessions

  • 请求体字段

    字段 必填 类型 说明
    session_name string(≤255) 会话名称
  • 响应 201JSON

    | 字段 | 类型 | 说明 | | --- | --- | --- | | session_id | uuid | 主键 | | session_name | string|null | 会话名 | | status | enum | OPEN|LOCKED|CLOSED | | last_seq | int | 当前最大 seq | | last_message_id | uuid|null | 最后一条消息 | | created_at, updated_at | datetime | 时间戳 |

  • 错误401 未授权

追加消息

  • POST /sessions/{session_id}/messages
  • 请求体字段
    字段 必填 类型 说明
    role enum `USER
    type string(≤64) user.prompt/agent.message
    content string 文本内容
    payload object jsonb 结构
    reply_to uuid 引用消息
    dedupe_key string(≤128) 幂等键
  • 响应 201JSON
    字段:message_id, session_id, seq, role, type, content, payload, reply_to, dedupe_key, created_at
  • 幂等:同 session + dedupe_key 返回已存在的消息(同 message_id/seq)。
  • 错误401 未授权403 违反状态门禁CLOSED 禁止LOCKED 禁止 user.prompt404 session 不存在422 校验失败。

按序增量查询

  • GET /sessions/{session_id}/messages?after_seq=0&limit=50
  • 查询参数
    参数 默认 类型 说明
    after_seq 0 int 仅返回 seq 大于该值
    limit 50 int(≤200) 返回数量上限
  • 响应 200data 数组,元素字段同“追加消息”响应。
  • 错误401/404/422

会话列表

  • GET /sessions?page=1&per_page=15&status=OPEN&q=keyword
  • 查询参数
    参数 默认 类型 说明
    page 1 int 分页页码
    per_page 15 int(≤100) 分页大小
    status - enum 过滤 `OPEN
    q - string ILIKE 模糊匹配 session_name
  • 响应 200分页结构data/links/metadata 每项字段:
    字段 类型 说明
    session_id uuid 会话主键
    session_name string null
    status enum `OPEN
    last_seq int 当前最大 seq
    last_message_id uuid null
    last_message_at datetime null
    last_message_preview string content 截断 120空内容返回空字符串
    last_message_role string null
    last_message_type string null
    created_at, updated_at datetime 时间戳
  • 排序:updated_at DESC
  • 错误401/422

会话更新

  • PATCH /sessions/{session_id} 请求体(至少一项,否则 422
    字段 必填 类型 说明
    session_name string 1..255 自动 trim
    status enum `OPEN
  • 规则:
    • CLOSED 不可改回 OPEN(返回 403
    • 任意更新都会刷新 updated_at
  • 响应 200字段同“会话列表”项。
  • 错误401 未授权403 状态门禁404 session 不存在422 校验失败。

获取会话详情

  • GET /sessions/{session_id}
  • 响应 200字段同“会话列表”项。
  • 错误401 未授权404 session 不存在。

归档会话Archive

  • POST /sessions/{session_id}/archive
  • 行为:将 status 置为 CLOSED,更新 updated_at,幂等(重复归档返回当前状态)。
  • 响应 200字段同“会话列表”项status=CLOSED
  • 错误401 未授权404 session 不存在。

获取单条消息(带会话校验)

  • GET /sessions/{session_id}/messages/{message_id}
  • 行为:校验 message.session_id 与路径参数一致,否则 404。
  • 响应 200字段同“追加消息”响应。
  • 错误401 未授权404 不存在或不属于该会话。

SSE 实时增量

  • GET /sessions/{session_id}/sse?after_seq=123
  • 头部:Accept: text/event-stream,可带 Last-Event-ID(优先于 query用于断线续传。
  • 查询参数
    参数 默认 类型 说明
    after_seq 0 int backlog 起始 seq若有 Last-Event-ID 则覆盖)
    limit 200 int(≤500) backlog 最多条数
  • SSE 输出格式:
    id: {seq}
    event: message
    data: {...message json...}
    
    
    • id 为消息 seq,便于续传;data 为消息 JSON同追加消息响应字段
  • Backlog建立连接后先补发 seq > after_seq 的消息order asc最多 limit 条),再进入实时订阅。
  • 实时Redis channel session:{session_id}:messages 发布消息 IDSSE 侧读取后按 seq 去重、推送。
  • Gap 回补:若订阅推送的 seq 与 last_sent_seq 存在缺口,会主动回补 backlog。
  • 心跳:周期输出 : ping 保活。
  • 错误401 未授权404 session 不存在。

Agent Run MVP-0RunDispatcher + AgentRunJob

流程概述

  1. 用户追加 role=USER && type=user.promptController 自动调用 RunDispatcher->dispatchForPrompt
  2. 并发保护:同会话只允许一个 RUNNING同一个 trigger_message_id 幂等复用已有 run_id
  3. 立即写入 run.statusSYSTEM/run.statuspayload {run_id,status:'RUNNING',trigger_message_id}dedupe_key=run:trigger:{message_id})。
  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.messagepayload 含 run_id, providerdedupe_key=run:{run_id}:agent:message)、run.status=DONEdedupe_key=run:{run_id}:status:DONE)。
  6. 异常ProviderException 写入 error + run.status=FAILEDdedupeerror payload 包含 retryable/http_status/provider/latency_ms。

Run 相关消息类型(落库即真相源)

type role payload 关键字段 说明
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, retryable?, http_status?, provider?, latency_ms?, raw_message? 任务异常时落库

触发 Run调试入口

  • POST /sessions/{session_id}/runs
  • 请求体字段
    字段 必填 类型 说明
    trigger_message_id uuid 通常为 user.prompt 消息 ID
  • 行为:同 trigger_message_id 幂等;若已有 RUNNING 则复用其 run_id。
  • 响应 201{ run_id }
  • 错误401 未授权404 session 不存在或 trigger_message 不属于该 session。

cURL 示例

# 创建会话
SESSION_ID=$(curl -s -X POST http://localhost:8000/api/sessions \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"session_name":"Demo"}' | jq -r '.data.session_id')

# 追加消息(支持 dedupe_key 幂等)
curl -s -X POST http://localhost:8000/api/sessions/$SESSION_ID/messages \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"role":"USER","type":"user.prompt","content":"hello","dedupe_key":"k1"}'

# 增量查询
curl -s "http://localhost:8000/api/sessions/$SESSION_ID/messages?after_seq=0&limit=50" \
  -H "Authorization: Bearer $TOKEN"

# 归档
curl -X POST http://localhost:8000/api/sessions/$SESSION_ID/archive \
  -H "Authorization: Bearer $TOKEN"

# 获取单条消息
curl -s http://localhost:8000/api/sessions/$SESSION_ID/messages/{message_id} \
  -H "Authorization: Bearer $TOKEN"

# SSE断线续传可带 Last-Event-ID
curl -N http://localhost:8000/api/sessions/$SESSION_ID/sse?after_seq=10 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: text/event-stream"

# 手动触发 Run调试用实际 user.prompt 会自动触发)
curl -s -X POST http://localhost:8000/api/sessions/$SESSION_ID/runs \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"trigger_message_id":"'$MESSAGE_ID'"}'