👋

DGX Spark + SGLang + Qwen3.6-35B-A3B-FP8 環境構築 その2: LiteLLM Proxy 活用

に公開

DGX Spark 上の Docker 環境で、SGLang と Qwen/Qwen3.6-35B-A3B-FP8 を動かす構成についての続編です。

前編はこちらです。

DGX Spark + Docker + SGLang + Qwen3.6-35B-A3B-FP8 環境構築

前編では、DGX Spark 上で SGLang の OpenAI-compatible API server を起動し、その前段に Python 標準ライブラリ製の gateway を置く構成を書きました。

この記事では、その構成を変更して、自作 gateway をやめ、LiteLLM Proxy を使う構成にします。

また、Hermes Agent や OpenCode のような Agent クライアントが送る tools / function calling request を、OpenAI-compatible API として正しく受けられる構成にします。

今回の構成変更

前編では、SGLang の前段に自作の Python gateway を置いていました。

その gateway では、次の処理をしていました。

- API key 認証
- SGLang API への中継
- enable_thinking 未指定時の補完
- stream=true の転送

ただ、この構成では gateway の HTTP 中継処理、ストリーミング転送、認証、JSON 変換などを自分で持つことになります。

小さな実装ではありますが、LLM gateway として長く使うなら、自作コードはできるだけ減らした方がよいと考えました。

そこで、今回は自作 gateway を外して、LiteLLM Proxy を使います。

新しい責務分担

新しい構成では、SGLang と LiteLLM の役割を分けます。

SGLang:
  Qwen3.6-35B-A3B-FP8 の推論だけを担当
  API key 認証なし
  Docker network 内部の 29999 のみ
  外部公開しない

LiteLLM:
  外部公開する OpenAI-compatible gateway
  API key 発行・認証を担当
  model alias を提供
  passthrough / fast / think を分ける
  Agent からの tools 付き request を upstream に渡す

SGLang は、GPU を使った推論サーバーとして内部に閉じます。

外部のクライアント、OpenCode、Hermes Agent、Open WebUI などは、SGLang に直接接続せず、LiteLLM に接続します。

Client

LiteLLM Proxy

SGLang

Qwen/Qwen3.6-35B-A3B-FP8

この構成にする理由

自作 gateway をなくせる

一番大きな理由は、自作 gateway をなくせることです。

前編の gateway は、Python 標準ライブラリだけで作っていたので構成としては単純でした。

ただし、実際には次のような責務を持っていました。

- HTTP request の受信
- request body size の制限
- JSON parse / serialize
- SGLang への HTTP 中継
- text/event-stream の逐次転送
- API key 認証
- CORS / OPTIONS
- エラーハンドリング

これらは、LLM gateway が本来持つべき機能です。

LiteLLM Proxy を使えば、OpenAI 互換 API、モデル alias、API key 管理、利用制御、streaming、ログなどを LiteLLM 側に任せられます。

SGLang を内部推論サーバーに戻せる

SGLang 本体は外部公開しません。

SGLang:
  29999
  expose のみ
  API key なし

これにより、SGLang 側の設定を単純にできます。

SGLang は Qwen3.6 をロードして、LiteLLM からの request を処理するだけです。

API key 認証を LiteLLM に集約できる

前編では、Python gateway が API key 認証をしていました。

今回は、LiteLLM が API key 認証を担当します。

Client:
  Authorization: Bearer <LiteLLM API key>

LiteLLM:
  key を検査する

SGLang:
  API key なし
  Docker network 内部のみ

SGLang が API key を持たないため、SGLang とクライアントの認証を分けて考える必要がありません。

公開 endpoint は LiteLLM だけです。

Hermes Agent / OpenCode の tools request を扱える

Hermes Agent や OpenCode のような Agent クライアントは、通常の chat completion request でも tools を送る場合があります。

これは異常ではなく、Agent としては想定動作です。

例えば Hermes では、実際の request payload が次のようになることがあります。

payload keys: ['model', 'messages', 'tools']

そのため、OpenAI-compatible API server 側は、少なくとも次のどちらかに対応している必要があります。

A. tools / function calling を正しく処理する

B. tools 非対応の場合でも、
   tools を受け取っただけではエラーにせず、
   tools を無視して通常の chat completion として処理する

tools が含まれるだけで No connected db のようなエラーを返す場合は、クライアント側ではなく、OpenAI-compatible API 実装側の互換性問題として扱います。

特に、tools の存在を「DB接続が必要な機能」と誤判定している gateway / backend 実装では、Agent クライアントからの通常リクエストが失敗します。

この構成では、LiteLLM を OpenAI-compatible gateway として使い、SGLang 側で Qwen3 Coder 系の tool calling parser を有効にします。

そのため、Hermes Agent や OpenCode から tools が送られる前提で確認します。

passthrough / fast / think をモデル名で分けられる

LiteLLM では、クライアントから見えるモデル名を alias として定義できます。

今回は、次の3つを定義します。

qwen3.6-35b-a3b-fp8:
  enable_thinking はクライアント依存

qwen3.6-35b-a3b-fp8-fast:
  enable_thinking 未指定時に false を付与

qwen3.6-35b-a3b-fp8-think:
  enable_thinking 未指定時に true を付与

/v1/models で見たときに、どの LLM を使っているか分かるように、元モデル名を含めた alias にします。

fast / think は「強制」ではなく preset として扱う

今回の検証では、LiteLLM の extra_body は、クライアントが chat_template_kwargs を指定しない場合の既定値付与として動作しました。

一方で、クライアントが次のように明示した場合は、クライアント側の指定が優先されました。

{
  "chat_template_kwargs": {
    "enable_thinking": true
  }
}

正確には次のように扱います。

qwen3.6-35b-a3b-fp8-fast:
  クライアントが enable_thinking を指定しない場合に false を付与する preset model alias

qwen3.6-35b-a3b-fp8-think:
  クライアントが enable_thinking を指定しない場合に true を付与する preset model alias

通常の Hermes Agent、OpenCode、Open WebUI などでは、クライアント側が毎回 chat_template_kwargs.enable_thinking を明示しないことが多いため、この構成で実用上は十分に使い分けできます。

クライアント指定を無視して厳密に強制したい場合は、LiteLLM の custom callback や、別の request rewrite proxy が必要になります。

この記事では、運用をシンプルにするため、LiteLLM の extra_body による preset 方式にしています。

最終構成

OpenCode / Hermes Agent / Open WebUI / curl

http://<DGX Spark IP>:30000/v1
Authorization: Bearer <LiteLLM API key>

LiteLLM Proxy
  0.0.0.0:30000

  model:
    qwen3.6-35b-a3b-fp8
    qwen3.6-35b-a3b-fp8-fast
    qwen3.6-35b-a3b-fp8-think

