Files
ars-backend/docs/ChatSession/chat-session-api.md
ROOG c55534ad20 main: 扩展 Agent Run 调度与队列功能
- 增加 Agent Run MVP-0,包括 RunDispatcher 和 AgentRunJob
- 优化队列配置,支持 Redis 队列驱动,添加 Horizon 容器
- 更新 Docker 配置,细化角色分工,新增 Horizon 配置
- 增加测试任务 `TestJob`,扩展队列使用示例
- 更新 OpenAPI 规范,添加 Agent Run 相关接口及示例
- 编写文档,详细描述 Agent Run 流程与 MVP-0 功能
- 优化相关服务与文档,支持队列与异步运行
2025-12-17 02:39:31 +08:00

213 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ChatSession & Message APIMVP-1 + Agent Run MVP-0
基地址:`http://localhost:8000/api`FrankenPHP 容器 8000 端口)
认证方式JWT`Authorization: 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。
## 领域模型
- `ChatSession``session_id`(UUID)、`session_name``status`(`OPEN`/`LOCKED`/`CLOSED`)、`last_seq`
- `Message``message_id`(UUID)、`session_id``role`(`USER`/`AGENT`/`TOOL`/`SYSTEM`)、`type`(字符串)、`content``payload`(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_seq``last_message_id``updated_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|AGENT|TOOL|SYSTEM` |
| 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) | 返回数量上限 |
- 响应 200`data` 数组,元素字段同“追加消息”响应。
- 错误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|LOCKED|CLOSED` |
| q | - | string | ILIKE 模糊匹配 session_name |
- 响应 200分页结构`data/links/meta``data` 每项字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| session_id | uuid | 会话主键 |
| session_name | string|null | 名称 |
| status | enum | `OPEN|LOCKED|CLOSED` |
| 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|LOCKED|CLOSED` |
- 规则:
- `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 去重、推送。
- 心跳:周期输出 `: ping` 保活(生产环境)。
- 错误401 未授权404 session 不存在。
## Agent Run MVP-0RunDispatcher + AgentRunJob
### 流程概述
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
- 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
### 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 | 任务异常时落库 |
### 触发 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 示例
```bash
# 创建会话
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'"}'
```