メインコンテンツへスキップ
友田 陽大
量子化LLM・セルフホスト
Qwen
エージェント
Tool Use
vLLM
TypeScript
Zod
生成AI

Qwen3-8B-AWQ をエージェント化:Qwen-Agent × function calling の本番設計

自前のQwen3-8B-AWQをツールを使うエージェントにする本番設計。vLLMのHermes形式tool callingの有効化、型安全なツール契約(Zod→JSON Schema)、引数を検証してから実行する安全なループ、反復回数の上限・冪等な副作用・認可ガード、思考モードでのReAct禁止という公式注意点まで、世界最高峰のコードで解説します。

公開日
読了時間
10分
著者
友田 陽大
シェア

この記事のゴール

LLM を「答えるだけ」から「ツールを使って仕事をする」へ引き上げるのが、エージェント化です。Qwen3-8B-AWQは関数呼び出し(function calling)に対応していて、自前GPUのまま、データを外に出さずにツール実行エージェントを組めます。

ただしエージェントは最も事故りやすい領域です。LLM が返した引数をそのまま実行すれば、SQLにもシェルにもなる。本稿は、判断は LLM・実行は決定的コードに分離し、引数を検証してから実行する安全なループを、型安全に組む方法を示します。

信頼性の開示:tool calling の仕様・vLLM フラグ・思考モデルでの注意点は、Qwen 公式(Function Calling)vLLM 公式(Tool Calling)Qwen-Agentに基づきます。設計原則(判断と実行の分離)はツール使用・関数呼び出しの設計と揃えています。GPU 本番運用は動画AIローカライズ基盤で踏んだ領域です。


30秒の結論

  • 有効化vllm serve Qwen/Qwen3-8B-AWQ --enable-auto-tool-choice --tool-call-parser hermes --reasoning-parser qwen3。これで OpenAI 互換の tools がそのまま機能する。
  • ツールは型付き契約:Zod を真実源に、tools 定義の JSON Schema を自動生成。返ってきた引数は実行前に Zod で検証
  • 安全装置を必ず:反復上限・ツール allowlist・副作用の冪等キー・認可。LLM 出力をそのまま実行しない
  • 公式の注意:思考モデルでは ReAct 等の stopword 依存テンプレートを使わない。Hermes 形式を使う。
  • 作るか借りるか:定型を任せたいなら Qwen-Agent、制御を握りたいなら素の OpenAI 互換ループ(本稿で実装)。

サーブ:Hermes 形式の tool calling を有効化

Qwen3 は Hermes スタイルの tool use を推奨。vLLM ではフラグで有効化します。

vllm serve Qwen/Qwen3-8B-AWQ \
  --enable-auto-tool-choice \
  --tool-call-parser hermes \
  --reasoning-parser qwen3 \
  --max-model-len 32768 --port 8000

🔧 公式の注意(思考モデル):Qwen 公式は、思考モデルで ReAct のような stopword 依存のツールテンプレートを推奨しないとしています。理由は「思考部(<think>)に stopword が出力され、tool call が壊れ得る」から。Hermes 形式(--tool-call-parser hermes)を使うのが安全策です。


ツールは「型付き契約」:Zod を真実源にする

エージェントの安全性は、ツール定義が型であることから始まります。Zod スキーマを1つ書き、そこからモデルへ渡す tools の JSON Schema と、モデルが返した引数を検証する parser の両方を導きます(DRY・構造化出力と同じ思想)。

// lib/tools.ts — ツール=「名前・引数スキーマ・決定的な実行関数」の契約
import { z } from "zod";

export interface Tool<A extends z.ZodType> {
  readonly name: string;
  readonly description: string;
  readonly args: A;                              // 引数スキーマ(真実源)
  readonly execute: (a: z.infer<A>) => Promise<unknown>; // 実行は決定的コード
}

/** 在庫照会(読み取り専用・副作用なし) */
export const getStock: Tool<z.ZodObject<{ sku: z.ZodString }>> = {
  name: "get_stock",
  description: "SKUの在庫数を返す",
  args: z.object({ sku: z.string().regex(/^[A-Z0-9-]{4,32}$/) }), // 入力境界を型で締める
  execute: async ({ sku }) => ({ sku, qty: await stockRepo.count(sku) }),
};

/** OpenAI互換 tools 定義へ変換(Zod → JSON Schema を自動生成) */
export function toOpenAITool<A extends z.ZodType>(t: Tool<A>) {
  return {
    type: "function" as const,
    function: { name: t.name, description: t.description, parameters: z.toJSONSchema(t.args) },
  };
}

