Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,13 @@ export AZURE_OPENAI_AUTH_MODE=api_key
# does not create a direct Anthropic API client for this backend.
# export ANTHROPIC_API_KEY=sk-ant-...

# ── Qwen Local Model (for qwen_chat backend) ────────────────────────
# ── Qwen via an OpenAI-compatible endpoint (qwen_chat backend) ──────
# export QWEN_CHAT_BASE_URL=http://localhost:8000/v1
# export QWEN_CHAT_MODEL=Qwen/Qwen3.5-4B
# Wire policy for chat_template_kwargs.enable_thinking, a vLLM/SGLang
# extension that OpenAI/Azure and strict gateways reject with HTTP 400:
# server_default (default) omit the field | enabled | disabled
# export QWEN_CHAT_THINKING_MODE=disabled
# The train/eval entry points likewise override this model with
# model.optimizer/model.target for the selected Qwen roles.

Expand Down
8 changes: 8 additions & 0 deletions configs/_base_/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ model:
target_azure_openai_ad_scope: "https://cognitiveservices.azure.com/.default"
target_azure_openai_managed_identity_client_id: ""

# Qwen backend settings (qwen_chat roles). `chat_template_kwargs` is a
# vLLM/SGLang extension that OpenAI/Azure reject, so the wire policy is
# explicit: server_default omits the key (safe default), enabled/disabled
# send it. Pin "disabled" to reproduce the published non-thinking numbers.
qwen_chat_thinking_mode: "" # server_default (default) | enabled | disabled
optimizer_qwen_chat_thinking_mode: "" # per-role override
target_qwen_chat_thinking_mode: "" # per-role override

# MiniMax backend settings (minimax_chat target)
minimax_region: "" # global_en (default) or cn_zh; selects the base URL
minimax_base_url: "" # region base URL if blank
Expand Down
5 changes: 3 additions & 2 deletions docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ model:
| `openai_chat` | ✓ | ✓ | Azure OpenAI, or its explicit compatibility auth mode |
| `openai_compatible` | ✓ | ✓ | Generic OpenAI Chat Completions endpoint |
| `claude_chat` | ✓ | ✓ | Claude Code CLI (`claude -p`) |
| `qwen_chat` | ✓ | ✓ | Qwen served through an OpenAI-compatible local endpoint |
| `qwen_chat` | ✓ | ✓ | Qwen served through an OpenAI-compatible endpoint (self-hosted vLLM/SGLang or a hosted gateway) |
| `minimax_chat` | ✓ | ✓ | MiniMax API |
| `copilot_chat` | ✓ | ✓ | GitHub Copilot CLI (`copilot -p`); alias `copilot` |
| `codex_exec` | — | ✓ | Codex CLI execution harness |
Expand Down Expand Up @@ -204,7 +204,8 @@ Model credentials are loaded from environment variables:
| `CURSOR_EXEC_PATH` | `cursor_exec` | Optional path to `cursor-agent`; defaults to `cursor-agent` |
| `CURSOR_EXEC_SANDBOX` | `cursor_exec` | Cursor sandbox mode: `enabled` (default) or `disabled` |
| `CURSOR_API_KEY` | `cursor_exec` | Optional authentication method understood directly by Cursor Agent |
| `QWEN_CHAT_BASE_URL` | `qwen_chat` | Local Qwen/vLLM endpoint |
| `QWEN_CHAT_BASE_URL` | `qwen_chat` | OpenAI-compatible Qwen endpoint: self-hosted vLLM/SGLang or a hosted gateway |
| `QWEN_CHAT_THINKING_MODE` | `qwen_chat` | `server_default` (default; omit `chat_template_kwargs`), `enabled`, or `disabled`; per-role `OPTIMIZER_`/`TARGET_` variants take precedence |
| `QWEN_CHAT_MODEL` | `qwen_chat` | Served model name for direct library use; train/eval YAML role models take precedence |
| `MINIMAX_REGION` | `minimax_chat` | Service region: `global_en` (default) or `cn_zh`; selects the base URL |
| `MINIMAX_BASE_URL` | `minimax_chat` | MiniMax-compatible base URL; overrides the region default |
Expand Down
34 changes: 32 additions & 2 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,42 @@ Every shared key also has an `optimizer_azure_openai_*` and
defaults to `claude` and can be overridden with `CLAUDE_CLI_BIN`.
`ANTHROPIC_API_KEY` is one authentication option understood by the CLI.

### Qwen thinking mode

