# 独自LLMとデジタルヒューマンを接続する

## 1. 概要

\*\*DIP（Digital Humans Identity Portal）\*\*の `Gateway` プロファイルを使用すると、お客様が独自で構築したチャットボットやLLMの API とデジタルヒューマンを接続して利用することができます。

**`Gateway` プロファイル** : フロントエンドから送られるリクエストを直接指定の URL (`endpoint_url`) に転送して応答を返却するプロファイル。

### 利用シーン

* **カスタム Bot 連携**： [接続実績のある会話AI・チャットボット](/ops/chatbot-integration/compatible-chatbot-ai-list.md) 以外の、自社開発したチャットボット API と接続し、独自対話ロジックを利用する場合
* **独自 LLM 活用**：自社専用にファインチューニングした大規模言語モデル（LLM）にリクエストを流し、回答を取得する場合
* **外部 SaaS 連携**：他社が提供する NLP/Semantic API を経由せずに直接呼び出す場合
* **リクエストベースカスタム処理**：ユーザー発話や発話言語など、リクエストに含まれる情報に応じてカスタムロジックを実行する場合
* **レスポンスベースカスタム処理**：生成された応答結果に基づき、後続のカスタム処理や外部アクションをトリガーする場合

### システム構成 & シーケンス

**システム構成**

```mermaid
flowchart LR
    FE[Frontend <br> （ホステッドエクスペリエンス）]
    
    subgraph DHP[デジタルヒューマンプラットフォーム]
        GW[Gateway プロファイル]
    end
    
    CS["お客様システム<br>(独自で準備したチャットボット/LLM API)"]
    
    FE -->|HTTP POST| GW
    GW -->|HTTP POST| CS
    CS -->|応答 JSON| GW
    GW -->|応答 JSON| FE

```

**シーケンス図**

{% hint style="warning" %}
デジタルヒューマンとの会話は、ユーザーがデジタルヒューマンを呼び出したとき、またはユーザーが発話したときに開始されます。これらのトリガーにより、デジタルヒューマンプラットフォームからお客様システムのエンドポイントへリクエストが送信されます。
{% endhint %}

```mermaid
sequenceDiagram
    participant FE as Frontend <br> （ホステッドエクスペリエンス）
    
    box デジタルヒューマンプラットフォーム
        participant GW as Gateway プロファイル
    end
    
    participant CS as お客様システム<br/>(独自で準備したチャットボット/LLM API)

    Note over FE: 🎤 デジタルヒューマンを呼び出しまたは<br/>ユーザーが発話
    FE->>GW: HTTP POST (STT テキスト化したユーザ発話・フロントエンド情報)
    GW->>CS: HTTP POST (顧客サーバーへ転送)
    CS-->>GW: 応答 JSON
    GW-->>FE: Websocket（フロントエンドへ発話要求）

```

1. **ユーザーの発話を受信　フロントエンド** → **Gatewayプロファイル**
   * ユーザーが話した内容を音声認識（STT）でテキスト化
   * テキスト化された発話内容とフロントエンド情報をHTTP POSTで送信
2. **お客様システムへの転送　Gatewayプロファイル** → **お客様システム**
   * 受信したリクエストをそのまま設定済みの`endpoint_url`へ転送
   * お客様が独自に準備したチャットボットやLLM APIが処理を実行
3. **AIからの応答生成　お客様システム** → **Gatewayプロファイル**
   * チャットボット/LLMが生成した応答をJSON形式で返却
   * 応答にはデジタルヒューマンが話すべき内容が含まれる
4. **デジタルヒューマンによる発話　Gatewayプロファイル** → **フロントエンド**
   * 受信したJSONをWebsocket経由でフロントエンドに即座に送信
   * フロントエンドがデジタルヒューマンに応答内容を発話させる

## 2. Gateway プロファイルの設定手順（DIP 管理画面）

