すべてのプロダクト
Search
ドキュメントセンター

Vector Retrieval Service for Milvus:Alibaba Cloud Milvus ナレッジベースを使用した法律事例検索アプリケーションの構築

最終更新日:Aug 28, 2026

Alibaba Cloud Milvus ナレッジベースは、公開されている法令、判例、企業のコンプライアンスポリシーを、検索可能な法律資料アシスタントに変えることができます。事例の詳細に基づいて類似事例を検索し、司法見解を要約し、原典を表示します。

ソリューション

エンドツーエンドのパイプラインは、「Alibaba Cloud Milvus ナレッジベースを使用したインテリジェントなカスタマーサービス Q&A アプリケーションの構築」のパイプラインと同一で、コンソールでのタグ定義 → タグごとのソースドキュメントのバッチインポート → バージョンの公開 → オプションのタグフィルタリングによる SDK 検索 → LLM による出典付きの回答生成 → Flask による Q&A ページの配信、という流れになります。そのチュートリアルのエンジニアリングコードである kb_client.py、upload.py、app.py、templates/index.html、および start.sh を直接再利用します。このトピックでは、法律関連のシナリオ向けに変更が必要な部分、すなわちケースタグ (数値タグタイプを含む)、検索パラメーター、プロンプトの制約、および追加が必要な空の結果の処理のみを取り上げます。

最初のバージョンでは、単一の犯罪種別または単一のコンプライアンスのトピックを選択し、10~50 件のソースドキュメントのみをインポートします。検証後に範囲を拡大してください。全体のプロセスには約 25~40 分かかります。ドキュメントの解析時間は、ソースドキュメントの数とサイズによって異なります。

このソリューションは資料検索のための支援ツールであり、法的見解を生成するものではありません。検索結果とモデルが生成した要約の両方について、手動によるレビューのステップを維持してください。

前提条件

  • Milvus ナレッジベースが作成され、ナレッジベース ID (例: kd-803ae9b10cc31) が記録されています。

  • Alibaba Cloud アカウントで RAM ユーザーを作成し、[永続的な AccessKey を使用] を選択し、システムポリシー AliyunMilvusFullAccess をアタッチしました。

  • OpenAI chat/completions プロトコルをサポートする大規模言語モデルのエンドポイントと API キーが必要です。

  • Python 3.8 以降がローカルにインストールされ、プロジェクトディレクトリがセットアップされていること。

  • ソースドキュメントの権限チェックと非識別化が完了していること。詳細については、「法律シナリオに関する注意事項」をご参照ください。

ステップ1:事例タグの定義

「ナレッジベース詳細」ページで、[基本情報] の下にある [タグ] > [管理] の順に選択し、次の 4 つのタグを追加します。 タグ名入力ボックスの右側にあるドロップダウンリストからフィールドタイプを選択します。 選択可能なオプションは string、int64、list、float32、bool です。

タグ名

フィールドタイプ

説明

値の例

docType

string

ドキュメントタイプ

判例、法令、コンプライアンスポリシー

court

string

審理裁判所

XX市第一人民法院

caseType

string

訴訟原因

契約詐欺、交通事故の発生

judgmentYear

int64

判決年

2025

判定年を int64 として定義しても、範囲クエリは有効になりません。取得 API は整数範囲式をサポートしていません。判定年を int64 として定義するのは、サーバーが数値文字列を整数に変換することで、=、in、および not in が正しく機能するようにするためです。したがって、「過去 3 年間のケース」のような要件の場合、クライアントはまず年のリストを計算してから、たとえば [2024, 2025, 2026] のように in で渡す必要があります。tag_filter の条件で ≥ や ≤ を使用しないでください。年の範囲が広い場合、同等に効率的な代替手段はありません。複数のクエリに分割してください。

警告

タグ管理ダイアログボックスは全体として保存され、ダイアログボックスを開いた後にタグリストが非同期で読み込まれます。新しいタグを追加して [Done] をクリックする前に、既存のすべてのタグが表示されるまで待ってください。そうしないと、保存時に「空のリスト + 新しいタグ」で全体が上書きされ、既存のタグ定義が消去されてしまう可能性があります。

