# 聊天与消息 聊天是你真正干活的地方。MateClaw 里其他所有东西——Agent、工具、记忆、Wiki、渠道——存在的理由,都是为了让**这个框里发生的事**足够好。 这一页讲的是这个框到底在做什么。不是 REST 端点,不是 SSE 事件字典。是你看到什么、它为你做了什么、为什么交互设计是现在这样。(API 细节在页面最底部,给需要集成的人看。) --- ## 你看到的东西 你输入,Agent 思考,token 开始流出来,你看着。 但那不是一堵文字墙往外冒。一条 Assistant 消息是由**若干 segment**组成的,每个 segment 都有类型: - **Thinking(思考)**——Agent 的内部推理过程,显示在一个默认折叠的面板里,点一下展开。默认不打开是为了简洁;网络中断了,这段思考也不会丢。 - **Tool call(工具调用)**——一张卡片,显示工具名、参数、(执行完之后的)结果。ChatGPT 式的展现,直接嵌在对话里,不是藏在一个"调试面板"后面。 - **Content(内容)**——真正的回答正文,一个 token 一个 token 流出来。 - **Attachment(附件)**——生成出来的图像、视频、音乐、TTS 片段,挂在**产出它的那条消息上**,不是飘在一条新气泡里。 Segment 是**渐进到达**的。每个 segment 一落盘就立刻持久化到数据库——意思是你在流式中途刷新页面,已经渲染出来的东西**不会丢**。后端挂了再重启,写到一半的回复回来时还在。 这件事以前不成立。现在成立了。 回答正文里的引用也是活的:`[[slug]]` 形式的 wikilink、以及基于知识库作答时正文里的 `[1]` / `[2]` **来源引用标记**都可以**点击直接跳到对应的 wiki 页面**,末尾"来源:"清单的每一行也整行可点。详见 [LLM Wiki · Chat 里点引用直接跳](./wiki#chat-里点-wikilink-直接跳)。 --- ## 持久化的任务清单:给需要时间的计划用 当你问了一个会触发 **Plan-and-Execute** 的问题,对话旁边会出现一个持久化的任务清单。它显示: - 当前的计划(2–6 步,Agent 在开始执行之前生成) - 每一步的实时状态:`pending → running → done`(或 `failed`) - 每一步的输出,执行过程中持续捕获 - 计划完成后的一个紧凑总结 任务清单扛得住刷新,扛得住离开页面再回来,扛得住 Plan 生成失败。如果一个计划在执行中途炸了,你会看到**具体是哪一步炸了、为什么炸**,不会是一个永远转不停的 spinner。 **复杂的、需要一连串有序步骤的任务**用 Plan-and-Execute,你能看着它一步一步做完。其他更小的事情用 ReAct。 --- ## 运行总览:长任务一眼看全 长任务(多步骤计划、多智能体协同)以前只能在消息流里上下翻找进度。聊天页右侧现在有一个常驻的**「运行总览」侧栏**,把后端本就在流式推送的数据装配到一处,不必翻历史: - **计划进度** —— Plan 模式下实时显示各步骤状态(待执行 / 执行中 / 已完成)、进度计数,步骤结果可展开。计划生成前显示「规划中…」占位,不再闪烁。 - **子 Agent 实时状态** —— 委派产生的子 Agent 以**树状**实时展示:名称、调用的工具、运行 / 完成 / 出错 / 停滞状态;多级委派可逐层展开。 侧栏可**折叠为带角标的竖条**;窄屏(< 1280px)自动降级为**浮层抽屉**,不挤占对话区。它纯前端实现、零新增接口,完全复用现有 SSE 事件流——所以委派树也仍会内联在消息里,侧栏只是把「当前 / 活跃」的总览拎出来常驻。 ::: tip 长任务观测建议 运行总览是 SSE 事件的实时投影,不是异步任务记录的权威查询面。刷新、重连或较早启动的后台委派可能暂时不在子 Agent 树中;需要确认终态时,仍以发起会话里的 `taskOutput(taskId)` 为准。运行态 `progress` 也可能保持 0 到终态后直接变成 100,请结合工具调用、计划清单和最终工件判断真实完成度。 ::: --- ## 思考、工具调用、以及"该不该信" 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 在本回合读到的所有内容,都会进它的上下文。 ::: tip 工具生成的文件:下载链接扛得住重启(1.5.0,#243) 员工调工具生成的文件(文档 / 图片 / 音频…)现在**落盘**到 `data/generated-files/`,带 7 天保留窗口 + 6 小时定时清理,内存里再放一层 LRU——下载链接重启后依然有效,不再受原来 10 分钟内存窗口限制。前端用一个全局点击代理拦截 `/api/v1/files/generated/{id}` 下载:成功走鉴权 fetch → blob 下载,失败(404/410/过期)只弹一个 toast,**不再因为一个失效链接把整个页面卡死**。 ::: ### 在线预览:Office / PDF / HTML / 文本不用下载就能看(2.0.0+) 以前图片、音视频、3D 模型都能内联预览,但 Agent 生成的 Word 报告只有一个下载按钮——要看一眼就得下载、找文件、开本地程序。现在**文档类附件在聊天里直接看**: - **点开即预览**:pdf / docx / xlsx / html / markdown / txt / 代码文件,点击附件卡片在玻璃拟态风格的预览层里打开——上传的和 AI 生成的都一样。 - **纯前端渲染**:PDF、Word、Excel 都在浏览器里解析渲染,不出网、不依赖任何外部预览服务,单 JAR 与桌面端打包形态不变。 - **啃不动的格式走服务端兜底**:pptx 与老版二进制 Office(doc / xls / ppt)由服务端转成 PDF 再预览;转换组件(LibreOffice)不在时优雅降级为下载,不报错不卡壳。 - **聊天内 HTML 预览**:已授权读取的 HTML 在隔离源的沙箱 iframe 中渲染,允许脚本以支持交互页面和图表,不授予应用同源权限。这不保证所有脚本、网络资源或浏览器行为均兼容。 - **直接查看生成文件**:直接打开 `/api/v1/files/generated/{id}` 的 HTML/SVG 响应采用更严格的策略:禁止脚本和表单,只保留允许的静态样式与媒体。依赖 JavaScript 的页面应使用聊天内 HTML 预览;两种查看路径的策略不同。 ### 主模型不支持图片?走"多模态旁路" ::: tip 1.3.0 新增 当 Agent 配的主模型是纯文本模型(如 `deepseek-chat` / `kimi-k2`),上传图片不再"系统无法工作",而是自动走旁路(sidecar)。详见 [issue #87](https://github.com/mateaix/mateclaw/issues/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 引擎](./agents)。 ``` 你输入 │ ▼ 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` | 所属会话 | | `role` | `user` / `assistant` / `system` / `tool` | | `content` | 消息全文(分段响应:拼接完成后的最终正文) | | `segments` | Segment 的 JSON 数组(thinking、tool_call、tool_result、content),用于渐进展示 | | `tool_calls` | Assistant 发起的工具调用的 JSON 数组 | | `tool_call_id` | Tool 角色消息:它满足的那次调用 ID | | `create_time` | 时间戳 | Segment 的结构是渐进展示的底层。它也让**数据库成为单一事实源**——UI 可以把任何一条历史回复完整地复现成它流式时的样子。 ### 回退与重新生成(2.0.0+) 两个高频动作在 2.0.0 变成了**服务端语义**,不再是前端的障眼法: - **回退到此处**:把会话截断回某条消息——之后的消息在数据库里真实删除、会话统计(消息数、最后消息摘要)同步重算。刷新页面、换个端打开,看到的都是回退后的状态,被"删掉"的回答不会复活。 - **重新生成**:删掉末尾那条 assistant 回答、**复用原来的 user 消息**重新执行——不再重复插入 user 行。以前每点一次"重新生成",数据库里就多一条重复的提问、旧回答还阴魂不散;现在历史干干净净,怎么刷新都一致。 同一套语义覆盖 Admin 控制台、WebChat 挂件和 API——三个入口的行为完全一致。有在途流的会话会先拒绝回退,避免截断正在写入的回合。 ### 按会话选模型 ::: tip 1.4.0 新增 聊天顶栏的模型选择器现在把模型**绑定在会话上**,而不是全局开关。详见 [issue #150](https://github.com/mateaix/mateclaw/issues/150)。 ::: 在顶栏切换模型,只影响**当前这个会话**:选择会随会话存下来,并从**下一条消息**开始生效。没有显式设置过的会话,回落到工作空间默认模型。运行时模型指示器始终和会话上钉住的那一个保持同步——你看到的就是下一回合真正会用的。 这条隔离也让模型配置更健壮:**一个写错的模型 id 不再拖垮它所在的整个 Provider**。坏会话只影响自己,其他会话照常跑。 ### 会话列表管理 ::: tip 1.4.0 新增 会话侧栏从一条单纯的历史列表,升级成了一个可操作的运营面板。详见 [issue #144](https://github.com/mateaix/mateclaw/issues/144)。 ::: - **置顶 / 取消置顶**——从每行的 `⋮` 溢出菜单操作,重要的会话固定在列表顶部的「置顶」分组里。 - **多选批量删除**——进入多选模式后,每行出现复选框,勾选若干条一次性删除。 - **按员工筛选**——当工作空间里有 **2 个及以上员工**时,侧栏顶部出现一个下拉,按员工过滤会话列表(只有一个员工时不显示,避免无意义的控件)。 - **状态点**——一眼看出每个会话的状态:正在生成(蓝色脉冲)、存在进行中的目标、有未读内容。 ### 全局快捷键 ::: tip 1.4.0 新增 两个全局快捷键,让你不碰鼠标就能在对话之间跳转。提示常驻在侧栏底部。 ::: | 快捷键 | 行为 | |--------|------| | `Ctrl/Cmd + K` | 打开员工选择器,跳到任意一个聊天 | | `Ctrl/Cmd + N` | 新建一个会话 | `Ctrl+N` 在你正于输入框 / 文本域里打字时不会触发,留给浏览器原生行为。 ### 会话管理页 ::: tip 1.4.0 新增 当会话多到侧栏装不下时,从聊天顶栏的溢出菜单进「会话管理」,去一个专门的管理页(`/sessions`)。 ::: 这个页面是为「会话很多」而生的: - **服务端分页**——不再一次把上千条会话塞进侧栏。 - **按标题 / ID 搜索**——输入即筛,定位到具体会话。 - **深度卡片布局**——每个会话一张卡片,信息密度比侧栏更高。 - **行内可编辑的模型 chip**——每行直接显示并切换该会话的模型,不用先进会话。 - **返回按钮**——一键回到聊天控制台。 ### 共享的员工选择器 ::: tip 1.4.0 新增 一个共享的选择器对话框,被三处复用:侧栏、`Ctrl+K` 快捷键、以及新建会话弹窗。 ::: 三个入口打开的是**同一个对话框**,行为完全一致。对话框里的 Agent 图标**按员工做了颜色区分**,多员工工作空间里一眼就能认出谁是谁。 --- ## 上下文窗口管理 每一个回合,MateClaw 都会构造真正送进 LLM 的那个 prompt。大致是: 1. **System prompt**——Agent 的指令 2. **工作空间文件注入**——`AGENTS.md`、`SOUL.md`、`PROFILE.md`、`MEMORY.md`(只注入 `enabled=true` 的那些) 3. **会话摘要**——如果早期轮次已经被压缩过 4. **最近的若干轮**——尽可能装进 token 预算 5. **当前用户消息**——永远在最后 这里的窗口取自模型自身的上下文长度:优先用模型配置里的 `maxInputTokens`,其次是本地推理服务探测到的窗口,再次是内置的常见模型窗口表(DeepSeek V4、Gemini、Claude、Kimi K2 等),都拿不到才回落全局默认 `defaultMaxInputTokens`。 当总量超过 `窗口 × compactTriggerRatio`(全局默认 128000 × 0.75 = 96000),系统会让 LLM 把早期轮次总结一下,把结果缓存 30 分钟,送出去的是压缩版。如果 LLM 依然报 `context_length_exceeded`,会触发紧急截断:不调 LLM,直接丢掉更早的消息,保留最近两轮。 更多细节,以及"为什么把摘要注入成 `UserMessage` 而不是 `SystemMessage`"的安全设计理由,在 [记忆系统](./memory) 里。 --- ## 多渠道:同一个 Agent 在每一处 不同的渠道用不同的传输协议,底下的 Agent 是同一个。同一份 system prompt,同一套工具,同一份记忆。 | 渠道 | 传输协议 | 流式 | |------|----------|------| | Web | SSE | ✅ | | 钉钉 | Stream(WebSocket)/ Webhook | ✅(AI Card) | | 飞书 | WebSocket / Webhook | ❌ | | 企业微信 | 长连接 / Webhook | ❌ | | 微信 | HTTP 长轮询 | ❌ | | Telegram | Long-Polling / Webhook | 打字指示器 | | Discord | Gateway WebSocket | 打字指示器 | | QQ | WebSocket / 回调 | ❌ | | Slack | Webhook / Socket mode | ❌ | 进 [多渠道接入](./channels) 看细节。 --- ## 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` | 阶段切换——`thinking`、`action`、`observation`、`summarizing` | | `message` | 内容 chunk——追加到当前 content segment | | `thinking` | 思考 chunk——追加到 thinking segment | | `tool_call_start` | Agent 开始调工具(工具名 + 参数) | | `tool_call_end` | 工具执行完(结果摘要) | | `plan_created` | Plan-and-Execute 生成了一个计划 | | `step_start` / `step_end` | Plan-and-Execute 的步骤边界 | | `approval_required` | 一次受守护的工具调用需要人工审批 | | `_usage_final` | Token 用量统计(流结束时) | | `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" ``` --- ## 思考过程与线性轨迹(2.1.0+) 2.1.0 把思考从“一个模糊的大段”变成按执行顺序排列的 segment: - 模型流中的 `` 在生成时实时提取,与最终答案分开; - ReAct 每轮推理保留在它真正发生的位置,工具调用和观察不会与下一轮错序; - 每段记录 wall-clock 起止时间,界面显示真实耗时与阶段脉冲; - 管理员可在系统设置中用「显示思考」控制是否渲染,用「显示全部轮次」控制显示全部还是只显示产出答案的轮次; - `mate.agent.reasoning.retention=all|terminal` 控制服务端保存全部轮次还是最终轮次; - 会话所有者可请求 `GET /api/v1/conversations/{conversationId}/trajectory`,把用户消息、reasoning、tool call、observation 和 final answer 导出为按执行顺序排列的纯文本。耗时来自 segment 起止时间并显示在 UI 中,当前纯文本导出不附带耗时字段。 工具执行前的阶段性叙述在真实结果到达后会标记为 `superseded`。当前聊天界面直接显示这些内容,trajectory 则用 `content superseded="true"` 明确标记并保留。Team Run 的中间通报会合并进运行卡片,不再重复形成多条最终答复。 ### 会话批量删除 Sessions 页一次可选择最多 200 个会话批量删除。服务端会对每个 id 去重、校验所有权,并只删除当前用户有权操作的会话。`team_worker` 不进入普通侧栏,因此不会出现在侧栏批量选择中。 --- ## 下一步 - [Agent 引擎](./agents)——真正在思考的是什么 - [记忆系统](./memory)——跨会话保留下来的是什么 - [多渠道接入](./channels)——聊天还能在哪里发生 - [LLM Wiki](./wiki)——Agent 在回答时能读到什么