`qwen_chat` speaks the OpenAI chat-completions protocol, so it reaches both
self-hosted servers (vLLM, SGLang) and hosted OpenAI-compatible gateways.
`chat_template_kwargs` is a vLLM/SGLang extension: OpenAI, Azure OpenAI, and
strict gateways reject the unknown body field with HTTP 400, and non-Qwen vLLM
models served with it may emit `<think>` output without an `<answer>` tag.
Because the correct wire policy therefore depends on the serving stack rather
than on a boolean preference, it is an explicit three-state setting:

| `thinking_mode` | On the wire |
|---|---|
| `server_default` (default) | `chat_template_kwargs` is **not sent**; the server's chat template decides |
| `enabled` | sends `chat_template_kwargs: {"enable_thinking": true}` |
| `disabled` | sends `chat_template_kwargs: {"enable_thinking": false}` |

`server_default` is the portable default and works against any
OpenAI-compatible endpoint, but Qwen3 chat templates enable thinking by
default, so the outcome depends on the serving stack and template version. The
backend warns once per role when a request is sent under `server_default`. For
reproducible runs pin `enabled` or `disabled`; the resolved per-role mode is
recorded in the run's `config.json` under `resolved_qwen_thinking_modes`.

The legacy `model.qwen_chat_enable_thinking` boolean keeps its historical
meaning exactly — `true` sends `enable_thinking: true`, `false` omits the field
(it never sent an explicit `false`) — so existing configs are unaffected.
Setting both keys to conflicting values raises an error rather than picking a
winner. Prefer `thinking_mode`; use `disabled` when you need the field sent.

### Qwen, MiniMax, and Exec Backends

