> ## Documentation Index
> Fetch the complete documentation index at: https://developer.qaip.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 外部LLM送信前のマスキング

> エージェント実行で会話履歴と検索結果を外部LLMへ送る前に、版管理されたポリシーでマスキングします。

`/agent/run`・`/agent/runs` は、リクエストごとに `redactionPolicyId` を指定することで、
外部の LLM・embedding プロバイダへ送る前に会話履歴・検索クエリ・検索結果をマスキングできます。

<Warning>
  `redactionPolicyId` を指定しない場合（省略、または `null`）は**マスキングを行わず、入力はそのまま
  外部プロバイダへ送信されます**。これは明示された API の契約であり、設定不備時に安全側へ倒れる
  （自動でマスキングされる）動作ではありません。マスキングが必要なリクエストでは必ず
  `redactionPolicyId` を指定してください。
</Warning>

## マスキングの対象

`redactionPolicyId` を指定すると、次のすべてが対象になります。

* 会話履歴の全ロール（`user` / `assistant` / `system` / `developer` / `tool` / `activity` / `reasoning`）
* 文字列 content、マルチモーダル content の text、辞書内の文字列、ツール呼び出し引数（JSON）内の文字列、
  文字列 metadata と参照 URL
* 親 run から復元した過去の会話履歴（run ごとに、そのとき指定したポリシーで再マスキングされます）
* ナレッジベース検索のクエリと検索結果（本文・タイトル・URL）
* Google Web 検索へ送るクエリと、後続の外部 LLM へ渡す検索結果（本文・タイトル・URL）
* 外部 Iceberg テーブルのスキーマ・クエリ結果
* エージェントが使うその他すべてのツールの実行結果

この機能は、外部プロバイダへ送るコピーだけをマスキングします。ダッシュボードや
`agent_runs` に保存される内容、`RUN_STARTED` イベントは原文のままです。復元用の
マッピングは保存されません。

外部プロバイダへ送るコピーから**除外**されるもの（マスキングではなく削除）:

* `forwardedProps`
* `state` / `tools` / `context` / `resume`

これらは外部プロバイダへ渡らないため、マスキングせず削除します（マスキングしても外部へは
渡らずコストだけが増え、一方で処理対象から外すと原文が残るため）。原文はダッシュボードや
`agent_runs` 側では保持されます。

対象外:

* システムプロンプト

ローカルのマスキング処理で検査できない入力（画像・音声などのインラインバイナリ、未対応の
メッセージ種別、未知の追加フィールド）は、原文のまま送信せずエラーになります。

## 検出する分類

| コード | 置換ラベル       | 内容                                                                                    |
| --- | ----------- | ------------------------------------------------------------------------------------- |
| `N` | `[氏名]`      | 氏名                                                                                    |
| `T` | `[電話番号]`    | 電話番号                                                                                  |
| `E` | `[メールアドレス]` | メールアドレス                                                                               |
| `A` | `[住所]`      | 番地を含む住所                                                                               |
| `I` | `[ID]`      | マイナンバー・免許証番号等の公的 ID                                                                   |
| `C` | `[カード番号]`   | クレジットカード番号                                                                            |
| `P` | `[個人情報]`    | その他の個人を特定できる情報                                                                        |
| `K` | `[認証情報]`    | API key、password、Bearer / access / refresh token、Cookie / session、接続文字列の password、秘密鍵 |
| `B` | `[業務機密]`    | ポリシーごとに定義された業務機密                                                                      |

組み込みポリシーで有効になる分類はポリシーごとに決まります。テナントポリシーでは9分類をすべて
有効にし、テナント管理者が変更できるのは `B`（業務機密）の定義だけです。PIIや認証情報の分類を
無効化することはできません。入力した定義は分類用データとして安全なJSON境界へ置かれ、system
instructionや出力schemaを変更する自由記述プロンプトとしては扱われません。

## テナントポリシーを管理する

管理APIは **All roleのAPIキー専用**です。App roleのAPIキーやDashboardのBearer認証では、一覧取得を
含む管理操作を実行できません。一方、ACTIVEにしたポリシーは同じテナントのApp roleのAPIキーからも
Agentの `redactionPolicyId` として利用できます。

ポリシーは `DRAFT → ACTIVE → ARCHIVED` の順で管理します。本文は上書きせず、新しい版を追記します。
ACTIVE切替後も実行中のrunは開始時の版を使い続け、切替後に開始したrunだけが新しい版を使います。

<Warning>
  ポリシー本文はサービスに保存されます。`description`、`include`、`exclude`、`examples`には
  実在する秘密情報や個人情報を入れず、必ず合成データを使ってください。一度ACTIVEにした版は
  ARCHIVEDにしてもhard deleteできません。その名前もテナント上限20件に数え続けられるため、
  新しい定義には可能な限り同じ名前の新しい版を作成してください。
