出现「像是 Octop 在回答,但内容其实是模型服务生成的」这类现象时,先别急着调模型参数或改会话结构。Octop 侧负责的是会话路由:会话归属、消息顺序、角色标注、上下文与提示词的拼装、把请求发给哪个模型服务;模型服务侧负责内容生成:拿到请求后产出文本。两者自托管时边界容易被界面掩盖,排查的第一步是把「会话状态」和「回答文本」当成两条独立信息分别记录,再对照两侧日志,判定问题落在哪一侧。
Octop 管会话路由,模型服务管内容生成,判断时先记录界面上的会话状态与回答文本,再对照会话记录的消息 ID、角色和模型服务端收到的请求。能定位到会话侧就不要改模型参数,能定位到模型侧就不要动会话结构。两侧都自托管时,边界以实际配置和日志为准,不确定的部分用同一问题切换配置复测来确认。
在 Octop 界面发一条消息并记录可见反馈
界面上同时混着两类信息:会话状态(发送中、已送达、失败、重试、会话标识)和模型内容(回答正文)。这两种反馈的来源不同,记录时要分开写,避免后面拿错误提示去反推模型行为。
建议按下面的清单逐条记录,一次只发一条消息,减少干扰:
- 输入原文与发送时间(尽量精确到秒,便于和模型服务日志对齐)
- 界面状态变化:发送中、已送达、失败提示、重试入口是否出现
- 返回文本:是完整回答,还是兜底话术、模板占位或空内容
- 是否有加载指示、超时提示、错误码或错误文案
- 同一会话是否出现重复消息、顺序颠倒、角色标错
验证方式:另开一个窗口或刷新会话列表,看这条消息是否即时出现在会话记录里。如果列表没更新但回答正常显示,说明会话落库和内容生成可能走了不同路径,这一点后面查消息表时要用到。
| 关注面 | 会话侧(Octop) | 模型侧(模型服务) |
|---|---|---|
| 会话归属与可见性 | 决定谁能看到哪条会话、会话如何分组 | 一般不感知会话,只接收请求 |
| 消息顺序与角色 | 保存 user / assistant / system 的角色与顺序 | 只看到拼装后的输入 |
| 提示词与上下文拼装 | 通常在这一侧拼装,也可能由模型服务自带模板,需要看请求体确认 | 可选:套用自身模板或做参数校验 |
| 实际文本生成 | 不生成内容 | 生成全部回答文本 |
| 超时与重试 | 负责到模型服务的调用超时与重试策略 | 负责自身推理超时与错误码 |
查会话记录中的消息 ID 与角色
打开会话详情、会话导出或后台的数据视图,确认 Octop 是否把用户消息、助手消息、系统提示分开存储。字段名以实际界面或导出结构为准,常见的是消息 ID、会话 ID、父消息 ID、角色、内容、创建时间、模型名。
下面是一段结构示意,只用于说明要核对哪些字段,字段名请以实际导出为准:
{
"session_id": "...",
"message_id": "...",
"parent_message_id": "...", // 用于判断顺序与分支
"role": "user | assistant | system",
"content": "...",
"created_at": "...",
"model": "..." // 是否记录取决于实现
}判断要点:如果 user 消息存在、assistant 消息的 content 为空或缺失,但界面确实显示了回答,说明回答没有回写到会话侧,需要查流式返回结束后的写入逻辑,而不是先怀疑模型。如果 system 提示在导出里能看到原文,下一节对照模型服务请求时可以直接比对这段内容是否被完整传入。
对照模型服务端收到的请求
在模型服务的访问日志或监控面板里,找这次请求对应的记录,重点看四项:请求时间、输入长度、返回状态、耗时。请求时间与界面发送时间的差值,可以判断延迟发生在哪一段;输入长度(字符数或 token 数,按实际统计口径)与界面上会话上下文的规模对比,可以判断上下文有没有完整传过去。
命令与路径按实际部署替换,下面只是抓取思路:
# 按时间窗口过滤最近的请求记录
grep -n 'T10:' /var/log/model-service/access.log | tail -50
# 只打印状态码与耗时字段(字段位置以实际日志格式为准)
awk '{print $1, $4, $9, $NF}' /var/log/model-service/access.log | tail -50三种常见结果:一是模型服务完全没有收到请求,问题在会话侧到模型服务的调用链路,通常与地址、鉴权、超时配置有关;二是收到请求但输入长度明显短于会话上下文,说明上下文没传全,要回头查会话侧的拼装逻辑;三是收到请求且返回状态异常,此时优先看模型服务自己的错误码和资源情况。
用同一问题切换模型配置做对比
用同一条输入、同一个会话,切换模型配置后再发一次,对比两边的差异。切换时只改一个变量,别同时改模型和提示词,否则无法归因。建议记录下面几项:
| 记录项 | 切换前 | 切换后 |
|---|---|---|
| 模型名占位符(以实际配置为准) | model-a | model-b |
| 系统提示 / 模板差异 | 记录原文或摘要 | 记录原文或摘要 |
| 模型服务收到的输入长度 | 记录数值 | 记录数值 |
| 返回文本差异 | 记录风格、长度、结构 | 记录风格、长度、结构 |
| 返回状态与耗时 | 记录 | 记录 |
判断方向:如果换模型后回答风格、措辞、结构化程度变化明显,但会话里的上下文和消息顺序没变,内容生成主要落在模型侧;如果两种模型下都出现同一段固定前缀、同一句免责话术或同样的截断方式,这段多半来自会话侧的模板或后处理。需要结合环境确认的一点是:有些模型服务会自带默认模板,切换模型时模板也随之变化,这种情况要同时看请求体才能分清。
把问题归到会话侧或模型侧
形成一套固定顺序,可以减少来回改配置的无效动作:
- 先看界面有没有错误或超时提示。有提示时,先查会话侧到模型服务的调用链路。
- 再看会话记录里 user 与 assistant 是否成对落库。缺 assistant 记录,查回写和流式结束处理。
- 再看模型服务是否收到请求、输入长度是否与界面上下文匹配。不匹配,查会话侧上下文拼装。
- 最后用同一问题切换模型或提示词各跑一次。回答随模型变,归模型侧;随模板变,归会话侧。
- 确定一侧后只改这一侧,用同一输入复测,确认改动生效再继续。
| 会话侧症状 | 模型侧症状 |
|---|---|
| 消息顺序错乱、角色标错 | 回答语言、风格、知识范围与模型特性相关 |
| 会话列表不更新、跨会话串消息 | 返回被截断、停止条件异常 |
| assistant 消息缺失或重复写入 | 状态码 4xx / 5xx、推理超时 |
| 请求没发出去、鉴权或地址错误 | 请求已到达但输入长度偏短 |
| 固定前缀、免责话术重复出现 | 同一输入在不同会话中输出稳定且与模板无关 |
按这个顺序走一遍,多数情况能判断出该查会话路由还是该查内容生成。两侧都自托管时,边界不是靠约定,而是靠日志、消息记录和可复现的对比结果来确认。