SGLang
  http://sglang-qwen36-35b-a3b-fp8:29999/v1
  Docker network 内部のみ

Qwen/Qwen3.6-35B-A3B-FP8

外部公開する port は LiteLLM の 30000 だけです。

外部公開:
  LiteLLM: 30000

内部のみ:
  SGLang: 29999

作業ディレクトリ

前編と同じ作業ディレクトリを使います。

cd ~/docker/sglang-qwen36-35b-a3b-fp8

最終的なファイル構成は次のようにします。

~/docker/sglang-qwen36-35b-a3b-fp8
  ├── .env
  ├── docker-compose.yml
  ├── litellm_config.yaml
  ├── hf-cache/
  └── logs/

.env

以下の内容で .env を作成します。

nano .env
# ==============================================================================
# Hugging Face
# ==============================================================================

# Hugging Face token です。
#
# Public model では空でも動作する場合があります。
# ただし、大きな model を取得する場合は、rate limit や download speed の面で
# token を設定しておく方が安定します。
#
# gated model、private model、組織アカウント経由での取得が必要な場合も、
# ここに Hugging Face access token を設定します。
#
# 例:
# HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
HF_TOKEN=


# ==============================================================================
# LiteLLM public gateway settings
# ==============================================================================

# 外部クライアントから接続する LiteLLM Proxy の公開 port です。
#
# OpenCode、Hermes Agent、Open WebUI、curl などのクライアントは、
# http://<DGX Spark IP>:30000/v1 を base URL として使います。
#
# SGLang 本体は外部公開しないため、外部から見える入口はこの port だけです。
LITELLM_PORT=30000

# LiteLLM の master key です。
#
# 管理用 key として使います。
# virtual key 発行には Postgres も必要になるため、この記事では割愛して、
# master key を API 認証にも使います。
#
# 生成例:
#   openssl rand -hex 32
#
# OpenAI 互換クライアントで扱いやすいように sk- で始めています。
LITELLM_MASTER_KEY=sk-replace_with_random_long_value

# LiteLLM の key 管理や内部暗号化用途で使う salt です。
#
# master key とは別のランダム値にします。
#
# 生成例:
#   openssl rand -hex 32
LITELLM_SALT_KEY=replace_with_random_long_value

# LiteLLM が upstream OpenAI-compatible endpoint に渡す dummy key です。
#
# SGLang 側は Docker network 内部専用で API key 認証なしにします。
# そのため、この値は SGLang 側では検査されません。
#
# ただし、LiteLLM の OpenAI-compatible provider では api_key の指定が必要になるため、
# 空ではなく dummy 値を入れます。
SGLANG_DUMMY_API_KEY=sglang-local-dummy-key


# ==============================================================================
# SGLang server basic settings
# ==============================================================================

# SGLang 本体の内部 port です。
#
# docker-compose.yml では ports ではなく expose だけを使います。
# そのため、ホストや LAN から http://<DGX Spark IP>:29999 へは接続できません。
#
# LiteLLM container から Docker network 内部でのみ接続します。
SGLANG_PORT=29999

# LiteLLM から見た SGLang の OpenAI-compatible API base URL です。
#
# docker-compose.yml の service 名を host 名として使います。
# SGLang と LiteLLM は同じ Docker Compose project 内にいるため、
# host.docker.internal ではなく service 名で接続します。
SGLANG_BASE_URL=http://sglang-qwen36-35b-a3b-fp8:29999/v1

# SGLang 上で提供する実モデル名です。
#
# LiteLLM 側では、この backend に対して3つの alias を定義します。
#
# alias:
#   qwen3.6-35b-a3b-fp8
#   qwen3.6-35b-a3b-fp8-fast
#   qwen3.6-35b-a3b-fp8-think
SERVED_MODEL_NAME=qwen3.6-35b-a3b-fp8

# 起動する Hugging Face model path です。
#
# ここで指定した checkpoint を SGLang がロードします。
MODEL_PATH=Qwen/Qwen3.6-35B-A3B-FP8


# ==============================================================================
# LiteLLM model alias names
# ==============================================================================

# enable_thinking をクライアント側に任せる model alias です。
#
# suffix を付けないことで、素の Qwen3.6 model として扱いやすくします。
LITELLM_MODEL_PASSTHROUGH=qwen3.6-35b-a3b-fp8

# enable_thinking 未指定時に false を付与する model alias です。
#
# Agent の通常作業、tool calling、長めの context、速度優先の用途で使います。
#
# 注意:
# クライアントが chat_template_kwargs.enable_thinking=true を明示した場合は、
# クライアント指定が優先されることを確認しています。
# そのため、これは「強制無効」ではなく「未指定時の preset」です。
LITELLM_MODEL_FAST=qwen3.6-35b-a3b-fp8-fast

# enable_thinking 未指定時に true を付与する model alias です。
#
# 設計判断、原因分析、難しいレビューなど、
# 短〜中コンテキストで高品質な推論が必要な場合に使います。
#
# 注意:
# これは「強制有効」ではなく「未指定時の preset」です。
LITELLM_MODEL_THINK=qwen3.6-35b-a3b-fp8-think


# ==============================================================================
# Long context settings
# ==============================================================================

# SGLang 側の最大コンテキスト長です。
#
# 262144 は 256K tokens です。
# Agent 作業や長いリポジトリ文脈を扱うため、SGLang 側の上限を大きくしています。
CONTEXT_LENGTH=262144

# LiteLLM の passthrough / fast model_info に表示する最大入力 token 数です。
#
# SGLang 側の CONTEXT_LENGTH と同じ値にします。
# /model/info などでクライアントに見える情報として使います。
#
# 注意:
# os.environ 経由で litellm_config.yaml に渡すため、
# LiteLLM 側で数値として扱われるかは起動後に /model/info で確認します。
LITELLM_MAX_INPUT_TOKENS=262144

# LiteLLM の think model_info に表示する最大入力 token 数です。
#
# think model は全コンテキスト用ではなく、
# 短〜中コンテキストで高品質推論を行う用途のため、小さめにします。
#
# 注意:
# これは model_info 上の目安です。
# 厳密な token 制限を行う場合は、LiteLLM 側の制限機能やクライアント側制御も併用します。
LITELLM_THINK_MAX_INPUT_TOKENS=32768

# 1回の生成で許可する最大出力 token 数です。
#
# SGLang の最大出力 token 目安、および LiteLLM の model_info に使います。
MAX_NEW_TOKENS=8192

# LiteLLM の model_info に表示する最大出力 token 数です。
#
# 基本的には MAX_NEW_TOKENS と同じ値にします。
LITELLM_MAX_OUTPUT_TOKENS=8192


# ==============================================================================
# LiteLLM local model cost metadata
# ==============================================================================

