Files
ars-backend/docs/ChatSession/chat-session-api.md
ROOG 59d4831f00 main: 增强工具调用与消息流程
- 支持 tool.call 和 tool.result 消息类型处理
- 引入 Tool 调度与执行逻辑,支持超时与结果截断
- 增加 ToolRegistry 和 ToolExecutor 管理工具定义与执行
- 更新上下文构建与消息映射逻辑,适配工具闭环处理
- 扩展配置与环境变量,支持 Tool 调用相关选项
- 增强单元测试覆盖工具调用与执行情景
- 更新文档和 OpenAPI,新增工具相关说明与模型定义
2025-12-22 12:36:59 +08:00

12 KiB
Raw Permalink 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-19AgentProvider Streaming 接入 —— ProviderEvent 统一事件流,新增 message.delta 输出与 OpenAI-compatible 适配器。
  • 2025-12-21Tool 子 Run 模式 —— Provider 支持 tool.delta→tool.call父 Run 调度子 Run 执行工具并写入 tool.result。

本次变更摘要2025-12-21

  • RunDispatcher 并发幂等:同 trigger_message_id 只产生一个 RUNNING且仅新建时 dispatch。
  • RunLoop/OutputSink 幂等agent.message、run.status、tool.call、tool.result 均采用 dedupe_key。
  • Cancel 强化:多检查点取消,确保不落 agent.message 且落 CANCELED 终态;父 Run 取消会终止等待的子 Run。
  • Provider 可靠性:超时/重试/429/5xx错误落库包含 retryable/http_status/provider/latency_ms。
  • StreamingAgentProvider 产出 message.delta / tool.delta / donefinish_reason=tool_calls 会触发子 Run 执行工具。
  • 工具闭环tool.callrole=AGENT落库→子 Run 调度→tool.resultrole=TOOL回灌→进入下一轮 LLM。

领域模型

  • 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
  • 工具消息:tool.callrole=AGENT携带 tool_call_id/name/argumentstool.resultrole=TOOL携带 parent_run_id/run_id/status/result

接口

创建会话

  • 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/message.delta
    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 以 Streaming 事件流产出文本增量message.delta
    • OutputSink 持续写入 message.delta,最终写入 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 的一次性回复
message.delta AGENT run_id, delta_index Provider 的增量输出Streaming
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'"}'