</Warning>

### 1. CLI schemaを確認する

APIキーはコマンドラインへ書かず、`QAIP_API_KEY` 環境変数で渡してください。ポリシー本文もshell引数へ
直書きせず、権限を制限した一時JSON fileか標準入力を使います。

```bash theme={null}
export QAIP_BASE_URL="https://api.example.com/api/v1"
read -s QAIP_API_KEY && export QAIP_API_KEY

qaip schema redaction-policies
```

### 2. DRAFTを検証・作成する

`policy.json` は `chmod 600` したfileとして用意します。`enabledCategories`、version、statusはサーバーが
決めるため入力しません。

```json policy.json theme={null}
{
  "name": "internal-contracts",
  "description": "社内契約情報向け",
  "businessConfidential": {
    "definition": "未公開の契約・価格・計画に関する情報",
    "include": [{ "text": "契約金額", "mode": "value_clause" }],
    "exclude": ["公開済みの価格表"],
    "examples": ["架空会社Aとの年間契約額は3,200万円"]
  }
}
```

```bash theme={null}
qaip api redaction-policies.validate --json @policy.json --dry-run
qaip api redaction-policies.validate --json @policy.json

qaip api redaction-policies.create --json @policy.json --dry-run
qaip api redaction-policies.create --json @policy.json
```

同じ正規化済みDRAFTの再送は同じ版を返します。異なるDRAFTが残っている場合は、既存本文を上書きせず
`409 DRAFT_ALREADY_EXISTS` になります。

### 3. ACTIVEにする

初回の切替ではACTIVE版がないことをCASで指定します。`cas.json` もfileまたは標準入力で渡します。

```json cas.json theme={null}
{ "expectedActiveVersion": null }
```

```bash theme={null}
qaip api redaction-policies.activate-version \
  --name internal-contracts --version 1 --json @cas.json --dry-run
qaip api redaction-policies.activate-version \
  --name internal-contracts --version 1 --json @cas.json --yes
```

初回リリースでは、テナント提供の評価ケースはactivateの必須条件ではありません。definitionの形式と
安全性は同期検証しますが、業務上の検出品質は利用者側でも合成データで確認してください。

### 4. Agentから利用する

```json theme={null}
{
  "runId": "6a5c8f1e-0000-4000-8000-000000000003",
  "threadId": "thread-tenant-policy",
  "state": {},
  "messages": [{ "id": "m1", "role": "user", "content": "架空会社Aとの契約金額は3,200万円です" }],
  "tools": [],
  "context": [],
  "forwardedProps": {},
  "redactionPolicyId": "internal-contracts"
}
```

### 5. 新しい版・rollback・archive

新しい本文から `name` を除いたJSON fileを作り、次版のDRAFTを追記します。

```bash theme={null}
qaip api redaction-policies.create-version \
  --name internal-contracts --json @policy-version.json --dry-run
qaip api redaction-policies.create-version \
  --name internal-contracts --json @policy-version.json

qaip api redaction-policies.list-versions \
  --name internal-contracts --limit 50
```

切替・rollback・archiveでは、直前に取得したACTIVE版を `expectedActiveVersion` に指定します。別の管理者が
先に切り替えた場合は `409 ACTIVE_VERSION_CONFLICT` となり、部分更新は行われません。policy detailを
再取得してから、新しい期待版でやり直してください。

```bash theme={null}
qaip api redaction-policies.activate-version \
  --name internal-contracts --version 2 --json @cas-v1.json --dry-run
qaip api redaction-policies.activate-version \
  --name internal-contracts --version 2 --json @cas-v1.json --yes

# 過去版を再度ACTIVEにするrollbackも同じactivate-versionを使います。
qaip api redaction-policies.activate-version \
  --name internal-contracts --version 1 --json @cas-v2.json --dry-run
qaip api redaction-policies.activate-version \
  --name internal-contracts --version 1 --json @cas-v2.json --yes

qaip api redaction-policies.archive \
  --name internal-contracts --json @cas-v1.json --dry-run
qaip api redaction-policies.archive \
  --name internal-contracts --json @cas-v1.json --yes
```

archive後の新しいAgent runは `422 UNKNOWN_REDACTION_POLICY` になります。既に作成済みのrunを同じrun IDや
idempotency keyで再送した場合は、ポリシーを再評価せず元のrunを返します。一度ACTIVEになった版は監査の
ためhard deleteできません。未有効化のDRAFTだけが `delete-version --yes` の対象です。

## リクエスト例

### マスキングなし（既定）

