把「会话界面」和「前后端桥接」当成两个可以各自修改的层,是排查这类问题最省事的一刀:OpenMuse 负责把消息画出来、把输入收进来、把会话状态摆在页面上;CopilotKit 负责把界面产生的一次请求转成后端能处理的调用,再把结果或错误送回界面。混着改的代价是,出错时你分不清是渲染没更新,还是请求根本没发出去,于是两边都动一遍,问题反而更难复现。
适用场景:会话页面能打开,但消息不刷新,或发了消息迟迟没有回应。操作动作:先判断现象属于「界面没反应」还是「界面有反应但结果是错的」,前者先查界面层的会话状态与渲染条件,后者先查桥接层的转发、工具注册和错误回传。验证方式:在每层加一条带层名前缀的日志,看一次请求停在哪一层。风险边界:分层只解决职责归位,不解决模型输出质量与后端业务逻辑本身的问题。
把一次用户提问拆成经过的每一层
建议先固定一条调用顺序,之后所有排查都对着这条顺序看。一次用户提问通常按下面的层依次经过:
- 界面层的输入控件收集文本、附件和当前会话标识。
- 界面层把这次输入写入本地会话状态,追加一条用户消息并触发渲染。
- 桥接层接到这次调用,附加运行上下文,例如会话标识、可用工具清单、需要的请求头。
- 桥接层把请求发到后端运行时端点。
- 后端执行,其中可能包含模型推理和一次或多次工具调用。
- 桥接层接收响应或错误,转成界面层能消费的结构。
- 界面层把助手消息渲染出来,并更新发送中、完成、失败这类状态。
每层之间传什么,判断标准是「每层只认自己那一份结构」:界面层到桥接层传的是消息数组加会话上下文;桥接层到后端传的是网络请求体加头部;后端回桥接层传的是结果对象或错误对象;桥接层回界面层传的是统一格式的消息片段。任何一层开始解析不属于自己的字段,后面就很难归位。
| 层 | 负责 | 不负责 | 出错时的典型表现 |
|---|---|---|---|
| 界面层(OpenMuse) | 输入收集、消息渲染、会话状态展示 | 执行权限、工具声明、令牌是否有效 | 消息不出现、重复渲染、发送按钮点了没反应 |
| 桥接层(CopilotKit) | 请求转发、工具注册位置、错误回传 | 业务规则判断、页面布局 | 请求没到后端、工具未触发、错误没被界面识别 |
界面层该负责什么
界面层不承担执行权限:它不决定调用哪个后端、不决定某个工具在当前会话里是否可用、也不负责判断凭据是否过期。它做的是三件事。
- 输入收集:文本、附件、会话切换,以及发送按钮的禁用逻辑和重复提交拦截。
- 消息渲染:用户消息、助手消息、工具调用占位(例如「正在调用检索」)、流式增量的拼接。
- 状态展示:发送中、失败、重试入口、错误提示文案,以及空态和加载态。
验证方式:把桥接端点临时指向一个固定返回错误的地址,界面仍然应该正常展示错误提示,而不是白屏或输入框卡死。如果这时白屏,问题在界面层。风险边界:界面层可以做本地乐观渲染,但不要用界面自己拼的内容覆盖后端返回的最终结果,否则之后无法判断哪一份是对的。
桥接层该负责什么
桥接层的核心是转发与错误回传,它通常不参与业务规则判断。
- 请求转发:把界面层传来的调用参数转成后端运行时能接受的请求,附加头部、超时和重试策略。
- 工具注册位置:工具的定义与可用范围通常在桥接层挂载;界面层只展示工具运行状态,不负责声明工具。
- 错误回传:网络失败、后端 4xx/5xx、工具执行异常,都要规整成界面能识别的错误对象,而不是把原始堆栈直接塞进消息体当成助手回复。
下面是一段通用接入骨架,字段名以你所装版本为准,放在桥接层初始化附近:
{
"runtimeEndpoint": "/api/copilotkit",
"tools": ["searchDocs", "createTicket"],
"forwardHeaders": ["authorization"],
"timeoutMs": 30000,
"onError": "return-structured-error"
}
验证方式:把 timeoutMs 临时改成一个很小的值,观察界面是否收到超时错误提示,而不是一直转圈。风险边界:重试建议放在桥接层做,并且只对幂等请求开启;否则一次工具调用可能在后端被执行多次。
用一次带工具调用的提问验证分层
验证操作可以按下面的步骤走一遍,每一步都只动一层:
- 在界面输入一句会触发工具的问题,例如「帮我查一下这份文档并创建一条工单」。
- 在界面层日志里打上带前缀的记录,例如
[ui] send、[ui] render user message。 - 在桥接层日志里打上
[bridge] forward、[bridge] tool call <name>、[bridge] response。 - 在后端打上
[server] received、[server] tool result。
观察点:用户消息是否立刻出现在列表里,属于界面层;请求是否带上了会话标识与工具清单,属于桥接层;日志里的工具名是否与注册清单一致,属于桥接层;助手消息是否只渲染成一条而不是两条重复,属于界面层。
判断标准:如果界面层日志有,但 [bridge] forward 没有,问题在界面到桥接这一跳;有 forward 但没有 tool call,问题多半在工具注册范围或触发条件;有 tool result 但界面没更新,问题在桥接回传结构或界面渲染条件。分层正确的标志是:改动某一层时,另一层的日志和表现不发生非预期变化。
报错按层归位
两类现象要分开看:界面无响应,偏向事件与状态没被触发,检查入口在浏览器侧;桥接返回错误,偏向请求已经发出但处理失败,检查入口在网络面板和桥接层日志。
| 现象 | 先查哪层 | 检查入口 | 日志位置 |
|---|---|---|---|
| 输入框能打字,发送后页面毫无变化 | 界面层 | 发送事件绑定、会话状态是否写入、渲染条件 | 浏览器控制台、界面层日志 |
| 消息已出现在列表里,但一直没回复 | 桥接层 | 运行时端点是否正确、网络面板里有没有这条请求 | 网络请求记录、桥接层转发日志 |
| 有回复,但内容是错误文案或堆栈 | 桥接层 | 错误回传结构、后端返回的状态码 | 后端访问日志、桥接层错误日志 |
| 工具没有被调用 | 桥接层 | 工具注册清单与作用域是否覆盖当前会话 | 桥接层工具注册日志 |
| 回复重复出现或顺序错乱 | 界面层 | 流式增量拼接与本地状态追加是否做了去重 | 界面层渲染日志 |
动作顺序建议:先把桥接端点的响应固定下来,例如让后端返回一个确定结构,再回头看界面渲染。这样一次只动一层,下一次报错时才能继续按层归位,而不是两边同时改。