# ローカルモデルなので、LiteLLM の model_info 上の入力単価は 0 にします。
#
# LiteLLM の管理画面や /model/info で参照される metadata 用です。
LITELLM_INPUT_COST_PER_TOKEN=0

# ローカルモデルなので、LiteLLM の model_info 上の出力単価も 0 にします。
#
# LiteLLM の管理画面や /model/info で参照される metadata 用です。
LITELLM_OUTPUT_COST_PER_TOKEN=0


# ==============================================================================
# Performance / memory tuning
# ==============================================================================

# SGLang の static memory allocation 比率です。
#
# model weights と KV cache pool に使う GPU memory の割合に関係します。
# FP8 checkpoint を使うため、初期値は 0.85 にしています。
MEM_FRACTION_STATIC=0.85

# Chunked prefill size です。
#
# 長い prompt を分割して prefill する単位です。
# 大きいほど速くなる可能性がありますが、ピークメモリも増える場合があります。
CHUNKED_PREFILL_SIZE=8192

# 同時に実行する request 数です。
#
# 長コンテキスト Agent 用途では、KV cache 使用量が大きくなります。
# 初期値は安全寄りに 1 にします。
MAX_RUNNING_REQUESTS=1

# queue 上限です。
#
# Agent から連続 request が来た場合に、即エラーにせず queue 上で待たせるための上限です。
MAX_QUEUED_REQUESTS=8

# KV cache dtype です。
#
# auto は SGLang 側に判断を任せる設定です。
# メモリ削減が必要な場合のみ、fp8_e5m2 などを検討します。
KV_CACHE_DTYPE=auto


# ==============================================================================
# Qwen3.6-35B-A3B-FP8 specific tuning
# ==============================================================================

# Mamba / MoE 系の scheduler strategy です。
#
# no_buffer:
#   長コンテキスト運用でメモリ使用量を抑えやすい安全寄りの設定です。
MAMBA_SCHEDULER_STRATEGY=no_buffer

# Mamba / GDN 系 state dtype です。
#
# bfloat16 にすることで、float32 よりもメモリ使用量を抑えやすくします。
MAMBA_SSM_DTYPE=bfloat16


# ==============================================================================
# Runtime behavior
# ==============================================================================

# Python stdout / stderr を buffer しない設定です。
#
# docker logs -f で SGLang や LiteLLM のログをリアルタイム確認しやすくします。
PYTHONUNBUFFERED=1

# tokenizer 並列処理 warning を抑制します。
TOKENIZERS_PARALLELISM=false

# PyTorch CUDA allocator の追加設定です。
#
# 初期状態では空にします。
# 長時間運用でメモリ断片化が疑われる場合などに、
# expandable_segments:True などを必要に応じて設定します。
PYTORCH_CUDA_ALLOC_CONF=

LITELLM_MASTER_KEYLITELLM_SALT_KEY を生成します。

openssl rand -hex 32
openssl rand -hex 32

.env に反映した後、権限を絞ります。

chmod 600 .env

docker-compose.yml

以下の内容で docker-compose.yml を作成します。

