Skip to content

聊天与消息

聊天是你真正干活的地方。MateClaw 里其他所有东西——Agent、工具、记忆、Wiki、渠道——存在的理由,都是为了让这个框里发生的事足够好。

这一页讲的是这个框到底在做什么。不是 REST 端点,不是 SSE 事件字典。是你看到什么、它为你做了什么、为什么交互设计是现在这样。(API 细节在页面最底部,给需要集成的人看。)


你看到的东西

你输入,Agent 思考,token 开始流出来,你看着。

但那不是一堵文字墙往外冒。一条 Assistant 消息是由若干 segment组成的,每个 segment 都有类型:

  • Thinking(思考)——Agent 的内部推理过程,显示在一个默认折叠的面板里,点一下展开。默认不打开是为了简洁;网络中断了,这段思考也不会丢。
  • Tool call(工具调用)——一张卡片,显示工具名、参数、(执行完之后的)结果。ChatGPT 式的展现,直接嵌在对话里,不是藏在一个"调试面板"后面。
  • Content(内容)——真正的回答正文,一个 token 一个 token 流出来。
  • Attachment(附件)——生成出来的图像、视频、音乐、TTS 片段,挂在产出它的那条消息上,不是飘在一条新气泡里。

Segment 是渐进到达的。每个 segment 一落盘就立刻持久化到数据库——意思是你在流式中途刷新页面,已经渲染出来的东西不会丢。后端挂了再重启,写到一半的回复回来时还在。

这件事以前不成立。现在成立了。


持久化的任务清单:给需要时间的计划用

