目標
Qwen3の思考プロセスを「目標→状態→アルゴリズム→境界条件→検証→コード」のフォーマットで出力させる。
背景
Qwen3などの思考モデルは、通常<think>...</think>タグ内に自由形式の推論過程を出力する。この自由形式をGBNF(GGML BNF)構文で制約することで、以下のような構造化された出力を実現可能:
<think>
GOAL: Pythonで二分探索を実装
STATE: ソート済み配列、目的値のインデックス返却
ALGO: ツーポインタ法、中点比較
EDGE: 空配列・目的値不存在・重複要素
VERIFY: 境界テストケース実行
</think>
def binary_search(arr, target):
# コード実装
利点
- 思考過程の機械解析・検証可能
- GOAL/STATE等のメタ情報抽出によるログ・監査
- モデルの過剰思考・脱線防止
1. llama.cppの実装方法
GBNF定義ファイル
think.gbnfの作成:
root ::= think code
think ::= "<think>\nGOAL: " line "STATE: " line "ALGO: " line "EDGE: " line "VERIFY: " line "</think>\n\n"
line ::= [^\n]+ "\n"
code ::= [\x09\x0A\x0D\x20-\x7E\u3000-\u303F\u4E00-\u9FFF\uFF00-\uFFEF]+
文字セット説明
\u4E00-\u9FFF:漢字領域\u3000-\u303F:CJK句読点\uFF00-\uFFEF:全角文字
実行方法
コマンドライン
llama-cli -m Qwen3.6-35B-A3B.gguf \
-n 1024 \
--grammar-file think.gbnf \
-p "Pythonで二分探索を実装し日本語コメント付き"
API経由
curl -s 'http://localhost:8080/completion' \
-H 'Content-Type: application/json' \
-d '{
"prompt": "Pythonで二分探索を実装",
"n_predict": 1024,
"grammar": "root ::= think code\nthink ::= \"<think>\\nGOAL: \" [^\\n]+ \"\\nSTATE: \" [^\\n]+ \"\\nALGO: \" [^\\n]+ \"\\nEDGE: \" [^\\n]+ \"\\nVERIFY: \" [^\\n]+ \"\\n</think>\\n\\n\"\ncode ::= [ -~\\u3000-\\u303f\\u4e00-\\u9fff\\uff00-\\uffef]+"
}'
2. vLLMの実装方法
起動設定
vllm serve Qwen3.6-35B-A3B \
--structured-outputs-config '{"enable_in_reasoning": true}'
APIリクエスト
curl -s 'http://localhost:8000/v1/chat/completions' \
-X POST \
-H 'Content-Type: application/json' \
-d '{
"model": "qwen",
"messages": [
{"role": "user", "content": "Pythonで二分探索を実装"}
],
"max_tokens": 1024,
"chat_template_kwargs": {"enable_thinking": true},
"structured_outputs": {
"grammar": "root ::= think code\nthink ::= \"<think>\\nGOAL: \" [^\\n]+ \"\\nSTATE: \" [^\\n]+ \"\\nALGO: \" [^\\n]+ \"\\nEDGE: \" [^\\n]+ \"\\nVERIFY: \" [^\\n]+ \"\\n</think>\\n\\n\"\ncode ::= [ -~\\u3000-\\u303f\\u4e00-\\u9fff\\uff00-\\uffef]+"
}
}'
3. 実装比較表
| 項目 | llama.cpp | vLLM |
|---|---|---|
| パラメータ名 | grammar |
structured_outputs.grammar |
| 思考プロセス制約 | デフォルト全域対応 | enable_in_reasoning: trueが必要 |
| JSONエスケープ | \n直接記述 |
JSON内で\\n必要 |
4. よくある問題と解決
- grammar無効化:
enable_in_reasoning: true設定漏れ - トークン不足:
max_tokensを1024以上推奨 - 正規表現のパフォーマンス:
item{0,5}を使用して指数関数的展開を回避
5. 日本語ラベル拡張版
root ::= think code
think ::= "<think>\n目的: " line "状態: " line "アルゴリズム: " line "境界: " line "検証: " line "</think>\n\n"
6. 実装フローまとめ
- .gbnfファイル作成
- llama.cppはそのまま利用可能
- vLLMは起動時に
--structured-outputs-config指定 max_tokensを十分確保