観測された事象と影響範囲
最高の生産性を求めて、ローカルで動くOllamaやOpenRouterをGitHub Copilotに連携させたはずが…。
思考の波に乗ったまさにその瞬間、エディタは無情にも "Response Contained No Choices" を突きつけます。
一体何が起きたのか。
Ollamaのログを睨んでも、OpenRouterのダッシュボードを見ても、原因は特定できない。
VS Code側の問題なのか、モデルの応答形式か、それとも見えないネットワークの気まぐれか。
貴重な開発時間が、原因不明のトラブルシューティングに溶けていく虚しさ。
自らの手で開発環境をコントロールしているつもりが、結局は巨大なブラックボックスの掌の上で踊らされているだけなのかもしれない。
この不確実性は、もはや無視できない技術的負債です。
対象のイシュー詳細
GitHub CopilotでBYOK(Ollama, OpenRouter.ai)利用時に "Response Contained No Choices" が発生する
Original: GitHub Copilot "Response Contained No Choices" Using BYOK - Ollama, OpenRouter.ai
自前のLLM環境利用時、モデルからの応答をVS Code側が正常に処理できずエラーになるバグ。
観測されたエラー構造
{
"id": "gen-abc123def456",
"model": "qwen/qwen-3.6-27a3b-instruct",
"object": "chat.completion",
"created": 1720354188,
"choices": [],
"usage": {
"prompt_tokens": 1024,
"completion_tokens": 0,
"total_tokens": 1024
}
}
原因の技術的深掘り
この問題の根源は、VS CodeのCopilotクライアントが内部で期待するAPIレスポンスのスキーマと、OllamaやOpenRouter経由で返される多様なLLMモデルの応答との間に存在する、僅かな「不整合」にあります。
具体的には、CopilotクライアントはレスポンスJSON内に、必ず`choices`というキーを持つ配列が存在し、その配列内に少なくとも1つ以上の補完候補オブジェクトが含まれることを強く期待しています。
しかし、特定の条件下、例えばモデルが安全上の理由で応答をフィルタリングした場合や、適切な補完を生成できなかった場合、APIエンドポイントは`choices`配列を空のまま返すことがあります。
これはAPI仕様上は正当な応答です。
問題は、VS Code側がこの「正常だが空の応答」をエラーケースとして適切にハンドリングできていない点にあります。
結果として、より具体的な原因を示すことなく "Response Contained No Choices" という汎用的なエラーをスローし、開発者を混乱の渦に突き落とすのです。
これは単なるモデルやAPIの問題ではありません。
多様なエンドポイントとの連携を前提としたクライアント側の堅牢性(resilience)に構造的な課題があることを示唆しています。
技術検証と解決策(ワークアラウンド)
ローカル環境やサードパーティAPIに依存した開発は、常にこのような予期せぬ「境界」の問題に悩まされます。
エディタのアップデート、拡張機能の変更、APIの仕様変更。その一つ一つが、あなたの生産性を人質に取るのです。
場当たり的なパッチで対応するのではなく、開発の基盤そのものを見直す時期なのかもしれません。
真の安定性とコントロールを求めるなら、アプリケーションの実行環境、そしてそれを支えるインフラ全体を、自らの手で構築・管理することが最も確実な道です。
独自のドメインでサービスを公開し、スケーラブルなサーバーを運用する。
それは、外部のブラックボックスから開発の主導権を奪い返し、ビジネスロジックという本質的な価値創造に100%集中できる環境を手に入れることに他なりません。
$ ssh username@your-new-server.com
$ sudo apt update && sudo apt upgrade -y
$ # ここから、誰にも邪魔されないあなたのインフラ構築が始まる
// A conceptual, robust API response handler
async function getCompletionSafely(request: CopilotApiRequest): Promise {
try {
const response = await fetchFromLanguageModel(request);
// Defensively access the response to prevent crashes
const firstChoice = response?.choices?.[0]?.text ?? null;
if (firstChoice) {
return firstChoice;
}
// Log the unexpected (but valid) response without throwing an error
console.warn('Completion response was valid but contained no choices.', response);
return null;
} catch (error) {
console.error('An unexpected error occurred while fetching completion:', error);
return null;
}
}