タグのタイプと値に関する注意点は次のとおりです。

  • int64 型のタグの場合、documents.jsonl に 2025 のようなプレーンな JSON 数値を直接書き込むことができます。フィルタリングの際、数値または文字列のどちらを渡しても value は一致します。

  • タグ値は空文字列にすることができます。法律、規制、コンプライアンスポリシーには審級がないため、"court": "" と記述できます。コンソールには court= と表示され、後でフィルター条件として court = "" を使用して、これらのソースドキュメントに完全に一致させることができます。

ステップ2:ソースドキュメントとインポートマニフェストの準備

  • ソースドキュメントを documents/ ディレクトリに配置してください。PDF、DOCX、Markdown、TXT、およびその他の形式がサポートされています。本文またはファイル名に、事件番号、裁判所、判決日を記載してください。我々のテストでは、本文の 1 行目に事件番号を記述したところ、事件番号で対応する判決を直接取得できました。

  • documents.jsonl を作成し、各ソースドキュメントにドキュメントタイプ、裁判所、訴訟原因、判決年のアノテーションを付けます。

    {"path": "documents/criminal-case-001.md", "metadata": {"docType": "Court judgment", "court": "The First People's Court of XX City", "caseType": "Contract fraud", "judgmentYear": 2025}}
    {"path": "documents/criminal-case-002.md", "metadata": {"docType": "Court judgment", "court": "The Second People's Court of XX City", "caseType": "Contract fraud", "judgmentYear": 2023}}
    {"path": "documents/law-excerpt.md", "metadata": {"docType": "Laws and regulations", "court": "", "caseType": "Criminal", "judgmentYear": 2024}}
    {"path": "documents/compliance-policy.md", "metadata": {"docType": "Compliance policy", "court": "", "caseType": "Compliance", "judgmentYear": 2024}}
  • 判例では、事件の事実、判決理由、結論の文脈を維持する必要があります。ナレッジベースの詳細ページで、[Processing Policy] の下にある [Create Policy] をクリックして、チャンクの粒度を調整します。最大チャンク長の単位は文字です (デフォルトは 512) 。係争中の主要な論点と判決の理由ができるだけ同じチャンクに収まるように、770~1,150 文字 (約 512~768 トークン) に設定します。

  • 同じ訴訟原因で結論の異なる事例をインポートします。法律検索の価値は、まさに意見の相違を提示することにあります。テストでは、結論が反対の 2 つの契約詐欺の判例をインポートした後、モデルは両方を引用し、「一方の事例では共同正犯が認定されたが、もう一方では共謀の証拠が不足していたため認定されなかった」と明示的に指摘しました。これは、単一の結論を持つ資料をインポートするよりも参照価値が高いです。モデルが個々の事例の結論を一般規則として提示しないように、プロンプトでそのような事例を別々に提示するよう要求します。

ステップ3:検索パラメーターとプロンプトの設定

config.json で、法務シナリオに合わせて aliyun.knowledge_base_version、retrieval、および scenario を調整します。 次の例では、これら 3 つのブロックのみを示します。 参照先のチュートリアルの config.json の残りのブロックは、前提条件で準備したナレッジベース ID、LLM エンドポイント、および API キーを含め、そのまま保持します。