nano docker-compose.yml
services:
  sglang-qwen36-35b-a3b-fp8:
    # NVIDIA NGC の SGLang コンテナイメージです。
    #
    # ホスト側に Python 環境を作らず、
    # SGLang 実行環境を Docker コンテナ内に閉じ込めます。
    image: nvcr.io/nvidia/sglang:26.04-py3

    # コンテナ名です。
    #
    # docker logs、docker exec、docker restart などで指定しやすくするため固定します。
    container_name: sglang-qwen36-35b-a3b-fp8

    # Docker daemon やホスト再起動後に自動復旧させます。
    #
    # unless-stopped は、手動停止した場合には自動再起動しないサーバー運用向けの設定です。
    restart: unless-stopped

    # NVIDIA GPU をコンテナに渡します。
    #
    # DGX Spark 上で SGLang サーバー専用に使う前提では all で問題ありません。
    gpus: all

    # PyTorch / SGLang の共有メモリや multi-process 通信用に host IPC を使います。
    #
    # 長コンテキストや大きなモデルを扱うため、共有メモリ不足を避ける目的があります。
    ipc: host

    # SGLang は Docker network 内だけで公開します。
    #
    # expose はコンテナ間通信用です。
    # ホスト側や LAN には 29999 を公開しません。
    #
    # 外部クライアントは LiteLLM の 30000 に接続します。
    expose:
      - "${SGLANG_PORT:-29999}"

    # .env から SGLang と LiteLLM 共通の環境変数を読み込みます。
    env_file:
      - .env

    environment:
      # Hugging Face token です。
      #
      # .env の HF_TOKEN をコンテナ内へ渡します。
      HF_TOKEN: ${HF_TOKEN:-}

      # Hugging Face cache の基準ディレクトリです。
      #
      # volumes で ./hf-cache に bind mount して永続化します。
      HF_HOME: /root/.cache/huggingface

      # Hugging Face Hub から取得したモデルファイルの cache 先です。
      #
      # 再起動やコンテナ再作成後も同じ cache を使うため、再ダウンロードを避けられます。
      HF_HUB_CACHE: /root/.cache/huggingface/hub

      # Transformers 互換の cache 設定です。
      #
      # ライブラリ側が TRANSFORMERS_CACHE を参照する場合に備えます。
      TRANSFORMERS_CACHE: ${TRANSFORMERS_CACHE:-/root/.cache/huggingface}

      # Python の標準出力を即時出力します。
      PYTHONUNBUFFERED: ${PYTHONUNBUFFERED:-1}

      # tokenizer 並列処理の warning を抑制します。
      TOKENIZERS_PARALLELISM: ${TOKENIZERS_PARALLELISM:-false}

      # PyTorch CUDA allocator の追加設定です。
      #
      # 初期状態では空です。
      # 必要な場合だけ .env 側で値を指定します。
      PYTORCH_CUDA_ALLOC_CONF: ${PYTORCH_CUDA_ALLOC_CONF:-}

    volumes:
      # Hugging Face model cache をホストへ永続化します。
      #
      # 初回起動時に取得したモデルファイルを保存し、
      # 2回目以降の起動で再利用します。
      - ./hf-cache:/root/.cache/huggingface

      # ログや補助ファイルを保存するためのディレクトリです。
      #
      # SGLang 本体の主なログは標準出力へ出るため、
      # 通常は docker logs で確認します。
      - ./logs:/logs

    command:
      # Python module として SGLang server を起動します。
      - python3
      - -m
      - sglang.launch_server

      # 起動する Hugging Face model path です。
      - --model-path
      - ${MODEL_PATH}

      # SGLang が OpenAI-compatible API 上で提供する実モデル名です。
      #
      # LiteLLM 側では、この backend model に対して3つの alias を定義します。
      - --served-model-name
      - ${SERVED_MODEL_NAME}

      # コンテナ内で listen するアドレスです。
      #
      # 0.0.0.0 はコンテナ内の全 network interface で listen する指定です。
      # ただし docker-compose.yml 側では ports を使っていないため、外部公開はされません。
      - --host
      - 0.0.0.0

      # SGLang 本体の内部 listen port です。
      #
      # LiteLLM から Docker network 内部で接続します。
      - --port
      - ${SGLANG_PORT}

      # 最大コンテキスト長です。
      #
      # 262144 は 256K tokens です。
      - --context-length
      - ${CONTEXT_LENGTH}

      # Qwen3 系の reasoning parser です。
      #
      # enable_thinking=true のときに reasoning content と通常 content の扱いに関係します。
      - --reasoning-parser
      - qwen3

      # Qwen3 Coder 系の tool calling parser です。
      #
      # Hermes Agent や OpenCode は、通常の chat completion request でも
      # tools / function calling 用の定義を送る場合があります。
      #
      # そのため、SGLang 側では Qwen3 Coder 系の tool call parser を有効にします。
      # tools を含む request を Agent の通常リクエストとして扱うために重要です。
      - --tool-call-parser
      - qwen3_coder

      # KV cache dtype です。
      #
      # 初期値は auto です。
      - --kv-cache-dtype
      - ${KV_CACHE_DTYPE}

      # static memory allocation 比率です。
      #
      # model weights と KV cache pool に割り当てる GPU memory 量に影響します。
      - --mem-fraction-static
      - ${MEM_FRACTION_STATIC}

      # chunked prefill size です。
      #
      # 長い prompt の prefill を分割し、ピークメモリや処理速度のバランスを調整します。
      - --chunked-prefill-size
      - ${CHUNKED_PREFILL_SIZE}

      # 同時実行 request 数です。
      #
      # 長コンテキスト AI Agent 用途では 1 が安全です。
      - --max-running-requests
      - ${MAX_RUNNING_REQUESTS}

      # queue 上限です。
      #
      # Agent から連続 request が来た場合に、即エラーにせず待たせるための上限です。
      - --max-queued-requests
      - ${MAX_QUEUED_REQUESTS}

      # Qwen3.6-35B-A3B-FP8 の hybrid / GDN / Mamba 系 scheduler 設定です。
      #
      # 初期値は no_buffer です。
      - --mamba-scheduler-strategy
      - ${MAMBA_SCHEDULER_STRATEGY}

      # Mamba / GDN 系 state dtype です。
      #
      # bfloat16 により、長コンテキスト運用時のメモリ使用量を抑えます。
      - --mamba-ssm-dtype
      - ${MAMBA_SSM_DTYPE}

    healthcheck:
      # SGLang の health endpoint を確認します。
      #
      # LiteLLM は SGLang の起動完了後に立ち上げたいので、
      # depends_on でこの healthcheck を参照します。
      test: ["CMD-SHELL", "curl -f http://localhost:${SGLANG_PORT:-29999}/health || exit 1"]

      # 30秒ごとに health check を実行します。
      interval: 30s

      # 1回の health check の timeout です。
      timeout: 10s

      # モデルロードに時間がかかるため、retry は多めにします。
      retries: 20

      # 初回モデルロード時間を考慮して、開始猶予を長めにします。
      start_period: 300s

  litellm-qwen-gateway:
    # LiteLLM Proxy の公式コンテナイメージです。
    #
    # OpenAI-compatible gateway、API key 管理、model alias、extra_body による preset などを担当します。
    image: ghcr.io/berriai/litellm:main-latest

    # コンテナ名です。
    container_name: litellm-qwen-gateway

    # Docker daemon やホスト再起動後に自動復旧させます。
    restart: unless-stopped

    # LiteLLM だけを外部公開します。
    #
    # ホスト側:
    #   30000
    #
    # コンテナ内部:
    #   4000
    #
    # クライアントは http://<DGX Spark IP>:30000/v1 に接続します。
    ports:
      - "0.0.0.0:${LITELLM_PORT:-30000}:4000"

    # .env から master key、model alias、SGLang 接続先などを読み込みます。
    env_file:
      - .env

    environment:
      # LiteLLM の master key です。
      #
      # 管理 API や virtual key 発行に使います。
      # virtual key 発行には Postgres も必要になるため、この記事では割愛して、
      # master key を API 認証にも使います。
      LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY}

      # LiteLLM の key 管理・暗号化用途で使う salt です。
      LITELLM_SALT_KEY: ${LITELLM_SALT_KEY}

      # LiteLLM から SGLang に接続する URL です。
      #
      # 同じ Docker Compose project 内なので、SGLang の service 名で接続します。
      SGLANG_BASE_URL: ${SGLANG_BASE_URL}

      # SGLang upstream に渡す dummy API key です。
      #
      # SGLang 側では認証しませんが、LiteLLM の OpenAI-compatible provider が
      # api_key を要求するため、空でない値を渡します。
      SGLANG_DUMMY_API_KEY: ${SGLANG_DUMMY_API_KEY}

      # SGLang が提供する実モデル名です。
      SERVED_MODEL_NAME: ${SERVED_MODEL_NAME}

      # LiteLLM からクライアントに見せる model alias です。
      LITELLM_MODEL_PASSTHROUGH: ${LITELLM_MODEL_PASSTHROUGH}
      LITELLM_MODEL_FAST: ${LITELLM_MODEL_FAST}
      LITELLM_MODEL_THINK: ${LITELLM_MODEL_THINK}

      # LiteLLM の /model/info に表示する token 上限や cost metadata です。
      LITELLM_MAX_INPUT_TOKENS: ${LITELLM_MAX_INPUT_TOKENS}
      LITELLM_THINK_MAX_INPUT_TOKENS: ${LITELLM_THINK_MAX_INPUT_TOKENS}
      LITELLM_MAX_OUTPUT_TOKENS: ${LITELLM_MAX_OUTPUT_TOKENS}
      LITELLM_INPUT_COST_PER_TOKEN: ${LITELLM_INPUT_COST_PER_TOKEN}
      LITELLM_OUTPUT_COST_PER_TOKEN: ${LITELLM_OUTPUT_COST_PER_TOKEN}

      # model_info metadata に実 checkpoint 名を表示するために使います。
      MODEL_PATH: ${MODEL_PATH}

      # Python の標準出力を即時出力します。
      PYTHONUNBUFFERED: ${PYTHONUNBUFFERED:-1}

    volumes:
      # LiteLLM の設定ファイルです。
      #
      # 3つの model alias と backend SGLang 接続先を定義します。
      - ./litellm_config.yaml:/app/config.yaml:ro

    command:
      # LiteLLM Proxy に設定ファイルを渡します。
      - --config
      - /app/config.yaml

      # コンテナ内部の listen port です。
      #
      # ホスト側の 30000 からこの 4000 に port mapping します。
      - --port
      - "4000"

      # コンテナ内の全 network interface で listen します。
      - --host
      - 0.0.0.0

    depends_on:
      # SGLang のモデルロードと health check 完了後に LiteLLM を起動します。
      #
      # 起動順を安定させるための設定です。
      sglang-qwen36-35b-a3b-fp8:
        condition: service_healthy