1. ログイン後、対象のペルソナページで「Gateway」で **NLP アカウント** を作成します。
2. パラメータ設定
   * **APIキー**
     * お客様のエンドポイントでAPIキー認証を使用する場合は、実際のAPIキーを入力してください
     * APIキー認証を使用しない場合は \`dummy\` を入力してください                                                                                                              &#x20;
     * 空白は設定できません                                                                                                                                                &#x20;
     * \`dummy\` 以外の値を設定した場合、リクエストヘッダーに \`X-API-Key\` として含まれます&#x20;
   * **endpoint\_url**
     * リクエストを転送する先のお客様システムのエンドポイント URL を指定

## 3. Gatewayプロファイル → エンドポイント のリクエスト形式

### リクエスト Header&#x20;

```
{
    "Content-Type": "application/json",
    "X-API-Key": "Gateway プロファイルで設定したAPIキーの値 (dummy以外)"
}
```

### リクエストBody JSON サンプル

```json
{
  "digital_human_id": "<実際のdigital_human_id>",
  "metadata": {
    "browser_detected_locales": "ja-JP:en-US",
    "custom": {},
    "persona_id": "<実際のpersona_id>",
    "prompt_id": "<実際のprompt_id>",
    "session_id": "<フロントエンドのsession_id>",
    "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...",
    "user_screen_height": 1350,
    "user_screen_width": 2560,
    "user_spoken_locale": "ja-jp",
    "user_timezone": "Asia/Tokyo"
  },
  "prompt": "ユーザー発話"
}
```

### 各フィールド説明

記号の意味:

* ● = 常に含まれる
* △ = 条件付き（注釈参照）
* （空欄）= 含まれない

| フィールド                        | 型      | nlp\_mode:v1 | nlp\_mode:v2 | 説明                                   |
| ---------------------------- | ------ | :----------: | :----------: | ------------------------------------ |
| `digital_human_id`           | string |       ●      |              | デジタルヒューマンの固有ID、ルーティング用               |
| `metadata`                   | object |       ●      |       ●      | 以下のサブフィールドを含む                        |
| └ `browser_detected_locales` | string |       ●      |       ●      | ブラウザ設定のロケール情報（例: ja-JP:en-US）        |
| └ `custom`                   | any    |       ●      |       ●      | フロントエンド設定のcustomMetadata（文字列/オブジェクト） |
| └ `persona_id`               | string |       ●      |       ●      | デジタルヒューマンのpersonaId                  |
| └ `prompt_id`                | string |       ●      |       ●      | リクエストID                              |
| └ `session_id`               | string |       ●      |       ●      | セッションID                              |
| └ `user_agent`               | string |       ●      |       ●      | ブラウザのUA情報                            |
| └ `user_screen_height`       | number |       ●      |       ●      | 表示画面 高さ（px）                          |
| └ `user_screen_width`        | number |       ●      |       ●      | 表示画面 幅（px）                           |
| └ `user_spoken_locale`       | string |     △ \*1    |     △ \*2    | 音声認識されたユーザーのロケール（例: ja-jp）           |
| └ `user_timezone`            | string |       ●      |       ●      | ユーザーのタイムゾーン（例: Asia/Tokyo）           |
| `prompt`                     | string |       ●      |       ●      | ユーザーの発話テキスト                          |

#### `user_spoken_locale` について

\*1 **V1**: UneeQ STT 利用時は含まれます。customStt（BYO-STT）利用時は空文字になります。

\*2 **V2**: UneeQ STT 利用時は含まれます。customStt 利用時は、使用するSTTプロバイダーの言語判定機能に依存します（Azure STT の `auto_detect_languages` 設定時は取得可能）。テキスト入力時は空文字になります。

`user_spoken_locale` が空文字の場合、Gateway 側でテキストからの言語判定をフォールバックとして実装することを推奨します。

## 4. エンドポイント → Gatewayプロファイル のレスポンス形式

### レスポンス Body JSON サンプル

```json
{
  "answer": "テスト応答",
  "instructions": {
    "displayHtml": {
      "html": "test"
    }
    // その他: customMetadata etc.
  }
}
```

### 各フィールド説明

| フィールド        | 型      | 必須 / 任意 | 説明                                                                                                                                                                                                                                                                                                                  |
| ------------ | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| answer       | string | 任意      | ユーザへの応答テキスト。空の場合は発話しません。                                                                                                                                                                                                                                                                                            |
| instructions | object | 任意      | <p>デジタルヒューマンに対する指示。Instructionsのみの指示で、発話なしにコンテンツを表示するなどの指示が可能です。<br><a href="https://gitlab.digitalhumans.jp/docs/docs-digitalhumansjp/-/blob/main/settings/README.md">displayHtml</a>・<a href="https://gitlab.digitalhumans.jp/docs/docs-digitalhumansjp/-/blob/main/settings/README.md">customMetadata</a> など</p> |

### タイムアウト

* リクエスト送信後5分以内にレスポンスがない場合、Gateway プロファイルはタイムアウトとして処理を終了します。

## 5. サンプル実装コード

### Node.js (Express)

```jsx
const express = require('express');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());