{
  "aliyun": {
    "knowledge_base_version": "LATEST_PUBLISHED"
  },
  "retrieval": {
    "page_size": 8,
    "candidate_count": 80,
    "min_score": 0.25,
    "semantic_weight": 0.4,
    "enable_query_expansion": true,
    "rerank_model_name": "qwen3-rerank",
    "tag_filter": {
      "relation": "and",
      "conditions": [
        {"field": "docType", "op": "=", "value": "Court judgment"}
      ]
    }
  },
  "scenario": {
    "title": "法務・コンプライアンス事例検索",
    "system_prompt": "あなたは法律資料の検索アシスタントです。最終的な法的見解は提供しないでください。検索された資料から、事実、係争中の主要な論点、司法見解とその根拠を厳密に要約し、各項目に [出典 N] を注釈として付けてください。異なる事例が相反する結論に達した場合は、それらを別々に提示してください。検索された資料が空であるか、質問と無関係である場合は、「資料が不十分なため、手動によるレビューを推奨します」とだけ返信し、検索された資料に記載されていない法律、規制、司法解釈、または事例を絶対に引用しないでください。",
    "image_enabled": false,
    "sample_questions": [
      "契約履行能力を偽って商品代金をだまし取った場合、契約詐欺における共同正犯はどのように判断されますか?",
      "交通事故を起こした後の自首は、量刑にどのように影響しますか?",
      "主犯と従犯の区別について論じている事例はどれですか?"
    ]
  }
}

主要なパラメーターに関する注意点は次のとおりです。

  • semantic_weight=0.4: 法律検索では、事件番号、犯罪種別、法律用語が多用されるため、セマンティックの重みを下げ、それに応じてキーワードの重みを上げます。弊社のテストでは、完全な事件番号で検索すると、対応する判決に正確にヒットします。この値は、検索結果の scoreDetails を使用して調整できます。scoreDetails には、keywordScore と semanticScore が含まれています。

  • qwen3-rerank を有効にした場合の candidate_count=80: 候補セットを拡張し、リランキングモデルが類似ケースとの類似度によって候補をランク付けできるようにします。 リランクスコアとベクトルスコアは同じスケールではないことに注意してください。 min_score と組み合わせると、一部の結果が除外される可能性があります。 チューニングする際は、まず 2 つのうちの 1 つを固定します。

  • コンプライアンス追跡で固定バージョンが必要な場合を除き、knowledge_base_version は LATEST_PUBLISHED に設定してください。バージョンの固定と削除の詳細については、「公開済みバージョンの管理」をご参照ください。

タグフィルタリングで機能する演算子は 3 つのみ

現在のバージョンでのテストでは、唯一 =、in、および not in は tag_filter.conditions で、実際に有効になる op の値です:

演算子

動作

例

=

完全一致:int64 タグの場合、数値または文字列のどちらを渡しても機能します

{"field": "judgmentYear", "op": "=", "value": 2025}

in

列挙一致

{"field": "judgmentYear", "op": "in", "value": [2024, 2025]}

not in

列挙の除外

{"field": "docType", "op": "not in", "value": ["コンプライアンスポリシー"]}

>, ≥, <, ≤

条件は無視され、すべてのデータが返されます (エラーは発生しません) 。

—

等しくない, 空, 空ではない, ...で始まる, ...で終わる

条件は無視され、すべてのデータが返されます

—

含む, 含まない

in/not in のエイリアスであり、文字列の包含ではありません。文字列の断片を渡すと、結果は 0 件になります。

—

効果のない演算子を渡した場合、API はエラーを発生させず、フィルタリングなしですべてのデータを返します。法律シナリオでは、これは除外されるべきであった事例が LLM のコンテキストに入力され、ページ上では何も問題がないように見えることを意味します。したがって、

  • 年の範囲でフィルターするには > や ≥ を使用しないでください。 代わりに in を使用して年を列挙してください。たとえば {"field": "judgmentYear", "op": "in", "value": [2023, 2024, 2025]} のようにします。

  • 空のタグのチェックには、emptyは使用しないでください。 代わりに {"field": "court", "op": "=", "value": ""} を使用してください。

  • フィルター条件を設定した後は、必ずフィルターなしの結果と総結果数を比較して、フィルターが実際に機能していることを確認してください。両者が同じ場合、条件は適用されていません。

    op が eq、==、または like などのエイリアスとして記述されている場合、API は 400 Unsupported tag filter operator を返し、すべてのクエリが失敗します。そのエラーメッセージに記載されている "Supported operators" には、上記の表にある機能しない演算子が含まれているため、使用可能なリストとして扱うことはできません。

ステップ4:app.py で空の検索結果をインターセプトする