当你问了一个会触发 Plan-and-Execute 的问题,对话旁边会出现一个持久化的任务清单。它显示:

  • 当前的计划(2–6 步,Agent 在开始执行之前生成)
  • 每一步的实时状态:pending → running → done(或 failed
  • 每一步的输出,执行过程中持续捕获
  • 计划完成后的一个紧凑总结

任务清单扛得住刷新,扛得住离开页面再回来,扛得住 Plan 生成失败。如果一个计划在执行中途炸了,你会看到具体是哪一步炸了、为什么炸,不会是一个永远转不停的 spinner。

复杂的、需要一连串有序步骤的任务用 Plan-and-Execute,你能看着它一步一步做完。其他更小的事情用 ReAct。


思考、工具调用、以及"该不该信"

MateClaw 的聊天 UI 在试着回答一个问题:AI 刚刚告诉你的事情,该不该信? 别处的默认答案是"看答案,自己猜"。MateClaw 想做得更好。

思考可见。 如果 Agent 推理得马虎,你可以展开思考面板看它到底是怎么想的。跳过了哪一步?写在里面。在发现错误前先幻觉了一个事实?你能看着它自己把错误抓回来。

工具调用可见。 Agent 搜网页、读文件、查 Wiki——每一次工具调用你都看得到查询是什么、返回了什么、它怎么用这个结果。没有任何东西藏在一个"相信我"的黑盒里。

阶段提示可见。 流式响应顶部有一个小指示器,显示当前阶段——思考中、搜索中、读取中、生成中、总结中。你永远不会对着一个 spinner 发呆、不知道 Agent 是死是活。

信任是靠"把过程摊开"挣来的。MateClaw 把过程摊开。

执行计划 & 工具调用详情查看器(1.5.0)。 计划面板每个步骤、每个工具调用行,右侧多了一个"查看详情"图标。点开是一个带毛玻璃背景的弹窗,显示完整的请求参数和响应输出——内联预览会截断的部分这里都在,带请求/响应各自的复制按钮,状态徽标标"进行中 / 已完成 / 失败 / 等待中"。这些数据存在消息元数据里,所以刷新页面后计划步骤和工具调用照样可读。


多渠道实时同步

ChatConsole 不只是你自己聊天的地方。它是一个运营控制台

  • 外部渠道实时同步——一个微信用户在跟你的 Agent 聊天,你在 ChatConsole 侧栏能同步看到推理过程、工具调用、流式回复。不用 F5,不用等。
  • 运行指示器——正在跑 Agent 任务的会话,图标上有琥珀色脉冲。你一眼能看到哪些会话在活跃。
  • 切换不踩死——切到别的会话时,上一个在后台继续跑完。切回来,自动重连到活跃 buffer,一个 token 都不丢。
  • 不再重复气泡——reconcile 层通过 ID 提升把前端流式 placeholder 和后端落库的 assistant 消息匹配为同一条,消息不会闪一下变两条。
  • 错误卡可操作——Ollama "does not support tools" 不再是"未知错误"。现在给你具体中文指引:"请切换到 qwen3 / qwen2.5:7b+ / llama3.1:8b+"。

附件与文件上传

三种给 Agent 文件的方式:

方式行为
点击附件按钮打开文件选择器,选一个或多个文件
粘贴(Ctrl/Cmd+V)粘贴从其他应用复制来的图片或文件
拖拽到聊天区域出现一个半透明蒙层,在里面任何地方松手

在桌面端拖一个文件夹进来,Agent 拿到的是这个文件夹的绝对路径引用——它可以用文件读取或 shell 工具去遍历里面的文件。在 Web 端拖文件夹进来,MateClaw 会递归展开、把每个文件单独上传。

默认上传限制:

设置默认值
单文件最大100 MB
单请求最大200 MB
允许类型全部

图片递交给支持视觉的模型做视觉理解。PDF 和 DOCX 走文本抽取(扫描件自动降级到 OCR)。Agent 在本回合读到的所有内容,都会进它的上下文。

工具生成的文件:下载链接扛得住重启(1.5.0,#243)

员工调工具生成的文件(文档 / 图片 / 音频…)现在落盘data/generated-files/,带 7 天保留窗口 + 6 小时定时清理,内存里再放一层 LRU——下载链接重启后依然有效,不再受原来 10 分钟内存窗口限制。前端用一个全局点击代理拦截 /api/v1/files/generated/{id} 下载:成功走鉴权 fetch → blob 下载,失败(404/410/过期)只弹一个 toast,不再因为一个失效链接把整个页面卡死

主模型不支持图片?走"多模态旁路"

1.3.0 新增

当 Agent 配的主模型是纯文本模型(如 deepseek-chat / kimi-k2),上传图片不再"系统无法工作",而是自动走旁路(sidecar)。详见 issue #87

工作机制:

  1. 你在「设置 → 模型 → 多模态旁路」配置一个视觉旁路模型(例如 glm-4v / qwen-vl-max)。
  2. 上传图片时,路由器检测主模型是否支持视觉:
    • 支持 → 直接走原有的 native multimodal 路径,图片字节喂给主模型;
    • 不支持 → 旁路触发:视觉模型对图片做一次"看图说话",把结构化描述拼回 user 消息文本,再交给主模型回答。

这样主对话保持便宜(只在有图片那一次额外花一次视觉模型的钱),同时用户的工具调用不被压制——之前的旧逻辑会在 system prompt 里硬性禁止 LLM 调用任何工具去解析跳过的附件,新版本拆掉了这条禁令:当 Agent 绑定了图片/视频处理类工具时,LLM 现在可以自主决定何时调用。

整个过程对用户完全可见

  • 输入框上方提示条:粘贴图片那一刻就告诉你"将自动调用 xxx 识别图片内容(旁路模式)"。
  • 消息气泡路由徽章:助手回复底部多一个 🔀 routed-to-xxx (图片) 标签,hover 看到主模型 / 旁路模型 / Provider 详情。
  • 历史消息归因:每条消息都显示当时实际用的模型——之前是黑盒,现在是玻璃盒。

当前限制:v1 仅支持图片旁路。视频附件目前需要切到具备视频能力的多模态主模型,或者绑定自定义视频处理工具(不会再被压制)。视频自动 sidecar 留到下一轮迭代。


消息实际上怎么流动

三十秒版本在下面。九十秒版本在 Agent 引擎

你输入


POST /api/v1/chat?agentId={id}                ← 或走 SSE 流式(POST /api/v1/chat/stream)


Conversation Manager                          ← 加载/创建会话,追加用户消息


Agent Engine                                  ← ReAct 循环,或 Plan-and-Execute 图
   │     ┌──► 上下文窗口装配:system prompt + 工作空间文件 + 历史
   │     ├──► 工具调用(经过 Tool Guard;可能停下来等审批)
   │     ├──► Wiki 读取(如果绑定了知识库)
   │     └──► 记忆写入(异步,回合结束之后)


SSE 流 / 直接响应                             ← segment 一段一段送达


写入 mate_message                             ← 实时,segment 级持久化

注意这件事:持久化是和流式同步的。Segment 是从 LLM 吐出来的同时往数据库写的,不是回合结束一次性写。这就是为什么你在流式中途刷新不会丢掉回复。


会话管理

一个会话是属于某个 Agent + 某个用户的消息序列。MateClaw 用两张表存它们:

mate_conversation

用途
id会话 ID
user_id所有者
agent_id会话跑在哪个 Agent 上
title从第一条用户消息自动生成(可编辑)
create_time / update_time时间戳

mate_message

用途
id消息 ID
conversation_id所属会话
roleuser / assistant / system / tool
content消息全文(分段响应:拼接完成后的最终正文)
segmentsSegment 的 JSON 数组(thinking、tool_call、tool_result、content),用于渐进展示
tool_callsAssistant 发起的工具调用的 JSON 数组
tool_call_idTool 角色消息:它满足的那次调用 ID
create_time时间戳

Segment 的结构是渐进展示的底层。它也让数据库成为单一事实源——UI 可以把任何一条历史回复完整地复现成它流式时的样子。

按会话选模型

1.4.0 新增

聊天顶栏的模型选择器现在把模型绑定在会话上,而不是全局开关。详见 issue #150

在顶栏切换模型,只影响当前这个会话:选择会随会话存下来,并从下一条消息开始生效。没有显式设置过的会话,回落到工作空间默认模型。运行时模型指示器始终和会话上钉住的那一个保持同步——你看到的就是下一回合真正会用的。

这条隔离也让模型配置更健壮:一个写错的模型 id 不再拖垮它所在的整个 Provider。坏会话只影响自己,其他会话照常跑。

会话列表管理

1.4.0 新增

会话侧栏从一条单纯的历史列表,升级成了一个可操作的运营面板。详见 issue #144

  • 置顶 / 取消置顶——从每行的 溢出菜单操作,重要的会话固定在列表顶部的「置顶」分组里。
  • 多选批量删除——进入多选模式后,每行出现复选框,勾选若干条一次性删除。
  • 按员工筛选——当工作空间里有 2 个及以上员工时,侧栏顶部出现一个下拉,按员工过滤会话列表(只有一个员工时不显示,避免无意义的控件)。
  • 状态点——一眼看出每个会话的状态:正在生成(蓝色脉冲)、存在进行中的目标、有未读内容。

全局快捷键

1.4.0 新增

两个全局快捷键,让你不碰鼠标就能在对话之间跳转。提示常驻在侧栏底部。

快捷键行为
Ctrl/Cmd + K打开员工选择器,跳到任意一个聊天
Ctrl/Cmd + N新建一个会话

Ctrl+N 在你正于输入框 / 文本域里打字时不会触发,留给浏览器原生行为。

会话管理页

1.4.0 新增

当会话多到侧栏装不下时,从聊天顶栏的溢出菜单进「会话管理」,去一个专门的管理页(/sessions)。

这个页面是为「会话很多」而生的:

  • 服务端分页——不再一次把上千条会话塞进侧栏。
  • 按标题 / ID 搜索——输入即筛,定位到具体会话。
  • 深度卡片布局——每个会话一张卡片,信息密度比侧栏更高。
  • 行内可编辑的模型 chip——每行直接显示并切换该会话的模型,不用先进会话。
  • 返回按钮——一键回到聊天控制台。

共享的员工选择器

1.4.0 新增

一个共享的选择器对话框,被三处复用:侧栏、Ctrl+K 快捷键、以及新建会话弹窗。

三个入口打开的是同一个对话框,行为完全一致。对话框里的 Agent 图标按员工做了颜色区分,多员工工作空间里一眼就能认出谁是谁。


上下文窗口管理

每一个回合,MateClaw 都会构造真正送进 LLM 的那个 prompt。大致是:

  1. System prompt——Agent 的指令
  2. 工作空间文件注入——AGENTS.mdSOUL.mdPROFILE.mdMEMORY.md(只注入 enabled=true 的那些)
  3. 会话摘要——如果早期轮次已经被压缩过
  4. 最近的若干轮——尽可能装进 token 预算
  5. 当前用户消息——永远在最后

当总量超过 defaultMaxInputTokens × compactTriggerRatio(默认 128000 × 0.75 = 96000),系统会让 LLM 把早期轮次总结一下,把结果缓存 30 分钟,送出去的是压缩版。如果 LLM 依然报 context_length_exceeded,会触发紧急截断:不调 LLM,直接丢掉更早的消息,保留最近两轮。

更多细节,以及"为什么把摘要注入成 UserMessage 而不是 SystemMessage"的安全设计理由,在 记忆系统 里。


多渠道:同一个 Agent 在每一处

不同的渠道用不同的传输协议,底下的 Agent 是同一个。同一份 system prompt,同一套工具,同一份记忆。

渠道传输协议流式
WebSSE
钉钉Stream(WebSocket)/ Webhook✅(AI Card)
飞书WebSocket / Webhook
企业微信长连接 / Webhook
微信HTTP 长轮询
TelegramLong-Polling / Webhook打字指示器
DiscordGateway WebSocket打字指示器
QQWebSocket / 回调
SlackWebhook / Socket mode

多渠道接入 看细节。


API 参考(给集成者)

发送消息

bash
curl -X POST 'http://localhost:18088/api/v1/chat?agentId=1' \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "东京现在几点?",
    "conversationId": "conv-abc123"
  }'

省略 conversationId 就会开一个新会话。agentId 是 query 参数,不是路径段。

SSE 流式

SSE 端点是 POST /api/v1/chat/stream,请求体里带 agentId。浏览器原生 EventSource 只支持 GET,所以集成时用 fetch() 读流:

javascript
const resp = await fetch('/api/v1/chat/stream', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN',
    'Content-Type': 'application/json',
    'Accept': 'text/event-stream',
  },
  body: JSON.stringify({
    agentId: 1,
    message: '东京现在几点?',
    conversationId: 'conv-abc123',
  }),
});

const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buf = '';
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buf += decoder.decode(value, { stream: true });
  // 按 SSE 协议拆 `\n\n` 边界,逐事件处理 segment
}

完整客户端实现可以参考 mateclaw-ui/src/composables/chat/useChat.ts

SSE 事件类型

事件含义
phase阶段切换——thinkingactionobservationsummarizing
message内容 chunk——追加到当前 content segment
thinking思考 chunk——追加到 thinking segment
tool_call_startAgent 开始调工具(工具名 + 参数)
tool_call_end工具执行完(结果摘要)
plan_createdPlan-and-Execute 生成了一个计划
step_start / step_endPlan-and-Execute 的步骤边界
approval_required一次受守护的工具调用需要人工审批
_usage_finalToken 用量统计(流结束时)
done流结束
error出错了

会话管理

bash
# 列表
curl http://localhost:18088/api/v1/conversations \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

# 取消息
curl http://localhost:18088/api/v1/conversations/conv-abc123/messages \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

# 删除
curl -X DELETE http://localhost:18088/api/v1/conversations/conv-abc123 \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

下一步