このチュートリアルでは、API リファレンス、アーキテクチャノート、ランブック、ポストモーテムを格納する Alibaba Cloud Milvus ナレッジベース上に、開発トラブルシューティングアシスタントを構築します。API 名やエラーコードで検索し、ソースの引用付きで順序立てられたトラブルシューティング手順を取得します。
ソリューション概要
このチュートリアルは、Alibaba Cloud Milvus ナレッジベースを使用したインテリジェントなカスタマーサービス Q&A アプリケーションの構築 を基に構築されています。エンドツーエンドのパイプラインは同一です。コンソールでタグを定義 → タグ別にソースファイルをバッチインポート → バージョンを公開 → SDK を通じて取得 (オプションのタグフィルタリング付き) → LLM にソースの引用付きの回答を生成させる → Flask で Q&A ページを配信。そのチュートリアルのエンジニアリングコード (kb_client.py、upload.py、app.py、templates/index.html、start.sh) を再利用し、ステップ 4:再利用コードの修正 で説明する 2 つの変更を加えます。このドキュメントでは、開発トラブルシューティングシナリオのために変更する必要がある点のみを説明します。モジュールタグ、用語の完全一致のための取得パラメーター、HTML ソースファイルの処理、および高リスクコマンドと空の結果に対する制約です。
最初のバージョンでは、単一のシステムから 5〜20 個のソースファイルのみを選択し、検証後に範囲を拡大します。プロセス全体には約 20〜30 分かかり、ドキュメントの解析時間はソースファイルの数とサイズによって異なります。
前提条件
Milvus ナレッジベースが作成されており、その [Knowledge Base ID] (例:
kd-803ae9b10cc31) を記録していること。Alibaba Cloud アカウントで RAM ユーザーが作成されており、[Use Permanent AccessKey Access] が選択され、システムポリシー
AliyunMilvusFullAccessがアタッチされていること。OpenAI
chat/completionsプロトコルをサポートする LLM エンドポイントとその API キーが準備できていること。Python 3.8 以降がローカルにインストールされていること。
ソリューション概要 で参照されているチュートリアルに従って、プロジェクトディレクトリと 5 つの再利用ファイル (
kb_client.py、upload.py、app.py、templates/index.html、start.sh) がセットアップされていること。ソースファイルにシークレット、トークン、または内部ネットワークアカウントが含まれていないこと。
ステップ 1:モジュールタグの定義
ナレッジベース詳細ページの [Basic Information] で、[Tags] > [Manage] を選択し、次の 3 つのタグを追加します。各タグのフィールドタイプを string に設定します。タイプのドロップダウンはタグ名入力ボックスの右側にあり、オプションは string、int64、list、float32、bool です。
| タグ名 | 説明 | 値の例 |
| module | ソースファイルが属するシステムまたはサービス | order-service、user-service |
| docType | ドキュメントタイプ | API、Runbook、ErrorCode、Postmortem |
| version | API またはドキュメントのバージョン | v2 |
タグ名 version は、取得リクエストでナレッジベースの公開バージョンを表す version パラメーターと同じ名前ですが、両者は互いに影響しません。タグは tagFilter.field で使用され、取得バージョンはリクエストのトップレベルに配置されます。混乱が懸念される場合は、代わりにタグ名を apiVersion にしてください。
タグ管理ダイアログボックスは全体として保存されます。ダイアログボックスを開くと、タグリストが非同期に読み込まれます。新しいタグを追加して [OK] をクリックする前に、既存のすべてのタグが表示されるまで待ってください。そうしないと、保存時に「空のリスト + 新しいタグ」で全体が上書きされ、既存のタグ定義が消去されてしまう可能性があります。
このシナリオでは、module タグが最も重要な設計判断です。異なるシステムでは、まったく同じ名前の API パスを持つことがよくあり、モジュールによるフィルタリングがないと互いに干渉してしまいます。このテストでは、user-service と order-service は両方とも同じパス GET /api/v2/orders/{orderId} を持っています。このパスを検索したとき:
| 取得条件 | 結果 |
| フィルターなし | user-service のソースファイルが 1 位にランク付けされました (関連性 0.246) — 間違ったモジュールがヒットしました |
module = order-service | order-service のソースファイルが 1 位にランク付けされ、user-service のソースファイルは完全に除外されました |
ステップ 2:ソースファイルとインポートマニフェストの準備
このステップでは、ソースファイルを収集し、インポートマニフェストで各ファイルに注釈を付け、ナレッジベースがそれらをどのようにチャンキングするかを設定します。
ソースファイルを
documents/ディレクトリに配置します。Markdown、HTML、PDF、DOCX、TXT がサポートされています。ソースファイルには完全なエラーコード、API パス、バージョン番号を省略せずに含めてください — テストでは、エラーコードとフルパスの両方を正確に取得できました。documents.jsonlを作成し、各ソースファイルにそのモジュール、ドキュメントタイプ、バージョンを注釈として付けます。{"path": "documents/order-api.md", "metadata": {"module": "order-service", "docType": "API", "version": "v2"}} {"path": "documents/order-timeout-runbook.md", "metadata": {"module": "order-service", "docType": "Runbook", "version": "v2"}} {"path": "documents/order-error-codes.html", "metadata": {"module": "order-service", "docType": "ErrorCode", "version": "v2"}} {"path": "documents/order-postmortem-2026-06.md", "metadata": {"module": "order-service", "docType": "Postmortem", "version": "v2"}}API リファレンス、ランブック、エラーコードテーブル、ポストモーテムをセットとしてインポートし、
docTypeで区別します。テストでは、エラーコードについて質問したところ、4 種類のソースファイルすべてが同時にヒットし、モデルは「意味 → トラブルシューティング手順 → 過去のケース」という完全な回答を生成しました。
HTML ソースファイルの処理
HTML は直接アップロードして解析でき、<table> 内のコンテンツはインデックスに登録されます。テストでは、HTML のエラーコードテーブルにのみ存在する ORD-42901 をクエリしたところ、そのファイルが正確にヒットしました。
ただし、HTML は Markdown よりもチャンクの粒度が粗くなります (このテストでは、2 つのテーブルを含む HTML ファイルが 1 つのチャンクに解析されました)。これは HTML の解析方法に関連しています。ソースファイルの形式を選択する前に、次の 2 点を理解してください。
コードブロックは元のレイアウトを保持しません。
<pre>と<code>はブロックとして認識されますが、テキストは再帰的に抽出されてスペースで結合されるため、インデント、改行、コードフェンスはすべて失われます。大量のコードやコマンドを含むソースファイルの場合は、インポートする前に Markdown に変換してください。そうしないと、取得されたコードスニペットが直接使用できない可能性があります。テーブルは全体としてインデックス化されます。
結論:単純な説明テキストや小さなテーブルは HTML を直接使用できます。正確な忠実度、コードセグメントによる取得、または大きなテーブルの分割が必要な場合は、Markdown または構造化されたソースファイルを優先し、インポート後に [Data Management] ページでチャンクをスポットチェックしてください。<table>は、元の HTML 文字列形式のまま単一の独立したセグメントとして追加され、行ごとに分割されません。そのため、大きなテーブルは粗いチャンクを形成しやすくなります。
チャンキングポリシーの設定
トラブルシューティングの手順やコード例は、途中で分断してはいけません。ナレッジベース詳細ページの [Processing Policy] で [Create Policy] をクリックし、チャンクの粒度を調整します。最大セグメント長の単位は文字数です (デフォルトは 512)。これを 580~770 文字 (約 384~512 トークン) に設定して、「1 つの完全なトラブルシューティング手順」または「1 つのコード例」が同じチャンクに収まるようにします。文字とトークンの比率はソースファイルの言語やトークナイザーによって異なるため、文字数をベースラインとして使用し、インポート後にチャンクをスポットチェックしてください。
ステップ 3:取得パラメーターとプロンプトの設定
開発のトラブルシューティングに関するクエリは、主にエラーコード、API パス、コマンドです。これらは用語の完全一致で検索されるため、パラメーターは他のシナリオと大きく異なります。
{
"retrieval": {
"page_size": 6,
"candidate_count": 64,
"min_score": 0.05,
"semantic_weight": 0.15,
"enable_query_expansion": false,
"rerank_model_name": "",
"tag_filter": {
"relation": "and",
"conditions": [
{"field": "module", "op": "=", "value": "order-service"}
]
}
},
"scenario": {
"title": "開発ドキュメントとトラブルシューティングアシスタント",
"system_prompt": "あなたは開発ドキュメントアシスタントです。取得した資料にのみ基づいて回答してください。API 名、エラーコード、コマンド、コードはそのまま保持してください。トラブルシューティング手順を順にリストアップし、[Source N] として引用してください。書き込み操作や高リスクのコマンドが含まれる場合は、リスクレベルを明記し、手動での確認を促してください。取得した資料が空の場合は、資料が不十分であることのみを返信し、いかなる変更操作も行わないように警告してください。",
"image_enabled": false,
"sample_questions": [
"注文クエリ API の必須パラメーターは何ですか?",
"接続タイムアウトのトラブルシューティングはどのような順序で行うべきですか?",
"このエラーコードはどのドキュメントに記載されていますか?"
]
}
}以下に、このシナリオに合わせて調整したパラメーターを説明します。例の残りのフィールドは、参照元のチュートリアルから引き継がれています。
semantic_weight: 0.15:最終スコアの重み付け係数。スコアはscore ≈ (1 - semantic_weight) × keywordScore + semantic_weight × semanticScore(ランク特徴量も追加される場合があります) として計算され、min_scoreはこの最終スコアに対して事後フィルタリングを実行します。このシナリオでは 0.15 を使用して、エラーコードや API パスなどの用語の完全一致のキーワードスコアがランキングを支配するようにします。semantic_weightはmin_scoreと相互作用するため、semantic_weight と min_score の同時調整 で説明されているように、両方を一緒に調整してください。enable_query_expansion: false:クエリ拡張は無効になっており、エラーコードや API 名が書き換えられないようにします。テストでは、これを有効にすると、クエリORD-50021のkeywordScoreが 0.283 から 0.226 に低下し、クエリORD-42901でリコールされた結果の数が 2 から 1 に減少しました。用語の完全一致の取得では無効にしておいてください。tag_filterがmoduleを固定:これにより、異なるシステムの同名 API が互いに干渉するのを防ぎます。効果については ステップ 1:モジュールタグの定義をご参照ください。1 つのエントリポイントで複数のモジュールをカバーする必要がある場合は、{"field": "module", "op": "in", "value": ["order-service", "user-service"]}を使用します。rerank_model_nameが空の場合は、再ランキングが有効になっていないことを意味します。エラーコードの完全一致はキーワードスコアに依存するため、再ランキングによるメリットは限定的です。opは=、in、およびnot inのみをサポートします。テストでは、≠、>、≥、<、≤、empty、not empty、start with、end withはサイレントに無視され、すべてのデータが返されます (エラーは報告されません)。containsおよびnot containsは、部分文字列の一致を意味するものではなく、単にin/not inのエイリアスです。文字列の断片を渡すと、結果は 0 件となります。eq、==、likeなどのエイリアスは400 Unsupported tag filter operatorを返し、すべてのクエリが失敗します。フィルター条件を設定した後、結果の総数をフィルター適用前の結果と比較し、フィルターが実際に有効になっていることを確認してください。
semantic_weight と min_score の同時調整
semantic_weight は最終的な重み付けスコアのみを変更し、取得結果の scoreDetails にある 2 つのサブスコア (keywordScore と semanticScore) は変更しません。重み付けスコアは、おおよそ次のようになります。
score ≈ semantic_weight × semanticScore + (1 - semantic_weight) × keywordScore調整する前に、次の 2 点に注意してください。
semantic_weightを下げても、合計スコアが必ずしも下がるとは限りません。合計スコアが下がるのは、結果バッチのsemanticScoreがそのkeywordScoreよりも高い場合のみです。これが、このドキュメントでmin_scoreも 0.05 に下げている理由です。2 つのパラメーターは、scoreDetailsを確認して一緒に調整する必要があります。
開発資料の場合、semantic_weightを 0 に設定すると、現在の実装ではmin_scoreのしきい値が適用されなくなります。「純粋なキーワード取得」という意味で 0 を使用しないでください。keywordScoreは通常semanticScoreよりもはるかに低いため (テストでは、それぞれ約 0.16~0.28 と 0.64~0.78)、この分布下ではsemantic_weightを下げると合計スコアが低い方に引っ張られます。これは普遍的なルールではありません。結果バッチのsemanticScoreがそのkeywordScoreよりも高い場合にのみ、重みを下げると合計スコアが下がり、そうでない場合は上がります。min_scoreを同時に下げないと、セマンティックにヒットした結果でさえフィルタリングされてしまいます。
| クエリ | semantic_weight=0.15 | semantic_weight=0.7 |
/api/v2/orders/{orderId} | 8 件の結果、トップスコア 0.269 | 8 件の結果、トップスコア 0.602 |
kubectl -n order rollout undo deploy/order-api | 4 件の結果 | 8 件の結果 |
| 自然言語クエリ「なぜ最近、注文 API が大幅に遅くなったのですか?」 | 3 件の結果 | 8 件の結果 |
semantic_weight=0.15 を使用する場合は、min_score を約 0.05 に設定します (上記の例はこのように設定されています)。min_score を 0.15 以上に設定したままにすると、コマンドベースおよび自然言語クエリのリコール率が半分以下に低下します。調整する際は、1 つのパラメーターを固定し、scoreDetails を通じて 2 つのサブスコアを観察してから決定してください。
高リスクコマンドと空の結果の制約
トラブルシューティングアシスタントは実行可能なコマンドを直接出力するため、2 つの点を制約する必要があります。
高リスクコマンド。 ソースファイル内のテーブルでリスクレベルと確認要件をマークしておくと、モデルはそれらを忠実に伝えます。テストでは、ランブックで「high risk, two-person confirmation required」とマークされた再起動コマンドについて質問したところ、モデルはコマンドに加えて「the risk level is high」、「two-person confirmation is required」、「do not skip troubleshooting and run the restart directly」という明確な記述を返しました。
空の取得結果。 ステップ 4:再利用コードの修正 で説明されているように、コードでこのケースをインターセプトする必要があります。
ステップ 4:再利用コードの修正
再利用するプロジェクトは、アシスタントを検証する前に 2 つの修正が必要です。app.py での空の結果に対するフォールバックと、kb_client.py での戻りコードの修正です。
app.py における空の取得結果のインターセプト
再利用する app.py では、取得結果が空の場合でも llm_answer() は LLM を呼び出します。その時点ではコンテキストは空の文字列であり、モデルは完全に自身の知識から回答します。テストで、ナレッジベースがカバーしていない「Redis クラスターのスプリットブレインから回復する方法は?」と質問したところ、モデルはパラメーター付きの完全な操作計画を出力し、警告を一切表示しませんでした。トラブルシューティングのシナリオでは、ユーザーがコマンドをコピーして本番環境で直接実行する可能性があるため、これはコードレベルでインターセプトする必要があります。
def llm_answer(question: str, results: list[dict[str, Any]]) -> str:
if not LLM.get("enabled", True):
return "LLM が有効になっていません。以下の取得結果を確認してください。"
if not results:
return "ナレッジベースから関連ドキュメントが取得されなかったため、トラブルシューティング手順を提供できません。これに基づいていかなる変更操作も行わないでください。モジュールの所有者に連絡することを推奨します。"
...これをプロンプトだけで制約するのは信頼できません — 「資料が質問をカバーしていない場合は明示的に通知する」と記述しても、モデルは回答してしまいます。
ステップ 5:アップロード、公開、検証の最後の受け入れ基準は、この修正をカバーしています。
kb_client.py のリターンコードチェックの修正
再利用する kb_client.py では、_check() は getattr(body, "code", 0) != 0 を評価します。成功したレスポンスの code は None であるため、成功した取得は「Failed: None」と報告されます。チェックを getattr(body, "code", None) not in (None, 0, "0") に変更します。修正後、成功した取得は「Failed: None」と報告されなくなります。
ステップ 5:アップロード、公開、検証
ソースファイルをアップロードします。
MetaFieldsはバッチ全体に適用されます。スクリプトはまずタグでグループ化し、次にバッチで送信します。python upload.py --manifest documents.jsonlコンソールの [Data Management] ページで、ソースファイルのステータスが [Completed] であることを確認します。アップロード API からの成功レスポンスは、非同期解析が送信されたことのみを意味します。
[Version Management] ページで、[Publish Version] をクリックします。ウィザードを完了した後、新しいバージョンのステータスが [Published] であることを確認します。
重要同時に存在できる公開済みバージョンは最大 3 つです。上限に達すると、[Publish Version] ボタンはグレーアウトされますが、ページには「There are currently N pending changes to publish」と表示され続けます。まず、バージョン履歴から不要になった古いバージョンを削除してください。開発ドキュメントは頻繁に変更されるため (リリースごとに API リファレンスやランブックが更新される可能性があります)、「現在のバージョン + 最新の履歴バージョン」のみを保持するようにしてください。
サービスを起動します。
python app.pyテスト用の質問を送信します。
curl -sS http://127.0.0.1:7860/api/ask \ -H 'Content-Type: application/json' \ -d '{"question":"接続タイムアウトのトラブルシューティングはどのような順序で行うべきですか?"}'次の 5 つの受け入れ基準に対して検証します。
ページが正常に開き、質問を送信すると、回答と取得ソースが一緒に返されます。
回答内の各
[Source N]が、下のソースセクションにある対応するソースファイルチャンクと一致します。完全なエラーコードで質問すると、そのエラーコードが出現するすべてのソースファイルがリストアップされます。テストでは、
ORD-50021をクエリすると、エラーコードテーブル、API リファレンス、ランブック、ポストモーテムの 4 つのソースファイルが、それぞれの場所とともに正しくリストアップされました。リスクの高い操作について質問すると、回答にリスクレベルと手動での確認要件が含まれます。
ナレッジベースがカバーしていない内容について質問すると、「materials are insufficient」という応答と、いかなる変更操作も行わないようにという警告が返され、捏造されたコマンドは表示されません。このケースを実際にテストしてください。
開発シナリオに関する注意事項
モジュールごとのエントリポイント —
tag_filterがmoduleを固定すると、エントリポイントはそのモジュールに関する質問にのみ回答します。サンプル質問も同じモジュール内に留めてください。完全一致用語の完全性 — ソースファイルには、完全なエラーコード、API パス、コマンド、バージョン番号を保持してください。これらの完全一致用語は、このシナリオにおける主要な取得エントリポイントです。
リスクのマーキング — ソースファイルにリスクレベルと確認要件をマークしてください。どのコマンドが危険かをモデルに判断させないでください。
アップロードにシークレットを含めない — シークレット、トークン、内部ネットワークアカウント、または本番データベースの接続文字列をアップロードしないでください。チャンクのコンテンツはコンテキストとして LLM に送信されます。
非推奨 API の説明 — 非推奨になった API 操作の説明は保持してください。テストでは、API リファレンスに「the v1 API is deprecated and its parameter names are incompatible」と記載した後、新しい API のパラメーターに関する質問に回答する際に、モデルは古いパラメーターを混在させませんでした。
取得結果の
tagsフィールドは、書き込まれたモジュールとバージョンをエコーバックしません。これはAddDocumentsの MetaFields とは異なるフィールドであり、その返却を有効にするパラメーターはありません。値が空でもアップロードが失敗したわけではありません。 回答にモジュールとバージョンを注釈付けする必要がある場合は、取得結果をdocumentIdでdocuments.jsonlと結合し、その情報をコンテキストに追加します。本番環境へのデプロイ — オンラインデプロイに Flask 開発サーバーを使い続けないでください。本番用の WSGI サーバーに切り替え、シークレット管理、認証、監査、レート制限を追加してください。
よくある質問
| 現象 | 原因と解決策 |
質問が 500 を返し、ログに 400 Unsupported tag filter operator と表示される | op が eq、==、like などのエイリアスを使用しています。代わりに =、in、または not in を使用してください。 |
| タグフィルターを追加したが、結果の数がフィルターなしの場合とまったく同じ | 効果のない演算子 (>、≥、≠、empty など) が使用されました。=、in、not in のみが有効です。 |
| 他のシステムの同名 API が取得される | module フィルターが設定されていないか、フィルターの値がアップロード時に書き込まれた値と一致していません。 |
| 取得結果が想定よりもはるかに少ない | semantic_weight が低いと、全体のスコアが押し下げられ、min_score と組み合わせることで、より多くの結果がフィルタリングされます。semantic_weight と min_score の同時調整 で説明されているように min_score を下げてください。 |
| エラーコードの取得が不正確 | enable_query_expansion が false であることを確認してください。有効にすると、エラーコードが書き換えられる可能性があります。 |
| タグフィルタリングが常に 0 件の結果を返す | タグ名のスペルがアップロード時に書き込まれたものと一致していません (API はエラーを報告せず、単に 0 件の結果を返します)。ナレッジベース詳細ページで [Tags] > [Manage] を選択して確認してください。 |
| ナレッジベースでカバーされていない質問に対して、実行可能に見えるコマンドが返された | 取得結果が空の場合でも llm_answer() が LLM を呼び出しています。ステップ 4:再利用コードの修正 で説明されているように、空の結果に対するフォールバックを追加してください。 |
| HTML ソースファイル内の詳細が取得できない | HTML はチャンクの粒度が粗いためです。最大セグメント長を短くしてください。大量のコードを含む HTML は、先に Markdown に変換してください。 |
| 取得時に「Failed: None」と報告される | 再利用したコードの _check() 内のチェックが正しくありません (getattr(body, "code", 0) != 0)。成功したレスポンスの code は None です。ステップ 4:再利用コードの修正 の修正を適用してください。 |
取得時に 404 Knowledge base version ... does not exist が返される | まだバージョンが公開されていないか、設定にハードコーディングされたバージョン番号が削除されています。代わりに LATEST_PUBLISHED を使用してください。 |
次のステップ
モジュールごとにカバレッジを拡大する:各
moduleの値に対して独立したエントリポイントを作成し、サンプル質問を同じモジュール内に留めます。本番環境への準備:Flask 開発サーバーから本番用の WSGI サーバーに切り替え、シークレット管理、認証、監査、レート制限を追加します。
ドキュメントの変更に合わせてバージョンを管理する:API リファレンスやランブックが更新されたときに新しいバージョンを公開し、現在のバージョンと最新の履歴バージョンのみを保持します。