litellm_config.yaml

LiteLLM の設定ファイルを作成します。

nano litellm_config.yaml
# ==============================================================================
# LiteLLM Proxy configuration
# ==============================================================================
#
# この設定では、1つの SGLang backend に対して、
# クライアントから見える3つの model alias を定義します。
#
# 1. qwen3.6-35b-a3b-fp8
#    - enable_thinking はクライアント依存
#
# 2. qwen3.6-35b-a3b-fp8-fast
#    - enable_thinking 未指定時に false を付与
#
# 3. qwen3.6-35b-a3b-fp8-think
#    - enable_thinking 未指定時に true を付与
#
# 実際の SGLang backend は同じです。
# 違いは、LiteLLM の extra_body で chat_template_kwargs.enable_thinking を
# 既定値として付与するかどうかです。
#
# 注意:
# extra_body は、今回の検証では「クライアント未指定時の preset」として動作しました。
# クライアントが chat_template_kwargs.enable_thinking を明示した場合は、
# クライアント指定が優先されました。
#
# そのため、fast / think は強制制御ではなく、通常利用向けの preset model alias として扱います。
#
# できるだけ .env に設定値を集約するため、
# model 名、backend URL、token 上限、cost metadata は
# os.environ/<VARIABLE_NAME> 形式で参照します。
#
# 注意:
# max_input_tokens などの数値項目も os.environ 経由で参照しています。
# 起動後に /model/info を確認し、期待通り解釈されているか確認します。
# ==============================================================================

model_list:
  # --------------------------------------------------------------------------
  # 1. Passthrough model
  # --------------------------------------------------------------------------
  #
  # クライアントから見える通常モデルです。
  #
  # enable_thinking は LiteLLM 側では変更しません。
  # クライアントが chat_template_kwargs.enable_thinking を指定した場合は、
  # その値がそのまま SGLang に渡ります。
  #
  # クライアント側で thinking を明示制御できる場合や、
  # 素の SGLang に近い挙動で使いたい場合に使います。
  - model_name: os.environ/LITELLM_MODEL_PASSTHROUGH
    litellm_params:
      # LiteLLM が upstream に送る model 名です。
      #
      # SGLang の --served-model-name と一致させます。
      model: os.environ/SERVED_MODEL_NAME

      # OpenAI-compatible endpoint として SGLang を呼び出します。
      custom_llm_provider: openai

      # LiteLLM から見た SGLang の API base URL です。
      #
      # 同じ Docker Compose project 内なので service 名で接続します。
      api_base: os.environ/SGLANG_BASE_URL

      # SGLang 側では認証しませんが、
      # LiteLLM の OpenAI-compatible provider で api_key が必要なため dummy を渡します。
      api_key: os.environ/SGLANG_DUMMY_API_KEY

    model_info:
      # LiteLLM 内部での識別用 ID です。
      #
      # .env 側の alias と同じ値にしておくと、
      # /model/info で見たときに model 名との対応が分かりやすくなります。
      id: os.environ/LITELLM_MODEL_PASSTHROUGH

      # chat completion 用モデルとして扱います。
      mode: chat

      # ローカルモデルなので課金単価は .env 上で 0 にしています。
      #
      # LiteLLM の管理 UI や /model/info で参照される metadata です。
      input_cost_per_token: os.environ/LITELLM_INPUT_COST_PER_TOKEN
      output_cost_per_token: os.environ/LITELLM_OUTPUT_COST_PER_TOKEN

      # passthrough は SGLang 側の最大コンテキスト長と同じ目安にします。
      #
      # この値は model_info 上の情報です。
      # 厳密な token 制限が必要な場合は、LiteLLM の制限機能や
      # クライアント側の max context 設定も併用します。
      max_input_tokens: os.environ/LITELLM_MAX_INPUT_TOKENS

      # 最大出力 token 目安です。
      max_output_tokens: os.environ/LITELLM_MAX_OUTPUT_TOKENS

      # /model/info などで確認するための説明です。
      description: Qwen3.6 35B A3B FP8 via SGLang. enable_thinking is controlled by the client.

      # 運用時に backend や thinking mode を確認しやすくする metadata です。
      metadata:
        backend: sglang
        real_model: os.environ/MODEL_PATH
        thinking_mode: passthrough

  # --------------------------------------------------------------------------
  # 2. Fast preset model
  # --------------------------------------------------------------------------
  #
  # Agent の通常作業向けモデルです。
  #
  # LiteLLM の extra_body により、
  # クライアントが chat_template_kwargs.enable_thinking を指定しない場合に、
  # enable_thinking=false を付与します。
  #
  # 注意:
  # クライアントが chat_template_kwargs.enable_thinking=true を明示した場合は、
  # クライアント指定が優先されました。
  #
  # そのため、この alias は「強制無効」ではなく、
  # 「未指定時に Thinking 無効として使う preset」です。
  #
  # コード修正、ファイル操作、tool calling、長めの context では、
  # 通常こちらを使います。
  - model_name: os.environ/LITELLM_MODEL_FAST
    litellm_params:
      # upstream の実モデル名です。
      #
      # SGLang の --served-model-name と一致させます。
      model: os.environ/SERVED_MODEL_NAME

      # SGLang を OpenAI-compatible endpoint として扱います。
      custom_llm_provider: openai

      # SGLang backend URL です。
      api_base: os.environ/SGLANG_BASE_URL

      # SGLang 側では認証しないため dummy key を渡します。
      api_key: os.environ/SGLANG_DUMMY_API_KEY

      # fast preset の既定値です。
      #
      # クライアントが chat_template_kwargs を送らない場合、
      # SGLang に enable_thinking=false が渡ります。
      #
      # 検証結果:
      # - クライアント未指定の場合は reasoning_content が出ず、Thinking 無効として動作しました。
      # - クライアントが enable_thinking=true を明示した場合は、クライアント指定が優先されました。
      extra_body:
        chat_template_kwargs:
          enable_thinking: false

    model_info:
      # LiteLLM 内部での識別用 ID です。
      id: os.environ/LITELLM_MODEL_FAST

      # chat completion 用です。
      mode: chat

      # ローカルモデルなので課金単価は .env 上で 0 にしています。
      input_cost_per_token: os.environ/LITELLM_INPUT_COST_PER_TOKEN
      output_cost_per_token: os.environ/LITELLM_OUTPUT_COST_PER_TOKEN

      # fast は Agent 作業向けなので 256K 目安にします。
      #
      # 長い prompt、tool result、リポジトリ文脈などを扱うため、
      # passthrough と同じ値にしています。
      max_input_tokens: os.environ/LITELLM_MAX_INPUT_TOKENS

      # 最大出力 token 目安です。
      max_output_tokens: os.environ/LITELLM_MAX_OUTPUT_TOKENS

      # /model/info などで確認するための説明です。
      description: Qwen3.6 35B A3B FP8 via SGLang. enable_thinking is set to false when the client does not specify it.

      # thinking_mode=false を明示します。
      metadata:
        backend: sglang
        real_model: os.environ/MODEL_PATH
        thinking_mode: false
        thinking_control: preset_when_unspecified

  # --------------------------------------------------------------------------
  # 3. Think preset model
  # --------------------------------------------------------------------------
  #
  # 短〜中コンテキストの高品質推論向けモデルです。
  #
  # LiteLLM の extra_body により、
  # クライアントが chat_template_kwargs.enable_thinking を指定しない場合に、
  # enable_thinking=true を付与します。
  #
  # 注意:
  # これは「強制有効」ではなく「未指定時に Thinking 有効として使う preset」です。
  #
  # 設計判断、原因分析、難しいレビューなどで使います。
  # 長大な repository context や大量の tool result を投げる用途にはしません。
  - model_name: os.environ/LITELLM_MODEL_THINK
    litellm_params:
      # upstream の実モデル名です。
      #
      # SGLang の --served-model-name と一致させます。
      model: os.environ/SERVED_MODEL_NAME

      # SGLang を OpenAI-compatible endpoint として扱います。
      custom_llm_provider: openai

      # SGLang backend URL です。
      api_base: os.environ/SGLANG_BASE_URL

      # SGLang 側では認証しないため dummy key を渡します。
      api_key: os.environ/SGLANG_DUMMY_API_KEY

      # think preset の既定値です。
      #
      # クライアントが chat_template_kwargs を送らない場合、
      # SGLang に enable_thinking=true が渡ります。
      #
      # 検証結果:
      # - クライアント未指定の場合は reasoning_content が出て、
      #   Thinking 有効として動作しました。
      extra_body:
        chat_template_kwargs:
          enable_thinking: true

    model_info:
      # LiteLLM 内部での識別用 ID です。
      id: os.environ/LITELLM_MODEL_THINK

      # chat completion 用です。
      mode: chat

      # ローカルモデルなので課金単価は .env 上で 0 にしています。
      input_cost_per_token: os.environ/LITELLM_INPUT_COST_PER_TOKEN
      output_cost_per_token: os.environ/LITELLM_OUTPUT_COST_PER_TOKEN

      # think は全コンテキストを投げる用途ではないため、
      # 短〜中コンテキストの目安にします。
      #
      # 注意:
      # これは /model/info などに出す model_info 上の目安です。
      # 厳密な token 制限ではありません。
      max_input_tokens: os.environ/LITELLM_THINK_MAX_INPUT_TOKENS

      # 最大出力 token 目安です。
      max_output_tokens: os.environ/LITELLM_MAX_OUTPUT_TOKENS

      # /model/info などで確認するための説明です。
      description: Qwen3.6 35B A3B FP8 via SGLang. enable_thinking is set to true when the client does not specify it.

      # thinking_mode=true を明示します。
      metadata:
        backend: sglang
        real_model: os.environ/MODEL_PATH
        thinking_mode: true
        thinking_control: preset_when_unspecified

