Files
LLM-Server/docs/coding-cli-setup/qwen-code.md
T
haylanandClaude Bot 17747694bd feat(llm): switch to DFlash2 speculative decoding, drop qwen-classifier
Replace MTP with draft-dflash and a separate DFlash2 drafter GGUF.
Remove the qwen-classifier backend to free VRAM (GPU was at 100%).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 21:28:55 +02:00

5.6 KiB

Qwen Code CLI

← back to overview

Source of truth: Qwen Code's repo for config keys; the overview for the gateway endpoint and context size. If this page disagrees with them, they win.

Qwen Code speaks plain OpenAI Chat Completions. It uses the main model only. There is no separate classifier backend any more (architecture → llama-server).

Auto Mode's classifier (fastModel)

Auto Mode's action classifier (permissions.autoMode) is qwen-code's safety gate for every tool call. It calls fastModel. With no classifier backend, either point fastModel at the main model (it then queues behind the main backend's slots, the reason the separate backend once existed), or leave fastModel unset and don't use Auto Mode.

~/.qwen/settings.json

{
  "modelProviders": {
    "openai": [
      {
        "id": "<main-model-provider-id-in-omniroute>",
        "name": "qwen3.8-27b-local",
        "envKey": "OMNIROUTE_API_KEY",
        "baseUrl": "http://<server>:<OMNIROUTE_API_PORT>/v1",
        "generationConfig": { "contextWindowSize": <LLAMA_CTX_SIZE / LLAMA_PARALLEL> }
      }
    ]
  },
  "security": { "auth": { "selectedType": "openai" } },
  "model": {
    "name": "<main-model-provider-id-in-omniroute>",
    "baseUrl": "http://<server>:<OMNIROUTE_API_PORT>/v1"
  },
  "fastModel": "<main-model-provider-id-in-omniroute>"
}
  • envKey names the environment variable Qwen Code reads the API key from — set OMNIROUTE_API_KEY=<qwen-code-cli API key> before launching.
  • The main model's contextWindowSize is the per-slot context, not LLAMA_CTX_SIZE itself (overview).
  • Qwen Code also recognizes advisorModel, visionModel, compactionModel, imageModel for other model roles — none are wired up in this stack; only fastModel is required.

Web search via OmniRoute

Qwen Code's own built-in web search (tools.webSearch.enabled) has nothing to search with here — leave it false. Instead this stack's SearXNG-backed search (gateway → Web search) is exposed through a thin stdio MCP wrapper around OmniRoute's /v1/search REST endpoint (that endpoint isn't itself MCP — OmniRoute's real MCP surface is admin-only/LOCAL_ONLY-gated). Save this as e.g. ~/.qwen/mcp-servers/omniroute-search/index.mjs (needs @modelcontextprotocol/sdk and zod: npm init -y && npm i @modelcontextprotocol/sdk zod in that directory):

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const BASE_URL = process.env.OMNIROUTE_BASE_URL || "http://proxy-ai.home";
const API_KEY = process.env.OMNIROUTE_API_KEY;

if (!API_KEY) {
  console.error("OMNIROUTE_API_KEY is not set in the environment.");
  process.exit(1);
}

const server = new McpServer({ name: "omniroute-search", version: "1.0.0" });

server.registerTool(
  "search",
  {
    description: "Web/news search via OmniRoute's /v1/search endpoint.",
    inputSchema: { query: z.string().describe("Search query") },
  },
  async ({ query }) => {
    const res = await fetch(`${BASE_URL}/v1/search`, {
      method: "POST",
      headers: { "Content-Type": "application/json", Authorization: `Bearer ${API_KEY}` },
      body: JSON.stringify({ query }),
    });
    const text = await res.text();
    if (!res.ok) return { content: [{ type: "text", text: `HTTP ${res.status}: ${text}` }], isError: true };
    return { content: [{ type: "text", text }] };
  }
);

await server.connect(new StdioServerTransport());

Register it in ~/.qwen/settings.json:

{
  "mcpServers": {
    "omniroute-search": { "command": "node", "args": ["<path-to>/index.mjs"] }
  },
  "tools": { "webSearch": { "enabled": false } }
}

It reuses the same OMNIROUTE_API_KEY env var as the model providers above — the API key needs search permission in OmniRoute, not just chat-completions.

Non-interactive mode (qwen -p ...) needs this tool explicitly allow-listed. MCP tools require interactive confirmation by default; --approval-mode auto alone doesn't bypass that for a non-interactive run — pass --allowed-tools mcp__omniroute-search__search (or -y for full YOLO) alongside -p, or the search call never reaches the classifier at all and silently no-ops. Confirmed live: without the allow-list, only the tool calls the CLI's non-interactive gate lets through end up as classifier requests.

Auto Mode tuning

Auto Mode's action classifier calls fastModel, now the main model, so it can queue behind a heavy generation. Give it real timeout headroom rather than trusting OmniRoute's default. And since the gateway is a single trusted local endpoint, it's reasonable to pre-approve requests to it rather than confirm every call:

{
  "permissions": {
    "autoMode": {
      "classifier": { "timeouts": { "stage1Ms": 600000 } },
      "hints": { "allow": ["Requests to proxy-ai.home, my own local omniroute model proxy"] }
    }
  }
}

hints.allow entries are free-text descriptions the classifier matches against, not exact strings — capped at 150 entries/200 chars each.

The main model's provider connection already carries the long per-connection timeout (providerSpecificData.timeoutMs, see gateway → Timeouts), which now covers classifier calls too.

Everything else in ~/.qwen/settings.json (hooks, security.auth's underlying tooling, editor prefs) is per-machine, not part of pointing at this stack — don't copy it wholesale between machines.