declare const stockRepo: { count(sku: string): Promise<number> };

ポイントは execute普通の関数であること。LLM は「どのツールをどの引数で呼ぶか」を判断するだけで、実行は私たちの決定的コードが握ります。


安全なツール実行ループ(世界最高峰の作法)

エージェントループの本質は「モデルがツールを要求 → 検証して実行 → 結果を戻す → 完了まで繰り返す」。ここに上限・検証・冪等性を織り込みます。

// lib/agent-loop.ts — 反復上限つき・引数検証つき・allowlistつきの安全なループ
import OpenAI from "openai";
import type { Tool } from "./tools";
import { toOpenAITool } from "./tools";
import { z } from "zod";

const client = new OpenAI({ baseURL: process.env.QWEN_BASE_URL, apiKey: "internal", timeout: 60_000 });

export async function runAgent(
  userMessage: string,
  registry: ReadonlyMap<string, Tool<z.ZodType>>, // allowlist:ここに無いツールは呼べない
  maxSteps = 6,                                    // 暴走・無限ループの上限(必須)
): Promise<string> {
  const messages: OpenAI.ChatCompletionMessageParam[] = [
    { role: "system", content: "ツールは必要な時だけ使う。引数は厳密に。" },
    { role: "user", content: userMessage },
  ];
  const tools = [...registry.values()].map(toOpenAITool);

  for (let step = 0; step < maxSteps; step++) {
    const res = await client.chat.completions.create({
      model: "Qwen/Qwen3-8B-AWQ", messages, tools,
      temperature: 0.7, top_p: 0.8,
      extra_body: { top_k: 20, chat_template_kwargs: { enable_thinking: false } }, // ツール選択は非思考で安定
    });
    const msg = res.choices[0]?.message;
    if (!msg) throw new Error("empty response");
    messages.push(msg);

    const calls = msg.tool_calls ?? [];
    if (calls.length === 0) return msg.content ?? ""; // ツール不要=最終回答

    // 要求された各ツールを「検証してから」実行(並列でも順次でも、結果を必ず戻す)
    for (const call of calls) {
      const tool = registry.get(call.function.name);
      // allowlist 外のツール要求は実行せず、その旨をモデルへ返す(落とさない)
      const result = tool
        ? await executeChecked(tool, call.function.arguments)
        : { error: `tool not allowed: ${call.function.name}` };
      messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) });
    }
  }
  throw new AgentStepLimitError(`exceeded ${maxSteps} steps`); // 上限超過は明示的に失敗させる
}

/** 引数は“外部入力”。Zodで検証してからのみ実行。検証NGは実行せずモデルへ差し戻す。 */
async function executeChecked(tool: Tool<z.ZodType>, rawArgs: string): Promise<unknown> {
  const parsed = tool.args.safeParse(safeJson(rawArgs));
  if (!parsed.success) return { error: "invalid arguments", issues: parsed.error.issues.length };
  return tool.execute(parsed.data); // ここで初めて副作用が起きる
}

const safeJson = (s: string): unknown => { try { return JSON.parse(s); } catch { return null; } };
export class AgentStepLimitError extends Error {}