# ==============================================================================
# LiteLLM runtime settings
# ==============================================================================

litellm_settings:
  # クライアントから渡された追加パラメータを落とさない設定です。
  #
  # Hermes Agent や OpenCode は、通常の chat completion request でも
  # tools / function calling 用の定義を送る場合があります。
  #
  # また、SGLang 固有の chat_template_kwargs も upstream に渡したいため、
  # drop_params は false にします。
  #
  # 注意:
  # tools は OpenAI-compatible API では通常の request field です。
  # tools が含まれるだけで backend がエラーを返す場合は、
  # client ではなく backend / gateway 側の互換性問題として扱います。
  drop_params: false

  # 詳細ログを抑制します。
  #
  # 調査時だけ true にすると、LiteLLM の挙動を追いやすくなります。
  set_verbose: false

# ==============================================================================
# LiteLLM proxy settings
# ==============================================================================

general_settings:
  # LiteLLM の master key です。
  #
  # .env の LITELLM_MASTER_KEY を使います。
  # /key/generate などの管理 API で使います。
  master_key: os.environ/LITELLM_MASTER_KEY

起動

作業ディレクトリへ移動します。

cd ~/docker/sglang-qwen36-35b-a3b-fp8

SGLang と LiteLLM を起動します。

docker compose up -d

SGLang のログを確認します。

docker logs -f sglang-qwen36-35b-a3b-fp8

LiteLLM 側のログも確認します。

docker logs -f litellm-qwen-gateway

状態を確認します。

docker compose ps

LiteLLM の疎通確認

.env を読み込みます。

set -a
. ./.env
set +a

まず、master key で /v1/models を確認します。

curl -s http://localhost:30000/v1/models \
  -H "Authorization: Bearer ${LITELLM_MASTER_KEY}"

期待する model 名は次です。

qwen3.6-35b-a3b-fp8
qwen3.6-35b-a3b-fp8-fast
qwen3.6-35b-a3b-fp8-think

passthrough の確認

suffix なしの model は、enable_thinking をクライアント側に任せます。

curl -s http://localhost:30000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" \
  -d '{
    "model": "qwen3.6-35b-a3b-fp8",
    "messages": [
      {
        "role": "user",
        "content": "Say OK."
      }
    ],
    "max_tokens": 16,
    "temperature": 0.2
  }'

この場合、LiteLLM は chat_template_kwargs.enable_thinking を付与しません。

クライアントが明示したい場合は、次のように送ります。

curl -s http://localhost:30000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" \
  -d '{
    "model": "qwen3.6-35b-a3b-fp8",
    "messages": [
      {
        "role": "user",
        "content": "設計方針を比較してください。"
      }
    ],
    "chat_template_kwargs": {
      "enable_thinking": true
    },
    "max_tokens": 512,
    "temperature": 0.2
  }'

fast preset の確認

fast model は、クライアントが chat_template_kwargs を指定しない場合に、enable_thinking=false を付与します。

curl -s http://localhost:30000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" \
  -d '{
    "model": "qwen3.6-35b-a3b-fp8-fast",
    "messages": [
      {
        "role": "user",
        "content": "Say OK."
      }
    ],
    "max_tokens": 16,
    "temperature": 0.2
  }'

検証では、この場合は次のような通常 content が返り、reasoning_content は出ませんでした。