再利用された app.py では、検索結果が空の場合でも llm_answer() は LLM を呼び出します。その場合、コンテキストは空の文字列となり、モデルは完全に自身の知識から回答します。私たちのテストでは、「知的財産権侵害の損害賠償はどのように計算されますか?」など、ナレッジベースに含まれていない質問をしました。モデルは、sources が空であったにもかかわらず、長文の損害賠償計算ルールと次のような捏造された出典注釈[Source 1: Interpretation of ... Punitive Damages, Article 2] を生成しました。法的なシナリオでは、この種の出力は非常に誤解を招きやすく、コードレベルで遮断する必要があります:

def llm_answer(question: str, results: list[dict[str, Any]]) -> str:
    if not LLM.get("enabled", True):
        return "LLM は無効です。以下の検索結果をご参照ください。"
    if not results:
        return "この質問に関連する資料がナレッジベースで見つからなかったため、回答できません。関連資料を追加して再試行するか、手動レビューにエスカレーションしてください。"
    ...

プロンプトの制約だけでは信頼性が高くありません。たとえ system_prompt で「資料が不十分な場合は、その旨を明確に述べる」と明示的に指示しても、モデルはそれでも回答してしまいます。前述のフォールバックを追加すると、情報が見つからなかった質問には一貫してプロンプトメッセージが返されるようになり、検索結果がある通常の質問が影響を受けることはありません。

ステップ5:アップロード、公開、検証

  1. ソースドキュメントをアップロードします。MetaFields はバッチ全体に適用され、スクリプトはドキュメントをタグ別にグループ化してバッチで送信します。

    python upload.py --manifest documents.jsonl
  2. コンソールの [Data Management] ページで、ドキュメントのステータスが [Processed] であることを確認します。アップロード API からの成功応答は、非同期解析が送信されたことのみを意味します。

  3. [Version Management] ページで、[Publish Version] をクリックし、ウィザードを完了して、新しいバージョンのステータスが [Published] であることを確認します。ボタンがグレー表示されている場合は、まず不要になった古いバージョンを削除してください。詳細については、「公開バージョンの管理」をご参照ください。

  4. サービスを開始します。

    python app.py
  5. サービスが開始したら、テスト用の質問をします。

    curl -sS http://127.0.0.1:7860/api/ask \
      -H 'Content-Type: application/json' \
      -d '{"question":"契約履行能力を偽って商品代金をだまし取った場合、契約詐欺における共同正犯はどのように判断されますか?"}'
  • ページは正常に開き、質問を送信すると回答と検索ソースの両方が返されます。

  • 回答内の各 [Source N] は、以下のソースセクションにあるソースチャンクに対応します。

  • 完全な事件番号で質問すると、対応する判例が正確にヒットします。

  • ナレッジベースがカバーしていない内容について質問すると、応答は「資料が不十分です」というメッセージを返し、いかなる法的引用も含まれていません。これはこのシナリオで最も重要な受け入れ基準であるため、実際にテストして確認してください。

  • さらに資料を追加して新しいバージョンを公開すると、ページは新しいコンテンツを検索できるようになります。

公開バージョンの管理

LATEST_PUBLISHED は、自動的に最新の公開バージョンを使用します。 また、v2 などの明示的なバージョン番号を指定して、バージョンを固定することもできます。 コンプライアンスシナリオでは、通常、どのバージョンの資料がどの決定をサポートしたかを追跡する必要があるため、アプリケーションのコールログに実際に使用されたバージョン番号を記録してください。

重要

バージョンを削除すると、直ちに取得できなくなり、その後バックエンドが非同期でデータをクリーンアップします。バージョンを削除する前に、そのバージョンにまだピン留めされているアプリケーションを新しいバージョンに切り替えてください。アプリケーションが使用しているバージョンを削除した場合、取得処理は直ちに 404 Knowledge base version ... does not exist を返し、ページ上のすべての質問が失敗します。