この 60 行に、本番エージェントの安全装置が詰まっています。

  • 反復上限(maxSteps:無限ループ・暴走を構造的に止める(コスト暴発も防ぐ)。
  • allowlist(registry:登録外のツールは呼べない。モデルが幻のツールを要求しても実行されない。
  • 引数検証(executeCheckedfunction.argumentsJSON文字列の外部入力safeParse を通った時だけ実行。NG は差し戻して自己修正させる。
  • 判断と実行の分離:LLM は要求するだけ、副作用は決定的コードの中で起きる。

副作用ツールは「冪等+認可」で守る

読み取りツール(在庫照会)は気楽ですが、書き込み・送信・決済といった副作用ツールは別格です。LLM はリトライや並列で同じ呼び出しを二度要求しえます。

// 副作用ツールは「冪等キー+認可」を必須に(二重実行・権限外実行を防ぐ)
export const refund: Tool<z.ZodObject<{ orderId: z.ZodString; idempotencyKey: z.ZodString }>> = {
  name: "refund",
  description: "注文を返金する(要認可・冪等)",
  args: z.object({ orderId: z.string().uuid(), idempotencyKey: z.string().min(8) }),
  execute: async ({ orderId, idempotencyKey }) => {
    await authz.require("refund", orderId);          // 認可:誰の権限で実行するか
    return payments.refund(orderId, { idempotencyKey }); // 冪等:二度呼ばれても一度だけ効く
  },
};
declare const authz: { require(action: string, resource: string): Promise<void> };
declare const payments: { refund(id: string, o: { idempotencyKey: string }): Promise<unknown> };

冪等性の作法は決済の冪等設計と同じ。エージェントが絡むと「二重実行」は現実的リスクなので、副作用ツールは冪等キー必須で設計します。認可は UIのif文ではなくツール実行の中で強制します。


自前ループ vs Qwen-Agent

自前 OpenAI 互換ループ(本稿)Qwen-Agent
制御完全に握れる(上限・検証・ログ)フレームワークに委譲
定型自分で書くテンプレート/パースを吸収
学習コストOpenAI SDK が分かれば可ライブラリ作法を学ぶ
向く本番の作り込み・監査要件素早い試作・標準的なツール連携

Qwen-Agent は「Qwen3 の function calling を OpenAI 互換 API 上でテンプレート化し、ツール変換とパースを llm.chat() が自動処理」します。試作や標準ケースは Qwen-Agent監査・ガードを握りたい本番は自前ループ——が使い分けの目安です。どちらでも、引数検証・上限・冪等・認可の原則は変わりません。


ハマりどころ & ベストプラクティス

  • 🔴 LLM 出力をそのまま実行しないfunction.arguments は外部入力。Zod 検証を通った時だけ実行する。
  • 🔴 反復上限を必ず置くmaxSteps 無しのループはコスト暴発・無限ループの温床。超過は明示的に失敗させる。
  • 🔴 副作用ツールは冪等+認可。二重実行と権限外実行を構造で防ぐ。危険なツール(任意SQL/シェル)はそもそも登録しない
  • 🟠 ツール選択は非思考でも安定。tool calling 自体は非思考モードで十分速い。複雑な計画が要る時だけ思考を使う。
  • 🟠 思考モデルで ReAct/stopword テンプレを使わない(公式)。Hermes 形式(--tool-call-parser hermes)を使う。
  • 🟢 allowlist で最小権限。エージェントが触れるツールを必要最小限に。幻のツール要求は差し戻す。
  • 🟢 各ステップを可観測に。どのツールをどの引数で呼び、検証が通ったか/失敗したかをメタデータでログ(引数のPIIは伏せる)。

よくある質問(FAQ)

Q. 8B でツール選択は正確にできる? A. 単純〜中程度のツール選択は実用的です。ツール数を絞りdescription と引数スキーマを明確にするほど安定します。複雑な多段計画が要るなら思考モードや上位モデルへのルーティングを検討。

Q. 並列ツール呼び出しは使える? A. モデルは1レスポンスで複数の tool call を返すことがあります。ループ側で各 call を検証して実行し、結果を全て戻す設計にしておけば、順次でも並列でも破綻しません(本稿の実装が対応済み)。

Q. arguments が壊れたJSONで返ってきたら? A. executeCheckedsafeJsonsafeParse実行せず差し戻します。モデルは差し戻しメッセージを見て自己修正します。壊れた引数で副作用を起こさないのが要点。

Q. Qwen-Agent と自前ループ、どっち? A. 試作・標準ケースは Qwen-Agent監査・ガード・ログを握りたい本番は自前ループ。原則(検証・上限・冪等・認可)はどちらでも必須です。

Q. エージェントのコストが心配です。 A. 反復上限でステップ数を、非思考モードで出力トークンを抑えます。ツール結果のキャッシュやモデルルーティングも効きます。各ステップのトークンを可観測にして削りどころを見つけます。


まとめ

Qwen3-8B-AWQ のエージェント化は、「判断は LLM・実行は決定的コード」を、型と安全装置で徹底すれば本番に耐えます。

  1. Hermes 形式で有効化--enable-auto-tool-choice --tool-call-parser hermes)。思考モデルで ReAct は使わない。
  2. ツールは型付き契約——Zod を真実源に tools を生成し、返った引数を実行前に検証。
  3. 安全なループ——反復上限・allowlist・引数検証・差し戻し。
  4. 副作用は冪等+認可——二重実行・権限外実行を構造で防ぐ。
  5. 試作は Qwen-Agent、本番は自前ループ——原則は不変。

自前LLMのエージェント化を、ツール設計・安全なループ・冪等な副作用・認可・可観測性まで含めて本番品質で構築します。AI基盤の実績をご覧のうえご相談ください。一人 × 生成AIで、速く・安く・安全に。

出典・公式リソース

※ tool calling 仕様・vLLM フラグは更新されます。実装前に一次情報とお使いの版で必ず確認してください。

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事で解説した技術の適用事例

AI動画ローカライズ・リップシンク基盤

ケーススタディを見る