```json theme={null}
{
  "runId": "6a5c8f1e-0000-4000-8000-000000000001",
  "threadId": "thread-1",
  "state": {},
  "messages": [{ "id": "m1", "role": "user", "content": "佐藤太郎さんの連絡先を教えてください" }],
  "tools": [],
  "context": [],
  "forwardedProps": {}
}
```

入力はそのまま外部プロバイダへ送信されます。

### マスキングあり（既知のポリシー ID）

```json theme={null}
{
  "runId": "6a5c8f1e-0000-4000-8000-000000000002",
  "threadId": "thread-1",
  "state": {},
  "messages": [{ "id": "m1", "role": "user", "content": "佐藤太郎さんの連絡先を教えてください" }],
  "tools": [],
  "context": [],
  "forwardedProps": {},
  "redactionPolicyId": "pii-standard"
}
```

外部プロバイダへは `[氏名]さんの連絡先を教えてください` として送信されます。ダッシュボードの
表示と保存内容は原文のままです。

### 未知のポリシー ID・空文字（`422`）

```json theme={null}
{ "...": "...", "redactionPolicyId": "no-such-policy" }
```

```json theme={null}
{ "error": { "message": "unknown redaction_policy_id", "type": "UNKNOWN_REDACTION_POLICY" } }
```

空文字・空白のみを指定した場合はスキーマ検証で弾かれるため、FastAPI 標準の検証エラー形式
（`detail` が配列）になります。

```json theme={null}
{ "detail": [{ "loc": ["body", "input", "redactionPolicyId"], "msg": "..." }] }
```

機械可読コードは `error.type` にあります（`UNKNOWN_REDACTION_POLICY` /
`AGENTCORE_REDACTION_UNSUPPORTED` / `REDACTION_UNAVAILABLE`）。

いずれの場合も「マスキングなし」とは解釈されません。run は作成されず、外部プロバイダも呼ばれません。

### AgentCore 実行モードとの併用（`422`）

サーバーが AgentCore 実行モードで動作している場合、`redactionPolicyId` との併用はできません。

```json theme={null}
{
  "error": {
    "type": "AGENTCORE_REDACTION_UNSUPPORTED",
    "message": "redaction_policy_id is not supported with the AgentCore execution mode"
  }
}
```

AgentCore ランタイム内のナレッジベース検索結果はマスキング処理へ到達できないため、この組み合わせだけを
拒否しています。`redactionPolicyId` を指定しない AgentCore 実行は従来どおり利用できます。

### マスキング基盤が利用できない場合（`503`）

```json theme={null}
{
  "error": {
    "type": "REDACTION_UNAVAILABLE",
    "message": "redaction is unavailable: model_server_unreachable"
  }
}
```

`redactionPolicyId` を指定したリクエストは、マスキング基盤が停止・timeout・失敗しても
原文送信にフォールバックしません。`redactionPolicyId` を指定しないリクエストと
`/completions`・`/search`・`/extract` は影響を受けず利用できます。

### 実行中にマスキングが失敗した場合（`RUN_ERROR`）

```json theme={null}
{ "type": "RUN_ERROR", "code": "REDACTION_FAILED", "message": "redaction failed; ..." }
```

run は `FAILED` になり、失敗したマスキング境界より後の外部プロバイダ呼び出しは行われません。
たとえば検索クエリの失敗時は embedding / Google 検索を呼ばず、検索結果の失敗時は後続の
外部 LLM を呼びません。すでに完了した先行呼び出しは取り消されません。

## 補足

* 親 run のポリシーは継承されません。子 run でもマスキングが必要な場合は、その run で
  `redactionPolicyId` を明示してください。
* **外部 Iceberg テーブルのクエリ結果は行数が絞られます。** マスキングは 1 セルずつ処理する
  ため、値の数が行数 × 列数で増えます。マスキングが制限時間内に終わる範囲まで結果を切り詰め、
  切り詰めた場合は結果の `truncated` が `true` になります（絞らないとマスキングが間に合わず
  run 自体が失敗します）。スキーマは切り詰めません。
* **引用リンクが機能しなくなる場合があります。** 検索結果の URL もマスキング対象なので、
  URL のパスやクエリにマスキング対象（氏名など）が含まれていると、その部分が置換ラベルに
  変わり、回答中の引用リンクが目的のドキュメントへ到達しません。署名付き URL のクエリに
  認証情報が含まれるケースを外部プロバイダへ渡さないための仕様です。原文の URL は保持されず、
  後からリンクを復元する手段はありません。
* モデルの応答に対する再識別・プレースホルダの復元は行いません。
* マスキングは処理失敗・不正応答・未対応入力の原文送信を防ぎますが、ローカルモデルの意味的な
  見落としを完全に排除するものではありません。この残存リスクは、認証情報の決定的検出、
  プロンプト安全対策、ポリシーごとに版管理した評価データセットでのリリース判定によって管理しています。