app.post('/gateway', (req, res) => {
  const { digital_human_id, metadata, prompt } = req.body;

  // 1. prompt に対する処理（例: Chatbot API 呼び出し）
  const replyText = `受け取ったプロンプト: ${prompt}`;

  // 2. レスポンス生成
  const responseBody = {
    answer: replyText,
    instructions: {
      displayHtml: { html: `<p>${replyText}</p>` }
    }
  };

  res.json(responseBody);
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Gateway プロファイルサーバー起動: http://localhost:${PORT}/gateway`);
});

```

### Python (FastAPI)

```python
from fastapi import FastAPI, Request
from typing import Any, Dict, Optional
from pydantic import BaseModel, Extra

class Metadata(BaseModel):
    # 既存フィールドをすべて Optional[Any] とし、default=None を付与
    browser_detected_locales:  Optional[Any] = None
    custom:                   Optional[Any] = None
    persona_id:               Optional[Any] = None
    prompt_id:                Optional[Any] = None
    session_id:               Optional[Any] = None
    user_agent:               Optional[Any] = None
    user_screen_height:       Optional[Any] = None
    user_screen_width:        Optional[Any] = None
    user_spoken_locale:       Optional[Any] = None 
    user_timezone:            Optional[Any] = None

    class Config:
        # 定義外のフィールドも受け入れる
        extra = Extra.allow

class GatewayRequest(BaseModel):
    digital_human_id: Optional[str]    = None
    metadata:         Optional[Metadata] = None
    prompt:           Optional[str]     = None

app = FastAPI()

@app.post("/gateway")
async def gateway(req: GatewayRequest):
    # 1. prompt に対する処理（例: Chatbot API 呼び出し）
    reply_text = f"受け取ったプロンプト: {req.prompt}"

    # 2. レスポンス生成
    return {
        "answer": reply_text,
        "instructions": {
            "displayHtml": {"html": f"<p>{reply_text}</p>"}
        }
    }
```

## 6. 非同期発話(Speak APIを使用する)

* リクエスト/レスポンスを伴わない、あるいはレスポンスタイムアウトの可能性があり、非同期で発話指示を行うには SpeakAPI を使用します。
* Gateway プロファイルへのリクエストには即時応答として `answer: ""` を返却し、バックグラウンドで LLM 処理を実行します。
* 発話リクエストは SpeakAPI エンドポイントへ以下情報を含めて HTTP POST することでトリガーします：
  * `persona_identifier`
    * 通常の Gateway リクエストには含まれないため、`customMetadata` に登録してリクエストパラメータの`metadata.custom` で受け取るか、`endpoint_url` のクエリパラメータに含めて受け渡してください。
    * デジタルヒューマンのペルソナを一意に識別する文字列です。
  * `session_id`
    * 会話セッションを識別するセッションのIDであり、ユニークなIDです。
* SpeakAPI の詳細仕様はこちら：[SpeakAPIドキュメント](https://gitlab.digitalhumans.jp/docs/docs-digitalhumansjp/-/blob/main/settings/README.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.digitalhumans.jp/dev/chatbot-connection/byo-llm-integration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