| Parameter family | Description |
|---|---|
| `model.qwen_chat_*` | Shared `base_url`, `api_key`, `temperature`, `timeout_seconds`, `max_tokens`, and `enable_thinking` |
| `model.optimizer_qwen_chat_*` / `model.target_qwen_chat_*` | Per-role Qwen overrides |
| `model.qwen_chat_*` | Shared `base_url`, `api_key`, `temperature`, `timeout_seconds`, `max_tokens`, `thinking_mode`, and the legacy `enable_thinking` |
| `model.qwen_chat_thinking_mode` | Wire policy for `chat_template_kwargs.enable_thinking`: `server_default` (default; omit the field), `enabled`, or `disabled`. See [Qwen thinking mode](#qwen-thinking-mode) |
| `model.optimizer_qwen_chat_*` / `model.target_qwen_chat_*` | Per-role Qwen overrides, including `*_qwen_chat_thinking_mode` |
| `model.minimax_*` | MiniMax `region`, `base_url`, `api_key`, shared `minimax_model`, `temperature`, `max_tokens`, and `enable_thinking`; `minimax_model` applies when MiniMax is the target |
| `model.codex_exec_*` | Codex path, sandbox, profile, SDK mode, reasoning, network/search, and approval policy; see compatibility notes below |
| `model.claude_code_exec_*` | Claude path, profile, SDK mode, effort, and thinking-token cap |
Expand Down
2 changes: 2 additions & 0 deletions scripts/eval_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,12 +525,14 @@ def _set_role(key: str, value: str) -> None:
timeout_seconds=cfg.get("qwen_chat_timeout_seconds"),
max_tokens=cfg.get("qwen_chat_max_tokens"),
enable_thinking=cfg.get("qwen_chat_enable_thinking"),
thinking_mode=cfg.get("qwen_chat_thinking_mode"),
target_base_url=cfg.get("target_qwen_chat_base_url") or None,
target_api_key=cfg.get("target_qwen_chat_api_key") or None,
target_temperature=cfg.get("target_qwen_chat_temperature"),
target_timeout_seconds=cfg.get("target_qwen_chat_timeout_seconds"),
target_max_tokens=cfg.get("target_qwen_chat_max_tokens"),
target_enable_thinking=cfg.get("target_qwen_chat_enable_thinking"),
target_thinking_mode=cfg.get("target_qwen_chat_thinking_mode"),
)
configure_minimax_chat(
region=cfg.get("minimax_region") or None,
Expand Down
6 changes: 6 additions & 0 deletions scripts/train.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,18 +180,21 @@ def parse_args() -> argparse.Namespace:
p.add_argument("--qwen_chat_timeout_seconds", type=float)
p.add_argument("--qwen_chat_max_tokens", type=int)
p.add_argument("--qwen_chat_enable_thinking", type=_BOOL)
p.add_argument("--qwen_chat_thinking_mode", type=str)
p.add_argument("--optimizer_qwen_chat_base_url", type=str)
p.add_argument("--optimizer_qwen_chat_api_key", type=str)
p.add_argument("--optimizer_qwen_chat_temperature", type=float)
p.add_argument("--optimizer_qwen_chat_timeout_seconds", type=float)
p.add_argument("--optimizer_qwen_chat_max_tokens", type=int)
p.add_argument("--optimizer_qwen_chat_enable_thinking", type=_BOOL)
p.add_argument("--optimizer_qwen_chat_thinking_mode", type=str)
p.add_argument("--target_qwen_chat_base_url", type=str)
p.add_argument("--target_qwen_chat_api_key", type=str)
p.add_argument("--target_qwen_chat_temperature", type=float)
p.add_argument("--target_qwen_chat_timeout_seconds", type=float)
p.add_argument("--target_qwen_chat_max_tokens", type=int)
p.add_argument("--target_qwen_chat_enable_thinking", type=_BOOL)
p.add_argument("--target_qwen_chat_thinking_mode", type=str)
p.add_argument("--minimax_region", type=str)
p.add_argument("--minimax_base_url", type=str)
p.add_argument("--minimax_api_key", type=str)
Expand Down Expand Up @@ -401,18 +404,21 @@ def _retired_option_sources(
"qwen_chat_timeout_seconds": "model.qwen_chat_timeout_seconds",
"qwen_chat_max_tokens": "model.qwen_chat_max_tokens",
"qwen_chat_enable_thinking": "model.qwen_chat_enable_thinking",
"qwen_chat_thinking_mode": "model.qwen_chat_thinking_mode",
"optimizer_qwen_chat_base_url": "model.optimizer_qwen_chat_base_url",
"optimizer_qwen_chat_api_key": "model.optimizer_qwen_chat_api_key",
"optimizer_qwen_chat_temperature": "model.optimizer_qwen_chat_temperature",
"optimizer_qwen_chat_timeout_seconds": "model.optimizer_qwen_chat_timeout_seconds",
"optimizer_qwen_chat_max_tokens": "model.optimizer_qwen_chat_max_tokens",
"optimizer_qwen_chat_enable_thinking": "model.optimizer_qwen_chat_enable_thinking",
"optimizer_qwen_chat_thinking_mode": "model.optimizer_qwen_chat_thinking_mode",
"target_qwen_chat_base_url": "model.target_qwen_chat_base_url",
"target_qwen_chat_api_key": "model.target_qwen_chat_api_key",
"target_qwen_chat_temperature": "model.target_qwen_chat_temperature",
"target_qwen_chat_timeout_seconds": "model.target_qwen_chat_timeout_seconds",
"target_qwen_chat_max_tokens": "model.target_qwen_chat_max_tokens",
"target_qwen_chat_enable_thinking": "model.target_qwen_chat_enable_thinking",
"target_qwen_chat_thinking_mode": "model.target_qwen_chat_thinking_mode",
"minimax_region": "model.minimax_region",
"minimax_base_url": "model.minimax_base_url",
"minimax_api_key": "model.minimax_api_key",
Expand Down
3 changes: 3 additions & 0 deletions skillopt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,21 @@
"model.qwen_chat_timeout_seconds": "qwen_chat_timeout_seconds",
"model.qwen_chat_max_tokens": "qwen_chat_max_tokens",
"model.qwen_chat_enable_thinking": "qwen_chat_enable_thinking",
"model.qwen_chat_thinking_mode": "qwen_chat_thinking_mode",
"model.optimizer_qwen_chat_base_url": "optimizer_qwen_chat_base_url",
"model.optimizer_qwen_chat_api_key": "optimizer_qwen_chat_api_key",
"model.optimizer_qwen_chat_temperature": "optimizer_qwen_chat_temperature",
"model.optimizer_qwen_chat_timeout_seconds": "optimizer_qwen_chat_timeout_seconds",
"model.optimizer_qwen_chat_max_tokens": "optimizer_qwen_chat_max_tokens",
"model.optimizer_qwen_chat_enable_thinking": "optimizer_qwen_chat_enable_thinking",
"model.optimizer_qwen_chat_thinking_mode": "optimizer_qwen_chat_thinking_mode",
"model.target_qwen_chat_base_url": "target_qwen_chat_base_url",
"model.target_qwen_chat_api_key": "target_qwen_chat_api_key",
"model.target_qwen_chat_temperature": "target_qwen_chat_temperature",
"model.target_qwen_chat_timeout_seconds": "target_qwen_chat_timeout_seconds",
"model.target_qwen_chat_max_tokens": "target_qwen_chat_max_tokens",
"model.target_qwen_chat_enable_thinking": "target_qwen_chat_enable_thinking",
"model.target_qwen_chat_thinking_mode": "target_qwen_chat_thinking_mode",
"model.minimax_region": "minimax_region",
"model.minimax_base_url": "minimax_base_url",
"model.minimax_api_key": "minimax_api_key",
Expand Down
Loading