{
  "message": {
    "content": "OK",
    "role": "assistant"
  }
}

つまり、クライアント未指定時は fast preset として Thinking 無効で動作しています。

fast preset の注意点

クライアントが明示的に enable_thinking=true を送ると、クライアント指定が優先されました。

curl -s http://localhost:30000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" \
  -d '{
    "model": "qwen3.6-35b-a3b-fp8-fast",
    "messages": [
      {
        "role": "user",
        "content": "Say OK."
      }
    ],
    "chat_template_kwargs": {
      "enable_thinking": true
    },
    "max_tokens": 16,
    "temperature": 0.2
  }'

この場合は reasoning_content が出ました。

そのため、fast は「強制無効」ではなく、「未指定時に Thinking 無効で使うための preset」です。

think preset の確認

think model は、クライアントが chat_template_kwargs を指定しない場合に、enable_thinking=true を付与します。

curl -s http://localhost:30000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" \
  -d '{
    "model": "qwen3.6-35b-a3b-fp8-think",
    "messages": [
      {
        "role": "user",
        "content": "Say OK."
      }
    ],
    "max_tokens": 16,
    "temperature": 0.2
  }'

検証では、この場合は reasoning_content が出ました。

{
  "message": {
    "role": "assistant",
    "reasoning_content": "Thinking Process: ..."
  }
}

つまり、クライアント未指定時は think preset として Thinking 有効で動作しています。

tools 付き request の確認

Hermes Agent や OpenCode は、通常の request でも tools を送ることがあります。

そのため、LiteLLM 経由で tools を含む request がエラーにならないことを確認します。

curl -s http://localhost:30000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${LITELLM_MASTER_KEY}" \
  -d '{
    "model": "qwen3.6-35b-a3b-fp8-fast",
    "messages": [
      {
        "role": "user",
        "content": "Say OK. Do not call tools."
      }
    ],
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "dummy_tool",
          "description": "Dummy tool for compatibility test.",
          "parameters": {
            "type": "object",
            "properties": {
              "text": {
                "type": "string"
              }
            },
            "required": ["text"]
          }
        }
      }
    ],
    "tool_choice": "auto",
    "max_tokens": 64,
    "temperature": 0.2
  }'

期待する結果は、少なくとも HTTP 200 で通常の chat completion response が返ることです。

この確認では、tool call が必ず発生する必要はありません。

重要なのは、tools が request に含まれているだけで backend がエラーを返さないことです。

OK:
  tools を理解して、必要なら tool_calls を返す

OK:
  tools を使わず、通常の assistant message を返す

NG:
  tools が含まれるだけでエラーになる

例えば、No connected db のようなエラーが返る場合は、tools の存在を backend 側が別機能のトリガーとして誤判定している可能性があります。

Hermes Agent から使う場合

Hermes Agent からは、LiteLLM を OpenAI-compatible endpoint として登録します。

base_url:
  http://<DGX Spark IP>:30000/v1

api_key:
  LiteLLM API key

通常作業用です。

model:
  qwen3.6-35b-a3b-fp8-fast

高品質推論用です。

model:
  qwen3.6-35b-a3b-fp8-think

モデル名から元の LLM が分かるようにしているため、gateway_fast のような名前よりも運用時に見分けやすくなります。

Hermes Agent は通常 request で tools を送る場合があります。

これは想定動作です。

そのため、Hermes から利用する場合は、まず tools 付き request の確認 にある curl で、tools を含む request がエラーにならないことを確認しておきます。

OpenCode から使う場合

OpenCode でも同じです。

base_url:
  http://<DGX Spark IP>:30000/v1

api_key:
  LiteLLM API key

model:
  qwen3.6-35b-a3b-fp8-fast

長いコード編集や通常の Agent 作業では fast を使います。

設計判断や難しい原因分析だけ think を使います。

OpenCode も Agent 的に動作するため、tools / function calling 系の request を送る場合があります。

この構成では、SGLang 側の --tool-call-parser qwen3_coder と、LiteLLM 側の drop_params: false により、tools を含む request を落とさず upstream に渡す方針にしています。

クライアント側の使い分け

通常モデル

model:
  qwen3.6-35b-a3b-fp8

enable_thinking はクライアント依存です。

クライアントが chat_template_kwargs.enable_thinking を送れば、その値が使われます。

LiteLLM は Thinking 設定を付与しません。

fast preset model

model:
  qwen3.6-35b-a3b-fp8-fast

クライアントが enable_thinking を指定しない場合に、LiteLLM が enable_thinking=false を付与します。

使いどころです。

- OpenCode
- Hermes Agent の通常作業
- ファイル編集
- tool calling
- 長めの context
- 速度優先

Agent 作業では、tool call を何度も行うため、Thinking を常時有効にすると遅くなりやすいです。

そのため、通常は fast を使います。

think preset model

model:
  qwen3.6-35b-a3b-fp8-think

クライアントが enable_thinking を指定しない場合に、LiteLLM が enable_thinking=true を付与します。

使いどころです。

- 設計方針の比較
- 原因分析
- 難しいレビュー
- アーキテクチャ判断
- 重要な意思決定

こちらは全コンテキストを投げる用途にはしません。

短〜中コンテキストで、質の高い推論が必要なときに使います。

通常運用コマンド

起動です。

cd ~/docker/sglang-qwen36-35b-a3b-fp8
docker compose up -d

停止です。

docker compose down

再起動です。

docker compose restart

SGLang だけ再起動します。

docker compose restart sglang-qwen36-35b-a3b-fp8

LiteLLM だけ再起動します。

docker compose restart litellm-qwen-gateway

ログ確認です。

docker logs -f sglang-qwen36-35b-a3b-fp8
docker logs -f litellm-qwen-gateway

設定変更の反映

.envdocker-compose.ymllitellm_config.yaml を変更した場合は、再作成します。

docker compose down
docker compose up -d

LiteLLM の設定だけ変更した場合は、LiteLLM だけ再起動してもよいです。

docker compose restart litellm-qwen-gateway

SGLang に直接アクセスできないことを確認する

SGLang は expose のみなので、ホスト側からは 29999 に直接接続できません。

curl -i http://localhost:29999/v1/models

接続できなければ、SGLang が外部公開されていない状態です。

同じ Docker network 内では、LiteLLM から次で接続できます。

http://sglang-qwen36-35b-a3b-fp8:29999/v1

トラブルシュート

/v1/models に3モデルが出ない

LiteLLM の config を確認します。

docker logs -f litellm-qwen-gateway

litellm_config.yamlmodel_name が環境変数から解決できているか確認します。

docker compose exec litellm-qwen-gateway env | grep LITELLM_MODEL

SGLang に接続できない

LiteLLM container から SGLang の health check を確認します。

docker compose exec litellm-qwen-gateway sh -lc \
  'python - <<EOF
