Alibaba Cloud Milvus ナレッジベースは、各部門に散在するポリシー、プロセス、SOP を統一された社内 Q&A エントリポイントに集約します。従業員は自然言語で質問し、プロセスのステップと出典の引用を含む回答を得ることができます。また、部門タグを使用することで、検索範囲を特定のスコープに限定できます。
ソリューション概要
エンドツーエンドのパイプラインは、「Alibaba Cloud Milvus ナレッジベースを利用したインテリジェントなカスタマーサービス Q&A アプリケーションの構築」と同じです。コンソールでタグを定義 → タグごとにドキュメントをバッチインポート → バージョンを公開 → SDK を介してオプションのタグフィルタリングで検索 → 大規模言語モデル (LLM) が出典の引用付きで回答を生成 → Flask が Q&A ページを提供、という流れになります。そのドキュメントのエンジニアリングコード (kb_client.py、upload.py、app.py、templates/index.html、start.sh) を直接再利用してください。このトピックでは、ポリシーシナリオ向けに変更が必要な部分、すなわち部門タグスキーマ、検索パラメーター、ポリシーアシスタントのプロンプト、ポリシーのバージョン管理のみを説明します。
最初のバージョンでは、1 つの部門から 5~20 個のドキュメントのみを選択し、検索品質とプロンプトを検証した後に拡張してください。全プロセスには約 20~30 分かかります。
前提条件
ナレッジベースを作成し、ナレッジベース ID (例:
kd-803ae9b10cc31) を記録しておきます。ご利用の Alibaba Cloud アカウントで RAM ユーザーを作成し、[永続的な AccessKey ペアを使用] を選択し、システムポリシー
AliyunMilvusFullAccessをアタッチします。OpenAI の
chat/completionsプロトコルをサポートする LLM エンドポイントとその API キー。ローカルに Python 3.9 以降がインストールされており、「ソリューション概要」で参照されているチュートリアルに従ってプロジェクトディレクトリがセットアップされていること。
ステップ 1:部門とポリシーのタグ定義
ナレッジベース詳細ページの [基本情報] で、[タグ] > [管理] を選択し、以下の 3 つのタグを追加します。すべてタイプは string です。
| タグ名 | 説明 | 値の例 |
| department | ポリシーを所有する部門 | Finance, Administration, IT |
| docType | ドキュメントタイプ | Policy, Process |
| effectiveDate | 発効日 | 2026-01-01 |
タグ値には任意のテキストが使用できるため、部門名やドキュメントタイプ名を直接使用できます。
タグ管理ダイアログボックスは全体として保存されます。ダイアログボックスを開くと、タグリストが非同期でロードされます。新しいタグを追加して [完了] をクリックする前に、既存のすべてのタグが表示されるまで待ってください。そうしないと、ダイアログボックスが空のリストと新しいタグで全体を上書きし、既存のタグ定義が削除されてしまう可能性があります。
すでにデータに書き込まれているタグ値は失われません。タグを再追加した後、詳細ページでタグ数を確認してください。
タグ定義は、主に使用可能な値を標準化するために行います。定義されていないタグ名でもデータと共に書き込み、フィルタリングに使用できますが、チームでの共同作業やメンテナンスを容易にするために、インポート前に定義しておくことを推奨します。
ステップ 2:ポリシードキュメントとインポートマニフェストの整理
ドキュメントを部門ごとに整理し、
documents/ディレクトリに配置します。回答のソースセクションで各エントリを簡単に識別できるように、各ファイル名にポリシー名とバージョンまたは発効日を含めます。documents.jsonlを作成し、各ドキュメントに部門、タイプ、発効日をアノテーションします。{"path": "documents/finance-travel.md", "metadata": {"department": "Finance", "docType": "Policy", "effectiveDate": "2026-01-01"}} {"path": "documents/finance-reimbursement.md", "metadata": {"department": "Finance", "docType": "Process", "effectiveDate": "2026-02-01"}} {"path": "documents/hr-leave.md", "metadata": {"department": "Administration", "docType": "Policy", "effectiveDate": "2026-01-01"}} {"path": "documents/it-troubleshoot.md", "metadata": {"department": "IT", "docType": "Process", "effectiveDate": "2026-03-01"}}各ポリシードキュメントの本文の冒頭に発効日とポリシーの所有者を記述し、古いバージョンには「バージョン X により廃止」とマークします。デフォルトでは、LLM は検索されたチャンクの本文しか参照しません。
effectiveDateタグは自動的にプロンプトに追加されず、ステップ 4 の変更を適用した後にのみ含まれます。チャンク化の粒度を調整します。ポリシードキュメントは通常、短いエントリです。ナレッジベース詳細ページの [処理ポリシー] (ナレッジベースのドキュメント処理とチャンク化の設定) で、[ポリシーの作成] をクリックします。チャンクの最大長の単位は文字 (デフォルトは 512) です。各プロセスのステップが分断されないように、580~770 文字 (約 384~512 トークン) に設定します。
ステップ 3:検索パラメーターとプロンプトの設定
config.json で、ポリシーシナリオに合わせて retrieval と scenario を調整します。これら 2 つのセクションのみを変更し、ファイルの残りの部分は変更しないでください。
{
"retrieval": {
"page_size": 6,
"candidate_count": 48,
"min_score": 0.35,
"semantic_weight": 0.6,
"enable_query_expansion": true,
"rerank_model_name": "",
"tag_filter": {
"relation": "and",
"conditions": []
}
},
"scenario": {
"title": "Enterprise policy and process Q&A",
"system_prompt": "You are an internal policy assistant. Answer only based on the retrieved published policies. Organize processes into steps, state the applicable conditions and required materials, and cite sources as [Source N]. When the materials conflict or are insufficient, clearly tell the user to contact the policy owner.",
"image_enabled": false,
"sample_questions": [
"What materials are required for travel expense reimbursement?",
"Who approves leave requests longer than three days?",
"What is the process for reporting an issue when my computer cannot connect to the network?"
]
}
}例にある他のパラメーター (page_size、candidate_count、enable_query_expansion、rerank_model_name) は、参照元のチュートリアルの値を維持します。min_score、semantic_weight、tag_filter パラメーターは、シナリオ固有の選択が必要です。
min_score— コーパス全体で普遍的に推奨される値はありません。実際の質問を使って、ご自身のコーパスに対してしきい値を調整してください。min_scoreを 0 に設定し、結果のバッチを取得します。結果の関連性を手動でラベリングします。
再現率と偽陽性のディストリビューションに基づいてしきい値を選択します。
このようなキャリブレーションデータがない場合は、このトピックのコーパスで測定された値である 0.35 から始めてください。そのコーパスでは、0.2 の場合、質問とは全く無関係なポリシー (スコア 0.36~0.43) が LLM のコンテキストに入力され、コストと誤答のリスクが増加しました。0.35 にすると、これらの結果は除外されました。コーパスを変更したり、再ランキングモデルを切り替えたり、semantic_weightを調整したりした場合は、常に再調整してください。
semantic_weight=0.6— ポリシーに関する質問では、固有名詞と口語表現が混在することが多いため、セマンティックマッチングとキーワードマッチングのバランスを取ります。このパラメーターは最終スコアの重み付け係数で、
score ≈ (1-semantic_weight) × keywordScore + semantic_weight × semanticScoreとして計算されます (ランク特徴量も追加される場合があります)。min_scoreは、この計算後の最終スコアでフィルタリングします。再ランキングなしの場合、
semanticScoreはベクトル類似度です。再ランキングを有効にすると、再ランキングモデルのスコアになります。両者はスケールが異なるため、重みを変更したり、再ランキングを切り替えたりした後は、scoreDetailsと一緒にしきい値を再調整してください。重みを下げても必ずしも合計スコアが下がるわけではありません。スコアが下がるのは、バッチに対して semanticScore が keywordScore よりも高い場合のみです。tag_filter— デフォルトではconditionsを空のままにして、すべての部門から検索します。エントリポイントを単一の部門に限定したい場合にのみ、フィルター条件を設定します。「ステップ 5:検索範囲の特定部門への限定」で、設定方法と知っておくべき演算子の動作について説明します。
ステップ 4:回答への部門と発効日の追加
ポリシーに関する Q&A では、どのバージョンが有効で、どの部門に適用されるかを判断する必要があるため、タグ値を LLM のコンテキストに含める必要があります。検索結果の tags フィールドは、書き込んだタグ値を返しません。これは、サービスの内部的なチャンクタグ拡張機能に由来するものであり、AddDocuments によって書き込まれた MetaFields とは異なるフィールドです。その返却を有効にするリクエストパラメーターはないため、値が空であってもアップロードの失敗やタグの損失を意味するわけではありません。
クライアント側でマッピングを維持します。ローカルのインポートマニフェストをファイル名でインデックス化し、各検索結果をその documentName でルックアップし、タグ値を LLM のコンテキストに追加します。
このルックアップは、完全な名前一致に依存します。検索によって返された documentName が documents.jsonl のファイル名と一致することを確認してください。一致しない場合、ソースエントリには警告なしにタグのラベルが付与されません。
app.py の app = Flask(__name__) の後に、マッピングを追加します。
META_BY_NAME: dict[str, dict[str, Any]] = {}
_manifest = Path("documents.jsonl")
if _manifest.is_file():
for _line in _manifest.read_text(encoding="utf-8").splitlines():
if _line.strip():
_entry = json.loads(_line)
META_BY_NAME[Path(str(_entry["path"])).name] = _entry.get("metadata") or {}次に、llm_answer() を変更して、コンテキストを構築する際にタグ値を含め、質問に現在の日付を提供するようにします。
def llm_answer(question: str, results: list[dict[str, Any]]) -> str:
if not LLM.get("enabled", True):
return "The LLM is disabled. Review the retrieval results below."
if not results:
return "The current policy materials contain no relevant provisions. Contact the corresponding policy owner for confirmation."
blocks = []
for index, item in enumerate(results, 1):
title = field(item, "documentName", "DocumentName")
meta = META_BY_NAME.get(title) or {}
label = ", ".join(f"{key}={value}" for key, value in meta.items())
header = f"[Source {index}] {title}" + (f" ({label})" if label else "")
blocks.append(f"{header}\n{field(item, 'content', 'Content')}")
context = "\n\n".join(blocks)
today = datetime.date.today().isoformat()
url = str(LLM["base_url"]).rstrip("/") + "/chat/completions"
response = requests.post(
url,
headers={"Authorization": f"Bearer {LLM['api_key']}"},
json={
"model": LLM["model"],
"temperature": 0.1,
"messages": [
{"role": "system", "content": SCENARIO.get("system_prompt", "Answer only based on the provided materials.")},
{"role": "user", "content": f"Current date: {today}\nQuestion: {question}\n\nRetrieved materials:\n{context}"},
],
},
timeout=90,
)
response.raise_for_status()
return response.json()["choices"][0]["message"]["content"].strip()ファイルのヘッダーに import datetime を追加します。
現在の日付を提供するステップは絶対に省略しないでください。LLM は今日の日付を知りません。effectiveDate のみを提供した場合、モデルは独自に現在の日付を想定し、逆の結論に達する可能性があります。例えば、すでに有効な新バージョンを「まだ有効ではない」と判断し、廃止された古い基準を引用することがあります。タグ値と現在の日付の両方が提供された場合にのみ、モデルは現在のバージョンを正しく選択し、古いバージョンが廃止されたことを説明できます。
ステップ 5:検索範囲の特定部門への限定
ある部門専用のエントリポイントを提供するには、対応するフィルター条件を設定します。
"tag_filter": {
"relation": "and",
"conditions": [
{"field": "department", "op": "=", "value": "Finance"}
]
}複数の条件を組み合わせることもできます。例えば、Finance 部門のプロセスドキュメントのみを検索する場合:
"conditions": [
{"field": "department", "op": "=", "value": "Finance"},
{"field": "docType", "op": "=", "value": "Process"}
]タグフィルター演算子の注意点
タグフィルターで実際に効果があるのは =、in、not in のみです。フィルターを設定する前に、以下の動作を確認してください。
| 設定 | 実際の動作 |
op が、400 Unsupported tag filter operator エラーの「サポートされている演算子」リストにある演算子を含む、他の任意の演算子である場合 | テストの結果、この条件は警告なしに無視され、フィルターが適用されていない全データが返されることが確認されています。 |
op が contains または not contains | これらは集合演算 in および not in のエイリアスに過ぎず、文字列包含ではありません。文字列のフラグメントを渡すと 0 件の結果が返されます。 |
field が定義されていないタグ名である場合 | API はエラーを返さず、0 件の結果のみを返します。 |
op は eq 、 == 、 equal 、または like | API は 400 Unsupported tag filter operator を返します。 |
正しい実践方法:
数値または日付の範囲でフィルタリングするには、
inを使用して値を列挙します。タグが空かどうかを確認するには、
= ""を使用します。フィルター条件を設定した後、フィルターの有無で結果の総数を比較し、フィルターが有効であることを確認します。
部門フィルターが設定されると、このエントリポイントはその部門の質問にしか回答できません。sample_questionsをそれに応じて調整してください。そうしないと、他の部門に関する質問は「関連する規定はありません」としか返されません。
エントリポイントを別の部門に切り替えるには、同時に 3 つの場所を変更します。
documents.jsonlのメタデータ。これはアップロード時に書き込まれるタグと、ステップ 4 で追加される回答ラベルのソースです。config.jsonのtag_filter。これはエントリポイントの検索範囲を定義します。サンプル問題は、新しい範囲に一致するように調整されています。
ステップ 6:アップロード、公開、検証
ドキュメントのアップロード
MetaFields はバッチ全体に適用されます。スクリプトはまずドキュメントをタグでグループ化し、バッチで送信します。
python upload.py --manifest documents.jsonl同じ名前のファイルを重複してアップロードすると失敗します。doc_name_dedup=True の場合、バッチ内のすべてのファイルが名前で重複排除されると、API は 400 No OSS document can be registered. を返します。ポリシーを更新する際は、ファイル名にバージョン番号を含めるか、先にコンソールで古いデータを削除してください。
処理結果の確認
コンソールの [データ管理] ページで、ドキュメントのステータスが [処理完了] であり、タグ列に書き込まれたタグ (例:docType=Process, department=IT +1) が表示されていることを確認します。
バージョンの公開
[バージョン管理] ページで、[バージョンの公開] をクリックします。3 ステップのウィザードを完了した後、新しいバージョンのステータスが [公開済み] になっていることを確認します。
ポリシーを更新した後は、バージョンを再公開する必要があります。アプリケーションは LATEST_PUBLISHED を使用している場合にのみ、新しいコンテンツを取得します。
バージョンクォータ:デフォルトでは、同時に最大 3 つの公開済みバージョンが存在できます。この制限はテナントごとにカウントされ、インスタンスの CU 仕様を上げても増加しません。制限に達すると、[バージョンの公開] ボタンがグレーアウトしますが、ページにはまだ公開待ちの変更が N 件あると表示されます。ポリシーナレッジベースは頻繁に更新されます。現在の有効なバージョンと最新の履歴バージョンのみを保持し、新しいバージョンを公開する前に古いバージョンをクリーンアップしてください。この制限はサーバー側で調整できますが、現在ユーザー向けのセルフサービスのクォータ申請エントリはありません。評価のためにチケットを起票してください。
バージョンの削除は不可逆であり、削除されたバージョンは直ちに検索できなくなります。バックエンドは非同期でデータをクリーンアップします。削除する前に、そのバージョン番号にロックされているアプリケーションがないことを確認し、まだ使用しているアプリケーションを新しいバージョンに切り替えてください。
サービスの開始と検証
python app.pyテスト質問を送信します。
curl -sS http://127.0.0.1:7860/api/ask \
-H 'Content-Type: application/json' \
-d '{"question":"What materials are required for travel expense reimbursement?"}'以下の基準を満たしていることを確認します。
ページが正常に表示され、質問を送信すると、回答と取得ソースの両方が返されます。
回答内の各
[Source N]に、下のソースセクションに対応するポリシーチャンクがあること。[Source N]のヘッダーに、department=、docType=、effectiveDate=などのタグのラベルが付与されていること。ラベルがない場合は、検索によって返されたdocumentNameがdocuments.jsonlのファイル名と一致しているか確認してください。ポリシーでカバーされていない内容について質問した場合、回答には関連する規定がないことが明確に示され、ポリシーの所有者に連絡するよう指示されること。
ポリシーが更新され、バージョンが再公開された後、ページが新しいバージョンのコンテンツを検索すること。これには、アプリケーションが
LATEST_PUBLISHEDを使用している必要があります。
ポリシーシナリオにおける考慮事項
古いポリシーバージョン — トレーサビリティのために履歴バージョンを保持する場合は、本文に「バージョン X により廃止」とマークし、
effectiveDateでバージョンを区別します。トレーサビリティが不要な場合は、[データ管理] ページで古いバージョンを削除して再公開し、モデルがバージョン間で揺れ動かないようにします。手動フォールバック — 重要なポリシーに関する Q&A では、手動のフォールバックエントリポイントを維持し、従業員には公式に公開されているポリシードキュメントに従うよう指示します。
よくある質問
以下の表は、一般的な現象とその原因および解決策をまとめたものです。
| 現象 | 原因と解決策 |
質問をすると 500 が返され、ログに 400 Unsupported tag filter operator | op が eq、==、like などのエイリアスを使用しています。=、in、または not in を代わりに使用してください。 |
| タグフィルターを追加した後、結果の数がフィルターなしの場合と全く同じ | >、≥、≠、empty など、効果のない演算子を使用しています。効果があるのは =、in、not in のみです。 |
| 部門フィルターを設定した後、ほとんどの質問が「関連する規定はありません」と回答される | エントリポイントが単一の部門に限定されています。質問のスコープが tag_filter と一致していることを確認するか、conditions を空のままにしてください。 |
| タグでフィルタリングすると常に 0 件の結果が返される | タグ名のスペルが書き込まれたものと一致していません。API はエラーを返さず、0 件の結果のみを返します。ナレッジベース詳細ページの [タグ] > [管理] でタグ名を確認してください。 |
| 回答に新旧のポリシーバージョンが混在している | 検索されたチャンクにバージョン情報がありません。ステップ 4 に従ってコンテキストにタグ値を含め、本文で古いバージョンを廃止済みとしてマークしてください。 |
アップロード時に 400 No OSS document can be registered. | バッチ内のすべてのファイルが名前で重複排除されました。ファイル名を変更するか、先に古いデータを削除してください。 |
検索時に 404 Knowledge base version ... does not exist | まだバージョンが公開されていないか、knowledge_base_version が実際のバージョン番号と一致していません。 |
| [バージョンの公開] ボタンがグレーアウトしているが、ページには公開待ちの変更が表示されている | 公開済みバージョンの数が上限の 3 に達しています。ボタンにカーソルを合わせるとヒントが表示されます。バージョン履歴で古いバージョンを削除してから公開してください。 |
回答にタグを表示したいが、検索結果の tags フィールドが空である | 検索結果はタグ値をバックフィルしません。ステップ 4 に従って documents.jsonl からルックアップしてください。 |
| タグを追加した後、元のタグ定義が消えてしまった | タグ管理ダイアログボックスは全体として保存されます。ダイアログボックスを再度開き、リストの読み込みが完了するまで待ってから、不足しているタグ定義を再追加してください。すでにデータに書き込まれているタグ値には影響ありません。 |