Skip to content

聊天与消息

本文档介绍 MateClaw 的聊天系统架构、消息流转机制、SSE 流式传输以及会话管理。

消息流转架构

用户发送消息后,经过以下链路处理:

用户输入 → ChannelAdapter → AgentService → StateGraph → LLM → 流式响应
  1. ChannelAdapter 接收消息(Web / 钉钉 / 飞书等),统一封装为内部消息格式
  2. AgentService 加载 Agent 配置,初始化 AgentState
  3. StateGraph 根据 Agent 类型(ReAct / Plan-and-Execute)编排执行流程
  4. 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"}

前端使用 EventSourcefetch 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_countToken 数量
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            # 压缩时保留最近的对话轮次

压缩流程

  1. 每次发送消息前,计算当前会话历史的 Token 总数
  2. 当 Token 数达到 max-input-tokens * compact-trigger-ratio(默认 96000 Token)时触发压缩
  3. 保留最近 preserve-recent-pairs 轮对话(默认 5 轮)
  4. 将较早的消息摘要化,生成压缩版本替代原文
  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 都会作为消息记录,用户可在前端查看完整的推理过程。

多渠道支持

不同渠道的消息处理方式:

渠道协议说明
WebSSE浏览器端实时流式输出
钉钉Stream 长连接通过钉钉官方 SDK WebSocket 收发消息
飞书WebSocket通过飞书长连接模式收发消息
企业微信长连接通过企微长连接模式收发消息
TelegramLong-Polling / Webhook默认 Long-Polling 轮询,可切换 Webhook
DiscordGateway WebSocket通过 JDA 库 Gateway 长连接收发消息

所有渠道共用同一套 Agent 引擎和工具系统,只是消息的输入输出适配不同。