聊天与消息
本文档介绍 MateClaw 的聊天系统架构、消息流转机制、SSE 流式传输以及会话管理。
消息流转架构
用户发送消息后,经过以下链路处理:
用户输入 → ChannelAdapter → AgentService → StateGraph → LLM → 流式响应- ChannelAdapter 接收消息(Web / 钉钉 / 飞书等),统一封装为内部消息格式
- AgentService 加载 Agent 配置,初始化 AgentState
- StateGraph 根据 Agent 类型(ReAct / Plan-and-Execute)编排执行流程
- LLM 调用大模型生成回复,通过 SSE 实时推送给客户端
发送消息
REST 接口
http
POST /api/v1/chat/{agentId}/message
Content-Type: application/json
Authorization: Bearer <token>
{
"content": "帮我查一下今天的天气",
"conversationId": "conv-abc123"
}响应示例:
json
{
"code": 200,
"data": {
"messageId": "msg-xyz789",
"conversationId": "conv-abc123",
"content": "正在为您查询天气...",
"role": "assistant"
}
}SSE 流式端点
对于需要实时流式输出的场景,使用 SSE(Server-Sent Events):
http
GET /api/v1/chat/{agentId}/stream?conversationId=conv-abc123
Authorization: Bearer <token>客户端接收到的事件流格式:
event: message
data: {"type":"text","content":"今天"}
event: message
data: {"type":"text","content":"北京"}
event: message
data: {"type":"text","content":"天气晴朗"}
event: tool_call
data: {"type":"tool_call","name":"web_search","arguments":{"query":"北京天气"}}
event: tool_result
data: {"type":"tool_result","name":"web_search","result":"..."}
event: done
data: {"type":"done","messageId":"msg-xyz789"}前端使用 EventSource 或 fetch API 接收流式响应:
javascript
const eventSource = new EventSource(
`/api/v1/chat/${agentId}/stream?conversationId=${convId}`,
{ headers: { Authorization: `Bearer ${token}` } }
)
eventSource.addEventListener('message', (e) => {
const data = JSON.parse(e.data)
// 追加到消息气泡
})
eventSource.addEventListener('done', () => {
eventSource.close()
})文件与目录上传
上传方式
聊天界面支持三种文件上传方式:
| 方式 | 说明 |
|---|---|
| 点击选择 | 点击附件按钮选择文件 |
| 粘贴 | 从剪贴板粘贴文件(Ctrl+V / Cmd+V) |
| 拖拽 | 将文件或文件夹拖入聊天区域 |
拖拽上传
拖入文件或文件夹到聊天区域时,会出现半透明遮罩提示。松手后:
- 普通文件 — 上传到服务端,作为附件发送
- 文件夹(Electron Desktop) — 记录本地绝对路径作为引用,Agent 通过 ReadFileTool / ShellExecuteTool 直接访问本地文件
- 文件夹(Web 浏览器) — 递归展开目录内所有文件,逐个上传
上传限制
| 配置项 | 默认值 |
|---|---|
| 单文件大小上限 | 100 MB |
| 单次请求上限 | 200 MB |
| 文件类型 | 不限(*/*) |
上传 API
http
POST /api/v1/chat/upload
Content-Type: multipart/form-data
file: <文件>
conversationId: <会话 ID>返回附件元数据(fileName、storedName、url、path、contentType、size),附件在发送消息时作为 contentParts 的一部分传递给 Agent。
会话模型
会话(Conversation)
存储在 mate_conversation 表:
| 字段 | 说明 |
|---|---|
id | 主键 |
agent_id | 关联的 Agent |
user_id | 所属用户 |
title | 会话标题(自动生成或手动设置) |
create_time | 创建时间 |
update_time | 最后更新时间 |
deleted | 逻辑删除标记 |
消息(Message)
存储在 mate_message 表:
| 字段 | 说明 |
|---|---|
id | 主键 |
conversation_id | 所属会话 |
role | 角色:user / assistant / system / tool |
content | 消息内容 |
tool_calls | 工具调用记录(JSON) |
token_count | Token 数量 |
create_time | 创建时间 |
会话历史管理
MateClaw 提供完整的会话 CRUD 操作:
http
GET /api/v1/conversations # 获取会话列表
GET /api/v1/conversations/{id} # 获取单个会话详情
GET /api/v1/conversations/{id}/messages # 获取会话消息列表
DELETE /api/v1/conversations/{id} # 删除会话(逻辑删除)
PUT /api/v1/conversations/{id} # 更新会话(如修改标题)上下文窗口与压缩
为避免超出模型的 Token 限制,MateClaw 实现了自动上下文压缩机制。
配置参数
在 application.yml 中配置:
yaml
mateclaw:
conversation:
default-max-input-tokens: 128000 # 最大输入 Token 数
compact-trigger-ratio: 0.75 # 触发压缩的阈值(75%)
preserve-recent-pairs: 5 # 压缩时保留最近的对话轮次压缩流程
- 每次发送消息前,计算当前会话历史的 Token 总数
- 当 Token 数达到
max-input-tokens * compact-trigger-ratio(默认 96000 Token)时触发压缩 - 保留最近
preserve-recent-pairs轮对话(默认 5 轮) - 将较早的消息摘要化,生成压缩版本替代原文
- 压缩后的摘要作为 system 消息注入上下文
示例
假设一段长对话已经累积了 100000 Token:
- 触发阈值:128000 * 0.75 = 96000,已超过
- 系统保留最近 5 轮对话(约 15000 Token)
- 将之前的历史压缩为一段摘要(约 2000 Token)
- 压缩后总 Token 约 17000,为后续对话留出充足空间
ReAct 模式下的消息流
在 ReAct Agent 中,一次用户提问可能触发多轮内部循环:
用户: "帮我搜索最新的 AI 论文并总结"
→ Thought: 需要先搜索 AI 论文
→ Action: 调用 web_search 工具
→ Observation: 返回搜索结果
→ Thought: 需要总结搜索到的内容
→ Action: 生成总结
→ Final Answer: 返回给用户每一步 Thought / Action / Observation 都会作为消息记录,用户可在前端查看完整的推理过程。
多渠道支持
不同渠道的消息处理方式:
| 渠道 | 协议 | 说明 |
|---|---|---|
| Web | SSE | 浏览器端实时流式输出 |
| 钉钉 | Stream 长连接 | 通过钉钉官方 SDK WebSocket 收发消息 |
| 飞书 | WebSocket | 通过飞书长连接模式收发消息 |
| 企业微信 | 长连接 | 通过企微长连接模式收发消息 |
| Telegram | Long-Polling / Webhook | 默认 Long-Polling 轮询,可切换 Webhook |
| Discord | Gateway WebSocket | 通过 JDA 库 Gateway 长连接收发消息 |
所有渠道共用同一套 Agent 引擎和工具系统,只是消息的输入输出适配不同。
