OpenCode 接入 Swift/Occamy 模型时多条 system 消息的 500 错误修复

发布于 2026-10-10 07:30 1115 字 6 min read AI 主笔 正文主要由 AI 起草,我负责挑选和改写。

暂无系列文章
最近几天在本地用 OpenCode 接 llama.cpp 服务器跑 Swift 和 Occamy 模型时,遇到一个反复出现的 500 错误:。查了一整天根因,最终通过前置合并多条 system 消息解决了问题。把过程写下来,也给自己留个记录——毕竟我记性不太好(疑似 ADHD,面对大量文字时海马体就不工作)。 问题复现 月 10 日,用户报告 Swift-1.5-Qwen3.8-27B 接入...

最近几天在本地用 OpenCode 接 llama.cpp 服务器跑 Swift 和 Occamy 模型时,遇到一个反复出现的 500 错误:System message must be at the beginning。查了一整天根因,最终通过前置合并多条 system 消息解决了问题。把过程写下来,也给自己留个记录——毕竟我记性不太好(疑似 ADHD,面对大量文字时海马体就不工作)。

问题复现

10 月 10 日,用户报告 Swift-1.5-Qwen3.8-27B 接入 OpenCode 时报 500,错误信息指向 System message must be at the beginning,定位到官方 chat_template 第 106 行。

紧接着 Occamy 模型也报同样的错:System message must be at the beginning,这次在 occamy-chat_template.jinja 第 85 行。

两个不同模型、不同模板,却指向同一个错误模式——这说明问题不在某个特定模板,而在于 OpenCode 的请求结构。

根因分析

OpenCode 在发送请求时,开头会带两条 system 消息:一条是主系统提示(主 prompt),另一条是环境/工具提示(告诉模型当前有哪些可用工具)。而 Swift 和 Occamy 的官方 chat_template 都只允许 index 0 位置存在一条 system 消息,用 not loop.first 来校验,遇到第二条 system 就抛错。

关键洞察:这不是模板本身的 bug,而是模板设计时没考虑到「多条开头 system」的使用场景。 OpenCode 的环境注入 + 用户主 prompt 的 system 部分,在模板侧看到了两条连续的 system 消息块。

修复思路

参考 Tiel chat_template.jinja 的 head.count 思路:不修改官方 body 模板(保持内容块验证和后续 system 拒绝逻辑),而是在前置层把连续的多条 system 消息合并进一个 </system> 块(\n\n 连接),主循环校验从 not loop.first 改为 loop.index0 >= ns_lead.count,即仍拒绝非开头 system,但允许并合并多条开头 system。

修复实现

以 Occamy 为例,新增 /home/cao/scripts/llama/occamy-chat_template.jinja:

  1. 在文件头统计「开头连续 system」数量 ns_lead.count(namespace,遇非 system 停止)
  2. tools 分支和非 tools 分支都改为把所有开头 system 合并进一个 </system> 块(\n\n 连接)
  3. 主循环校验从 not loop.first 改为 loop.index0 >= ns_lead.count

即仍拒绝非开头 system,但允许并合并多条开头 system。这是照搬 Tiel chat_template.jinja 的 head.count 思路。

Swift 模型(Swift-1.5-Qwen3.8-27B)的情况类似但更简单:官方模板内置了 8952 字符的 chat template,与本地运行子服务器的 57627 端口 /props 返回的内置模板完全一致(SHA256 验证通过)。修复新增 /home/cao/scripts/llama/swift-chat_template.jinja,同样采用前置合并连续 leading system 为一个内容块数组、双换行连接的方式。

验证过程

Occamy 验证:

  • 单 system(有/无 tools)输出与修复前逐字节相同
  • 两条开头 system 合并成功
  • 非开头 system 仍抛错(保留拒绝逻辑)
  • 完整 agent 对话(双 system + tool_calls + tool 响应 + user)渲染正常
  • 真实 router e2e(双 system + tools + user,model=occamy-1.0-3050)返回 finish=stop content=SYSTEM-OK,tg 21.92s

Swift 验证:

  • benchmarks/swift-template-20261010/verify.py PASS 44 项渲染
  • 真实 llama.cpp direct Jinja + common 路径覆盖旧错误、新合并、单/无 system 逐字一致
  • tools 和 history、reasoning options、system image 拒绝均验证通过

注意事项

  1. 服务器启动时缓存模板:改完 chat_template 后必须重启 llama-server/router 才生效。已有进程会缓存旧 preset,用户需重启 launcher 激活。
  2. 同一文件被多预设共用:occamy-chat_template.jinja 也被 [occamy-1.0] 双 GPU 预设共用,修改影响所有引用该文件的预设。
  3. 备份习惯:修改前先备份原文件(如 occamy-chat_template.jinja.before-sysfix-20260929),出问题可快速回退。
  4. 证据留存:验证日志放在 benchmarks/ 目录下(如 benchmarks/occamy-3050-20260929/sysfix-e2e.log),方便后续追溯。

延伸:OpenCode 配置相关经验

同一天还解决了两个 OpenCode 配置问题,简要记录:

DeepSeek Flash 图片输入:在 opencode.json 中添加 provider.deepseek.models.deepseek-flash: {attachment:true, modalities:{input:[text,image],output:[text]}},通过本机 /session/:id/message 接口附 PNG 图验证模型正确识别图中数字。注意 CLI 的 opencode run -f 会额外加入 tool use 文本,纯色图曾被误判,不能仅凭 -f 代表 Web 附件路径。

Exa MCP schema 冲突:OpenCode 接 DeepSeek flash 时报 Invalid schema for function exa_agent_run。将 mcp.exa.url 改为 https://mcp.exa.ai/mcp?tools=web_search_exa,web_fetch_exa,明确只暴露搜索和抓取工具,避免 agent_run schema 进入模型请求。


这些天折腾下来最大的体会:LLM 本地部署的工具链调试,一半时间在查 template 渲染,另一半时间在确认”改完要重启”。希望这篇记录能帮到同样遇到 system 消息 500 错误的朋友。

执笔 AI 主笔

随机文章

加载中...