同時に存在できる公開バージョンは最大 3 つです。[Publish Version] ボタンはグレー表示されますが、ページには「N 件の保留中の変更があります」と表示されます。まず、バージョンレコードから不要になった古いバージョンを削除してください。バージョンを固定する場合は、古いバージョンを削除する前に設定を更新する手順も確立する必要があります。

法律シナリオに関する注意事項

アプリケーションをエンドユーザーと共有する前に、以下の考慮事項を確認してください。

  • 権限のある資料のみ — 公開および処理する権限のある資料のみを使用し、アップロード前に権限チェックを完了してください。判例に個人情報が含まれている場合は、まず非識別化してください。ドキュメントチャンクはコンテキストとして LLM に送信され、これはデータの外部送信にあたります。

  • 常設の免責事項 — ページには常設の免責事項を掲載する必要があります。サンプルページには、「回答は、公開済みのナレッジベースコンテンツから生成されます」としか記載されていません。 templates/index.html 内の注意書きを、「これらの結果は資料検索の補助にすぎず、法的見解を示すものではありません。専門家によるレビューを受けてください。」と明確に記載されるように変更してください。

  • ドキュメントタイプごとにエントリポイントを分離 — たとえば、docType = 法令 を使用して一般公開用のエントリポイントを制限し、法令のみを取得するようにします。一方、内部分析用のエントリポイントでは判例も許可します。

  • 本番環境へのデプロイ — Flask 開発サーバーを使い続けないでください。本番用の WSGI サーバーに切り替え、シークレット管理、認証、監査、スロットリングを追加してください。

よくある質問

現象

原因と解決策

どの質問でも 404 Knowledge base version ... does not exist が返されます

設定にハードコーディングされたバージョン番号が削除されたか、公開されていません。 LATEST_PUBLISHED を使用するか、[バージョン管理] ページに実際に存在するバージョン名を指定してください。

質問をすると 500 エラーが返され、ログに 400 Unsupported tag filter operator と表示されます

op は eq、==、like などのエイリアスを使用します。代わりに =、in、または not in を使用してください。

タグフィルタリングありの結果数が、フィルタリングなしの場合とまったく同じである

無効な演算子 (>、≥、≠、または empty など) が使用されています。代わりに =、in、または not in を使用してください。ステップ 3 をご参照ください。

年の範囲によるフィルタリングが効果ない

数値比較演算子は有効になりません。年の列挙には in を使用してください。

タグフィルタリングが常に 0 件の結果を返す

タグ名のスペルが記述されたものと異なっている場合、API はエラーを発生させずに 0 件の結果を返すだけです。ナレッジベースの詳細ページにある [タグ] > [管理] でそれを確認してください。弊社のテストでは、contains 演算子も同様に 0 件の結果を返します。

ナレッジベースがカバーしていない質問をしたが、もっともらしい法的引用が得られた

llm_answer() は、取得結果が空の場合でも LLM を呼び出していました。ステップ 4 の、結果が空の場合のフォールバックを追加します。

検索結果の tags フィールドが空です

現在のバージョンでは、タグのバックフィルは行われません。tags フィールドは、アップロード時に入力した裁判所と判決年を返しません。これは AddDocuments の MetaFields とは異なるフィールドであり、この値を返すためのパラメーターはありません。空の値は、アップロードが失敗したことを意味するものではありません。この情報を回答に表示する必要がある場合は、取得結果を documentId で documents.jsonl と結合し、コンテキストに追加してください。

アップロードすると、400 No OSS document can be registered. が返されます。

バッチ内のすべてのファイルが、ファイル名の重複により重複排除されました。ファイル名を変更するか、まずコンソールで古いデータを削除してください。

取得が失敗し、code = None と報告されます。

再利用されたコードにある _check() のロジックは誤っています (getattr(body, "code", 0) != 0)。正常なレスポンスでは code が None になるためです。これを getattr(body, "code", None) not in (None, 0, "0") に変更します。

リランキングを有効にすると、結果が少なくなる

リランクスコアとベクトルスコアはスケールが異なります。min_score と組み合わせると、より多くの結果がフィルタリングで除外されます。比較するには、まず min_score の値を下げるか、リランキングを無効にしてください。