-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Description
Environment
- Client: OpenCode via Homebrew on macOS — v0.4.41 (also seen with 0.4.40)
- Server: llama.cpp OpenAI‑compatible server (Windows)
- Model: Qwen3‑30B A3B Coder (GGUF), served via llama.cpp; alias
qwen3-30b - Endpoint:
http://{server-ip}:8080/v1 - Auth header:
Authorization: Bearer test-api-key
Summary
OpenCode includes a tools parameter and injects a Jinja <tools> prompt block even in “plain chat”.
- If llama.cpp runs without
--jinja, it rejects the request with:tools param requires --jinja flag. - If llama.cpp runs with
--jinja, it attempts to render OpenCode’s Jinja tools block and fails with a 500 due to missing filters (e.g.,reject), producing a long “Value is not callable” stack trace.
Reproduction (no --jinja)
-
Start llama.cpp:
llama-server.exe -m C:\models\Qwen3-30B-A3B-Coder-480B-Distill-v2-Q8_0.gguf --alias qwen3-30b --host 0.0.0.0 --port 8080 --api-key test-api-key
-
OpenCode provider points to
http://{server-ip}:8080/v1, agent set to “plain chat” (no tools in config). -
Send “hi” in OpenCode.
Actual: Server 500 + log: tools param requires --jinja flag.
Expected: If no tools are configured, OpenCode should omit the tools key entirely.
Reproduction (with --jinja)
-
Start llama.cpp:
llama-server.exe -m C:\models\Qwen3-30B-A3B-Coder-480B-Distill-v2-Q8_0.gguf --alias qwen3-30b --host 0.0.0.0 --port 8080 --api-key test-api-key --jinja
-
Same OpenCode config; send “hi”.
Actual: llama.cpp 500 with Jinja crash while rendering tool template. Excerpt:
Value is not callable: null at row 58, column 110:
{%- for json_key in param_fields.keys() | reject("in", handled_keys) %}
...
{{- "<tools>" }} {%- for tool in tools %} ...
Expected: Either don’t send Jinja in system prompt, or send OpenAI‑native tool schema without relying on server‑side Jinja filters.
Sanity checks (curl)
-
With
--jinja, server acceptstools: []:curl http://{server-ip}:8080/v1/chat/completions \ -H "Content-Type: application/json" -H "Authorization: Bearer test-api-key" \ -d '{"model":"qwen3-30b","messages":[{"role":"user","content":"Say hi"}],"tools":[]}'
Returns completion.
-
Without
--jinja, server accepts when notoolskey is present:curl http://{server-ip}:8080/v1/chat/completions \ -H "Content-Type: application/json" -H "Authorization: Bearer test-api-key" \ -d '{"model":"qwen3-30b","messages":[{"role":"user","content":"Say hi"}]}'
Returns completion.
Why this seems client‑side
Even with a “plain chat” agent and no tools in my opencode.json, OpenCode still sends either:
- a
toolsfield in the JSON payload, or - a Jinja
<tools>block in the system prompt.
This causes failures on llama.cpp depending on --jinja. Changelog 0.4.40 mentioned “disable todo tools for qwen models”, but the failures persist (they’re triggered by the prompt template/tool scaffolding, not the model).
Requested improvements
- Conditional tools emission: If an agent has no tools configured or the provider is generic OpenAI‑compatible (e.g., llama.cpp), omit
toolsandtool_choiceentirely. Consider auto‑retry without tools on 4xx/5xx indicating unsupported tools. - Config switch: A per‑agent flag like
"sendTools": falsethat guarantees no tools are sent. - Jinja‑free prompting mode: Provide a “raw OpenAI” prompt path (no
{% ... %}in system prompt). - Qwen defaults: Extend 0.4.40’s Qwen compatibility so all tool scaffolding is disabled by default for Qwen on OpenAI‑compatible providers, unless explicitly enabled.
Workarounds that fixed it for me
- Start llama.cpp with
--jinjaand supply a minimal chat template that ignores tools, or - Run OpenCode through a tiny local proxy that strips
tools/tool_choicefrom requests (works flawlessly with llama.cpp without--jinja).
Artifacts (can provide on request)
- Server logs with 500 traces (redacted)
opencode.json(redacted)- Minimal proxy script that strips
toolsand demonstrates successful operation
Thanks! Happy to test a dev build or provide more logs.