import urllib.request
print(urllib.request.urlopen("http://sglang-qwen36-35b-a3b-fp8:29999/health", timeout=5).read().decode())
EOF'

SGLang container が起動しているか確認します。

docker compose ps

fast で reasoning_content が出る

まず、クライアント request に次が含まれていないか確認します。

{
  "chat_template_kwargs": {
    "enable_thinking": true
  }
}

この指定がある場合、今回の検証ではクライアント指定が優先され、fast でも Thinking が有効になりました。

fast preset として使う場合は、クライアント側で chat_template_kwargs.enable_thinking を明示しないようにします。

think で reasoning_content が出ない

litellm_config.yamlqwen3.6-35b-a3b-fp8-think 側に、次が入っているか確認します。

extra_body:
  chat_template_kwargs:
    enable_thinking: true

LiteLLM を再起動します。

docker compose restart litellm-qwen-gateway

tools を送ると No connected db が返る

Hermes Agent や OpenCode は、通常の chat completion request でも tools を送る場合があります。

これは Agent クライアントとしては正常な動作です。

まず、request に tools が含まれているか確認します。

payload keys: ['model', 'messages', 'tools']

この状態で backend が No connected db を返す場合、tools の存在を backend / gateway 側が DB 接続機能のトリガーとして誤判定している可能性があります。

OpenAI-compatible API としては、少なくとも次のどちらかの挙動が必要です。

tools 対応の場合:
  tools を処理し、必要に応じて tool_calls を返す

tools 非対応の場合:
  tools を無視し、messages のみで通常の chat completion として処理する

tools が含まれるだけでエラーにする挙動は、Agent クライアントとの互換性を壊します。

対処方針は次の通りです。

1. SGLang 側では --tool-call-parser qwen3_coder を有効にする

2. LiteLLM 側では drop_params: false のままにする

3. backend / gateway 側で tools を DB 接続処理のトリガーにしない

4. tools 非対応の backend では、tools を安全に無視して通常応答する

暫定回避として client 側で tools を送らない設定にする方法もありますが、Hermes Agent や OpenCode の Agent 機能を制限することになります。

そのため、恒久対応は backend / gateway 側で tools を正しく受け付けることです。

tools 付き request が LiteLLM で落ちているか確認したい

LiteLLM 側の詳細ログを一時的に有効化します。

litellm_config.yaml の次の値を変更します。

litellm_settings:
  set_verbose: true

LiteLLM を再起動します。

docker compose restart litellm-qwen-gateway

ログを確認します。

docker logs -f litellm-qwen-gateway

確認が終わったら、ログ量を抑えるために戻します。

litellm_settings:
  set_verbose: false

再度 LiteLLM を再起動します。

docker compose restart litellm-qwen-gateway

SGLang 側の tool parser 設定を確認したい

SGLang の起動 command に次が含まれているか確認します。

- --tool-call-parser
- qwen3_coder

docker compose の実効設定を確認します。

docker compose config | grep -A 5 -B 5 tool-call-parser

SGLang を再作成します。

docker compose down
docker compose up -d

注意点

fast / think は強制制御ではない

今回の LiteLLM extra_body 構成では、fast / think は「強制」ではありません。

検証結果は次の通りです。

fast + クライアント未指定:
  Thinking 無効

fast + クライアントが enable_thinking=true を明示:
  Thinking 有効

think + クライアント未指定:
  Thinking 有効

そのため、fast / think は通常利用向けの preset model alias として扱います。

クライアント指定を無視して厳密に強制したい場合は、LiteLLM custom callback、OpenResty / Lua、または自作 rewrite proxy が必要になります。

think は全コンテキスト用にしない

qwen3.6-35b-a3b-fp8-think は、品質重視の推論用です。

長大な repository context や大量の tool result を投げる用途にはしません。

通常の Agent 作業では qwen3.6-35b-a3b-fp8-fast を使います。

Agent クライアントでは tools が送られる前提で考える

Hermes Agent や OpenCode のような Agent クライアントでは、通常リクエストでも tools が送られることがあります。

そのため、構成確認では messages だけの request ではなく、tools を含む request でも確認します。

messages のみ:
  基本的な chat completion の確認

messages + tools:
  Agent 互換性の確認

messages のみでは動くが、tools を含むと失敗する場合は、Agent 用途ではまだ不十分です。

tools は DB 接続機能のトリガーではない

OpenAI-compatible API における tools は、function calling 用の通常フィールドです。

tools が含まれていること自体は、DB 接続を要求していることを意味しません。

そのため、backend / gateway 実装では、tools の存在だけで DB 接続処理に分岐しないようにします。

非対応であれば、安全に無視して通常応答する方が、Agent クライアントとの互換性は高くなります。

まとめ

今回の続編では、前編の自作 Python gateway をやめ、LiteLLM Proxy を採用しました。

構成は次のようになります。

SGLang:
  Qwen3.6-35B-A3B-FP8 の推論だけを担当
  internal port: 29999
  external公開なし
  API key 認証なし
  Qwen3 Coder 系 tool calling parser を有効化

LiteLLM:
  OpenAI-compatible gateway
  external port: 30000
  API key 発行・認証を担当
  model alias を提供
  Agent からの tools 付き request を upstream に渡す

クライアントから見える model は次の3つです。

qwen3.6-35b-a3b-fp8
qwen3.6-35b-a3b-fp8-fast
qwen3.6-35b-a3b-fp8-think

それぞれの役割です。

qwen3.6-35b-a3b-fp8:
  enable_thinking はクライアント依存

qwen3.6-35b-a3b-fp8-fast:
  enable_thinking 未指定時に false を付与
  通常の Agent 作業向け
  tool calling や長めの context でも基本はこちらを使う

qwen3.6-35b-a3b-fp8-think:
  enable_thinking 未指定時に true を付与
  短〜中コンテキストの高品質推論向け

外部から接続する URL は LiteLLM のみです。

http://<DGX Spark IP>:30000/v1

Hermes Agent や OpenCode から使う場合は、messages だけでなく、tools を含む request でもエラーにならないことを確認します。

OpenAI-compatible API として期待する挙動:
  tools 対応なら tools / function calling を処理する
  tools 非対応なら tools を安全に無視して通常応答する

避けるべき挙動:
  tools が含まれるだけで No connected db などのエラーを返す

SGLang、Docker、Hugging Face cache、Qwen3.6-35B-A3B-FP8 の基本構築は、前編に書いてあります。

DGX Spark + Docker + SGLang + Qwen3.6-35B-A3B-FP8 環境構築

この記事では、その構成を LiteLLM Proxy ベースに変更し、passthrough / fast / think の3モデル alias で使い分け、さらに Agent クライアントの tools 付き request を受けられるようにするところを記録しました。

Discussion