> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# 独自モデルを統合する

> **Note**
>
> Custom LLMでは、外部エンドポイントを介して会話を独自のLLMに接続できます。
> ElevenLabsは[ネイティブ統合LLM](/docs/ja/eleven-agents/customization/llm)にも対応しています。

**Custom LLM**では、独自のOpenAI APIキーを使用することも、完全にカスタムなLLMサーバーを実行することもできます。

## 概要

デフォルトでは、OpenAIなどの人気モデルに対して独自の内部認証情報を使用します。カスタムLLMサーバーを使用するには、次のいずれかのOpenAI互換リクエスト／レスポンス構造に対応している必要があります：

* [Chat Completions API](https://platform.openai.com/docs/api-reference/chat/create)（`/v1/chat/completions`）
* [Responses API](https://platform.openai.com/docs/api-reference/responses/create)（`/v1/responses`）

> **Info**
>
> Responses APIは、追加機能をサポートするOpenAIの新しいAPI形式です。どちらのAPI形式も、
> カスタムLLM統合で完全にサポートされています。

次のガイドでは、両方のユースケースを説明します：

1. **独自のOpenAIキーを使用する**：独自のOpenAI APIキーをプラットフォームで使用します。
2. **カスタムLLMサーバー**：独自のLLMサーバー実装をホストして接続します。

以下の方法を学べます：

* OpenAI APIキーをElevenLabsに保存する
* OpenAIの[Chat Completions](https://platform.openai.com/docs/api-reference/chat/create)または[Responses](https://platform.openai.com/docs/api-reference/responses/create)エンドポイントを再現するサーバーをホストする
* ElevenLabsをカスタムエンドポイントに接続する
* 必要に応じてLLMに追加パラメータを渡す

## 推論の要約

エンドポイントは、最終回答とは別に推論を返す必要があります。ElevenLabsは最終回答から推論を生成しません。

サポートされているエンドポイントから推論をリクエストするには、エージェントのLLM設定で**Reasoning summary**をオンにするか、API経由で`enable_reasoning_summary`を設定します。

### 推論を返す

エンドポイントに一致する形式を使用してください：

#### Chat Completions API

各レスポンスデルタの`reasoning`または`reasoning_content`フィールドで推論をストリーミングします。

Gemini互換エンドポイントの場合、ElevenLabsは
`google.thinking_config.include_thoughts`で思考をリクエストし、
`extra_content.google.thought`でマークされたコンテンツを読み取ります。

#### Responses API

`summary`フィールドに要約テキストを含む`reasoning`出力項目を返します。

推論の労力が設定され、Reasoning summaryが有効な場合、ElevenLabsは
リクエスト内の`reasoning.summary`を`auto`に設定します。

保存、配信、制限については[推論の要約](/docs/ja/eleven-agents/customization/llm#reasoning-summary)を参照してください。

## 独自のOpenAIキーを使用する

カスタムOpenAIキーを統合するには、ElevenLabsダッシュボードでエージェント設定を更新してカスタムLLMサーバーを指定し、`OPENAI_API_KEY`を含むシークレットを作成します：

ElevenLabsダッシュボードのAgent設定で、右側にある「LLM」ドロップダウンメニューから「Custom LLM」を選択します。

![シークレットを追加](/docs/_fern-img/83c62b81ef04860b9d884e28e1b4ac7bbceaa6e481e72e15ae82a76ec9bbad8e.webp)

「LLM」の下にあるフィールドをクリックし、下へスクロールして「Custom LLM」を選択します。

カスタムLLMサーバーのServer URLとModel IDを入力します。

![URLを入力](/docs/_fern-img/85ed8154868096a5c8de4c5e1c09d83c861069ea098c7643a960cac1736b67cb.webp)

「API key」の下にあるドロップダウンをクリックし、「Create new secret」を選択します。キーに`OPENAI_API_KEY`と名前を付け、「value」フィールドにキーを追加して「Add secret」をクリックします。

「x」ボタンをクリックしてLLMモーダルを閉じ、「Publish」をクリックして変更を保存します。

## カスタムLLMサーバー

カスタムLLMサーバーを使用するには、OpenAI形式を利用した互換性のあるサーバーエンドポイントを設定します。Chat Completions API（`/v1/chat/completions`）またはResponses API（`/v1/responses`）のどちらかを実装できます。

どちらのエンドポイントも、`Content-Type: text/event-stream`を使用したSSE（Server-Sent Events）形式でレスポンスを返す必要があります。

#### Chat Completions API

Chat Completions APIは`/v1/chat/completions`エンドポイントを使用します。

各チャンクは`data: {json}\n\n`形式で、ストリームは`data: [DONE]\n\n`で終了する必要があります。

サーバー実装の例を示します：

```python
import json
import os
import fastapi
from fastapi.responses import StreamingResponse
from openai import AsyncOpenAI
import uvicorn
import logging
from dotenv import load_dotenv
from pydantic import BaseModel
from typing import List, Optional

# Load environment variables from .env file
load_dotenv()

# Retrieve API key from environment
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
if not OPENAI_API_KEY:
    raise ValueError("OPENAI_API_KEY not found in environment variables")

app = fastapi.FastAPI()
oai_client = AsyncOpenAI(api_key=OPENAI_API_KEY)

class Message(BaseModel):
    role: str
    content: str

class ChatCompletionRequest(BaseModel):
    messages: List[Message]
    model: str
    temperature: Optional[float] = 0.7
    max_tokens: Optional[int] = None
    stream: Optional[bool] = False
    user_id: Optional[str] = None

@app.post("/v1/chat/completions")
async def create_chat_completion(request: ChatCompletionRequest) -> StreamingResponse:
    oai_request = request.dict(exclude_none=True)
    if "user_id" in oai_request:
        oai_request["user"] = oai_request.pop("user_id")

    chat_completion_coroutine = await oai_client.chat.completions.create(**oai_request)

    async def event_stream():
        try:
            async for chunk in chat_completion_coroutine:
                # Convert the ChatCompletionChunk to a dictionary before JSON serialization
                chunk_dict = chunk.model_dump()
                yield f"data: {json.dumps(chunk_dict)}\n\n"
            yield "data: [DONE]\n\n"
        except Exception as e:
            logging.error("An error occurred: %s", str(e))
            yield f"data: {json.dumps({'error': 'Internal error occurred!'})}\n\n"

    return StreamingResponse(event_stream(), media_type="text/event-stream")

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8013)
```

#### Responses API

Responses APIは`/v1/responses`エンドポイントを使用します。

各チャンクは`event: {type}\ndata: {json}\n\n`形式で、ストリームは`data: [DONE]\n\n`で終了する必要があります。最低限必要なイベントは次のとおりです：

* `response.output_text.delta` - テキストコンテンツのストリーミング用
* `response.completed` - 完了を通知するため

**`server.py`**

```python title="server.py"
import json
import os
import fastapi
from fastapi.responses import StreamingResponse
from openai import AsyncOpenAI
import uvicorn
import logging
from dotenv import load_dotenv
from pydantic import BaseModel
from typing import List, Optional

load_dotenv()

OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
if not OPENAI_API_KEY:
    raise ValueError("OPENAI_API_KEY not found in environment variables")

app = fastapi.FastAPI()
oai_client = AsyncOpenAI(api_key=OPENAI_API_KEY)

class InputMessage(BaseModel):
    role: str
    content: str

class ResponseCreateRequest(BaseModel):
    model: str
    input: List[InputMessage]
    instructions: Optional[str] = None
    temperature: Optional[float] = 0.7
    max_output_tokens: Optional[int] = None
    stream: Optional[bool] = False

@app.post("/v1/responses")
async def create_response(request: ResponseCreateRequest) -> StreamingResponse:
    input_messages = [{"role": msg.role, "content": msg.content} for msg in request.input]

    response_stream = await oai_client.responses.create(
        model=request.model,
        input=input_messages,
        instructions=request.instructions,
        temperature=request.temperature,
        max_output_tokens=request.max_output_tokens,
        stream=True
    )

    async def event_stream():
        try:
            async for event in response_stream:
                if event.type == "response.output_text.delta":
                    yield f"event: response.output_text.delta\ndata: {json.dumps({'type': 'response.output_text.delta', 'delta': event.delta})}\n\n"
                elif event.type == "response.completed":
                    yield f"event: response.completed\ndata: {json.dumps({'type': 'response.completed', 'response': {'id': event.response.id, 'status': 'completed'}})}\n\n"

            yield "data: [DONE]\n\n"

        except Exception as e:
            logging.error("An error occurred: %s", str(e))
            yield f"event: error\ndata: {json.dumps({'type': 'error', 'error': {'message': str(e)}})}\n\n"

    return StreamingResponse(event_stream(), media_type="text/event-stream")

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8013)

```

**`server.ts`**

```typescript title="server.ts"
import express, { Request, Response } from "express";
import OpenAI from "openai";

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

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

interface InputMessage {
  role: string;
  content: string;
}

interface ResponseCreateRequest {
  model: string;
  input: InputMessage[];
  instructions?: string;
  temperature?: number;
  max_output_tokens?: number;
  stream?: boolean;
}

app.post("/v1/responses", async (req: Request, res: Response) => {
  const request = req.body as ResponseCreateRequest;

  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");

  try {
    const stream = await openai.responses.create({
      model: request.model,
      input: request.input,
      instructions: request.instructions,
      temperature: request.temperature ?? 0.7,
      max_output_tokens: request.max_output_tokens,
      stream: true,
    });

    for await (const event of stream) {
      if (event.type === "response.output_text.delta") {
        res.write(
          `event: response.output_text.delta\ndata: ${JSON.stringify({ type: "response.output_text.delta", delta: event.delta })}\n\n`
        );
      } else if (event.type === "response.completed") {
        res.write(
          `event: response.completed\ndata: ${JSON.stringify({ type: "response.completed", response: { id: event.response.id, status: "completed" } })}\n\n`
        );
      }
    }

    res.write("data: [DONE]\n\n");
    res.end();
  } catch (error) {
    console.error("An error occurred:", error);
    res.write(
      `event: error\ndata: ${JSON.stringify({ type: "error", error: { message: String(error) } })}\n\n`
    );
    res.end();
  }
});

app.listen(8013, () => console.log("Server running on port 8013"));
```

このコード、または独自のサーバーコードを実行します。

![](/docs/_fern-img/8032697eeac9a62bb084feb13ab05294df2fee44f14a97897f0b7c10ffee813c.webp)

### サーバーの公開URLを設定する

サーバーにアクセスできるようにするには、ngrokなどのトンネリングツールを使用して公開URLを作成します：

```shell
ngrok http --url=<Your url>.ngrok.app 8013
```

![](/docs/_fern-img/16d139cc2889d46e026c8aa7578fcdcd8d228dbe0f4df245091ea6de2ceb345a.webp)

### ElevenLabs CustomLLMを設定する

次に、ElevenLabsダッシュボードでエージェント設定を更新して、カスタムLLMサーバーを指定します。

![](/docs/_fern-img/d57d81e44dffb7e6f581a4eed449d887f6c365b8ceb4f4f0174bd7e2ffd62537.webp)

サーバーURLをngrokエンドポイントに指定し、「Limit token usage」を5000に設定します。

これで、独自のLLMサーバーを使用してエージェントとやり取りできます。

## 処理が遅いLLMの最適化

カスタムLLMの処理時間が遅い場合（エージェント型推論や前処理が必要な場合など）、ストリーミングレスポンスに**バッファワード**を実装すると、会話の流れを改善できます。この手法では、LLMが完全なレスポンスを生成している間も、自然な発話のプロソディを維持できます。

### バッファワード

LLMが完全なレスポンスの処理により時間を要する場合は、末尾が`"... "`（省略記号の後にスペース）となる初期レスポンスを返します。これにより、会話の動的な感覚を保ちながら、テキスト読み上げシステムが自然な流れを維持できます。
これにより、LLMがより長く推論できる後続コンテンツへと自然につながる間が生まれます。後続のコンテンツが`"..."`に連結されるとオーディオの歪みにつながる可能性があるため、追加のスペースは重要です。

### 実装

カスタムLLMサーバーを変更してバッファワードを実装する方法は次のとおりです。

**`server.py`**

```python title="server.py"
@app.post("/v1/chat/completions")
async def create_chat_completion(request: ChatCompletionRequest) -> StreamingResponse:
    oai_request = request.dict(exclude_none=True)
    if "user_id" in oai_request:
        oai_request["user"] = oai_request.pop("user_id")

    async def event_stream():
        try:
            # Send initial buffer chunk while processing
            initial_chunk = {
                "id": "chatcmpl-buffer",
                "object": "chat.completion.chunk",
                "created": 1234567890,
                "model": request.model,
                "choices": [{
                    "delta": {"content": "Let me think about that... "},
                    "index": 0,
                    "finish_reason": None
                }]
            }
            yield f"data: {json.dumps(initial_chunk)}\n\n"

            # Process the actual LLM response
            chat_completion_coroutine = await oai_client.chat.completions.create(**oai_request)

            async for chunk in chat_completion_coroutine:
                chunk_dict = chunk.model_dump()
                yield f"data: {json.dumps(chunk_dict)}\n\n"
            yield "data: [DONE]\n\n"

        except Exception as e:
            logging.error("An error occurred: %s", str(e))
            yield f"data: {json.dumps({'error': 'Internal error occurred!'})}\n\n"

    return StreamingResponse(event_stream(), media_type="text/event-stream")

```

**`server.ts`**

```typescript title="server.ts"
app.post("/v1/chat/completions", async (req: Request, res: Response) => {
  const request = req.body as ChatCompletionRequest;
  const oaiRequest = { ...request };

  if (oaiRequest.user_id) {
    oaiRequest.user = oaiRequest.user_id;
    delete oaiRequest.user_id;
  }

  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");

  try {
    // Send initial buffer chunk while processing
    const initialChunk = {
      id: "chatcmpl-buffer",
      object: "chat.completion.chunk",
      created: Math.floor(Date.now() / 1000),
      model: request.model,
      choices: [
        {
          delta: { content: "Let me think about that... " },
          index: 0,
          finish_reason: null,
        },
      ],
    };
    res.write(`data: ${JSON.stringify(initialChunk)}\n\n`);

    // Process the actual LLM response
    const stream = await openai.chat.completions.create({
      ...oaiRequest,
      stream: true,
    });

    for await (const chunk of stream) {
      res.write(`data: ${JSON.stringify(chunk)}\n\n`);
    }

    res.write("data: [DONE]\n\n");
    res.end();
  } catch (error) {
    console.error("An error occurred:", error);
    res.write(`data: ${JSON.stringify({ error: "Internal error occurred!" })}\n\n`);
    res.end();
  }
});
```

## システムツールの統合

カスタムLLMは、[システムツール](/docs/ja/eleven-agents/customization/tools/system-tools)をトリガーして、会話のフローと状態を制御できます。これらのツールは、エージェントで設定すると、チャット完了リクエストの`tools`パラメータに自動的に含まれます。

### システムツールの仕組み

1. **LLMによる判断**：カスタムLLMは会話のコンテキストに基づき、これらのツールを呼び出すタイミングを判断します
2. **ツールレスポンス**：LLMは標準のOpenAI形式で関数呼び出しを返します
3. **バックエンド処理**：ElevenLabsがツール呼び出しを処理し、会話の状態を更新します

システムツールの詳細については、[ガイド](/docs/ja/eleven-agents/customization/tools/system-tools)をご覧ください。

### 利用可能なシステムツール

#### 通話を終了

**目的**：適切な条件が満たされたときに、会話を自動的に終了します。

**トリガー条件**：LLMは次の場合にこのツールを呼び出す必要があります。

* 主なタスクが完了し、ユーザーが満足している
* 双方の合意により会話が自然に終了した
* ユーザーが会話を終了したいと明示的に示している

**パラメータ**：

* `reason`（string、必須）：通話を終了する理由
* `message`（string、任意）：通話終了前にユーザーへ送信する別れのメッセージ

**関数呼び出し形式**：

```json
{
  "type": "function",
  "function": {
    "name": "end_call",
    "arguments": "{\"reason\": \"Task completed successfully\", \"message\": \"Thank you for using our service. Have a great day!\"}"
  }
}
```

**実装**：エージェント設定でシステムツールとして構成します。LLMは、この関数を呼び出すタイミングに関する詳細な指示を受け取ります。

詳細：[通話終了ツール](/docs/ja/eleven-agents/customization/tools/system-tools/end-call)

#### 言語検出

**目的**：会話中に検出されたユーザーの言語へ自動的に切り替えます。

**トリガー条件**：LLMは次の場合にこのツールを呼び出す必要があります。

* ユーザーが現在の会話言語とは異なる言語で話している
* ユーザーが言語の切り替えを明示的にリクエストしている
* 会話に多言語サポートが必要である

**パラメータ**：

* `reason`（string、必須）：言語を切り替える理由
* `language`（string、必須）：切り替え先の言語コード（対応言語リストに含まれている必要があります）

**関数呼び出し形式**：

```json
{
  "type": "function",
  "function": {
    "name": "language_detection",
    "arguments": "{\"reason\": \"User requested Spanish\", \"language\": \"es\"}"
  }
}
```

**実装**：エージェント設定で対応言語を構成し、言語検出システムツールを追加します。エージェントは、検出された言語に合わせて音声とレスポンスを自動的に切り替えます。

詳細：[言語検出ツール](/docs/ja/eleven-agents/customization/tools/system-tools/language-detection)

#### エージェント転送

**目的**：ユーザーのニーズに応じて、専門分野を持つAIエージェント間で会話を転送します。

**トリガー条件**：LLMは次の場合にこのツールを呼び出す必要があります。

* ユーザーのリクエストに専門知識や別のエージェント機能が必要である
* 現在のエージェントではクエリを十分に処理できない
* 会話の流れから別の種類のエージェントが必要であることが示されている

**パラメータ**：

* `reason`（string、任意）：エージェント転送の理由
* `agent_number`（integer、必須）：転送先エージェントのゼロ始まり番号（構成された転送ルールに基づく）

**関数呼び出し形式**：

```json
{
  "type": "function",
  "function": {
    "name": "transfer_to_agent",
    "arguments": "{\"reason\": \"User needs billing support\", \"agent_number\": 0}"
  }
}
```

**実装**：条件を特定のエージェントIDにマッピングする転送ルールを定義します。現在のエージェントが転送できるエージェントを構成します。エージェントは転送設定でゼロ始まりの番号により参照されます。

詳細：[エージェント転送ツール](/docs/ja/eleven-agents/customization/tools/system-tools/agent-transfer)

#### 人間に転送

**目的**：AIの支援が不十分な場合に、会話を人間のオペレーターへシームレスに引き継ぎます。

**トリガー条件**：LLMは次の場合にこのツールを呼び出す必要があります。

* 人間の判断が必要な複雑な問題
* ユーザーが人間の支援を明示的に求めている
* 特定のリクエストに対してAIの能力の限界に達している
* エスカレーションプロトコルがトリガーされている

**パラメータ**：

* `reason`（string、任意）：転送の理由
* `transfer_number`（string、必須）：転送先の電話番号（設定済みの番号と一致する必要があります）
* `client_message`（string、必須）：転送を待つ間にクライアントへ読み上げるメッセージ
* `agent_message`（string、必須）：通話を受ける人間のオペレーター向けメッセージ

**関数呼び出し形式**：

```json
{
  "type": "function",
  "function": {
    "name": "transfer_to_number",
    "arguments": "{\"reason\": \"Complex billing issue\", \"transfer_number\": \"+15551234567\", \"client_message\": \"I'm transferring you to a billing specialist who can help with your account.\", \"agent_message\": \"Customer has a complex billing dispute about order #12345 from last month.\"}"
  }
}
```

**実装**：転送先の電話番号と条件を構成します。顧客と通話を受ける人間のオペレーターの両方に対するメッセージを定義します。TwilioとSIPトランキングの両方に対応しています。

詳細：[人間への転送ツール](/docs/ja/eleven-agents/customization/tools/system-tools/transfer-to-number)

#### ターンをスキップ

**目的**：エージェントが発話せずに、一時停止してユーザー入力を待てるようにします。

**トリガー条件**：LLMは次の場合にこのツールを呼び出す必要があります。

* ユーザーが少し時間が必要だと示している（「少々お待ちください」「考えさせてください」）
* ユーザーが会話フローの一時停止をリクエストしている
* エージェントがユーザーに情報を処理する時間が必要だと検出している

**パラメータ**：

* `reason`（string、任意）：一時停止が必要な理由を説明する自由形式の理由

**関数呼び出し形式**：

```json
{
  "type": "function",
  "function": {
    "name": "skip_turn",
    "arguments": "{\"reason\": \"User requested time to think\"}"
  }
}
```

**実装**：追加の構成は必要ありません。このツールは、ユーザーが再び話すまでエージェントが沈黙を保つよう通知するだけです。

詳細：[ターンスキップツール](/docs/ja/eleven-agents/customization/tools/system-tools/skip-turn)

#### 留守番電話検出

**パラメータ**：

* `reason`（文字列、必須）：留守番電話を検出した理由（例：「自動応答メッセージを検出」、「人間からの応答なし」）

**関数呼び出し形式**：

```json
{
  "type": "function",
  "function": {
    "name": "voicemail_detection",
    "arguments": "{\"reason\": \"Automated greeting detected with request to leave message\"}"
  }
}
```

詳細：[留守番電話検出ツール](/docs/ja/eleven-agents/customization/tools/system-tools/voicemail-detection)

### システムツールを含むリクエスト例

システムツールを構成すると、カスタムLLMは標準のOpenAI形式でツールを含むリクエストを受け取ります。

```json
{
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant. You have access to system tools for managing conversations."
    },
    {
      "role": "user",
      "content": "I think we're done here, thanks for your help!"
    }
  ],
  "model": "your-custom-model",
  "temperature": 0.7,
  "max_tokens": 1000,
  "stream": true,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "end_call",
        "description": "Call this function to end the current conversation when the main task has been completed...",
        "parameters": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "description": "The reason for the tool call."
            },
            "message": {
              "type": "string",
              "description": "A farewell message to send to the user along right before ending the call."
            }
          },
          "required": ["reason"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "language_detection",
        "description": "Change the conversation language when the user expresses a language preference explicitly...",
        "parameters": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "description": "The reason for the tool call."
            },
            "language": {
              "type": "string",
              "description": "The language to switch to. Must be one of language codes in tool description."
            }
          },
          "required": ["reason", "language"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "skip_turn",
        "description": "Skip a turn when the user explicitly indicates they need a moment to think...",
        "parameters": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "description": "Optional free-form reason explaining why the pause is needed."
            }
          },
          "required": []
        }
      }
    }
  ]
}
```

> **Note**
>
> システムツールを使用するには、カスタムLLMが関数呼び出しに対応している必要があります。モデルがOpenAI形式で
> 適切な関数呼び出しレスポンスを生成できることを確認してください。

# 追加機能

#### カスタムLLMパラメータ

カスタムLLM実装に追加パラメータを渡すことができます。

#### Python

#### 追加パラメータを定義

カスタムパラメータを含むオブジェクトを作成します。

```python
from elevenlabs.conversational_ai.conversation import Conversation, ConversationInitiationData

extra_body_for_convai = {
    "UUID": "123e4567-e89b-12d3-a456-426614174000",
    "parameter-1": "value-1",
    "parameter-2": "value-2",
}

config = ConversationInitiationData(
    extra_body=extra_body_for_convai,
)
```

#### LLM実装を更新

追加パラメータを処理できるように、カスタムLLMコードを変更します。

```python
import json
import os
import fastapi
from fastapi.responses import StreamingResponse
from fastapi import Request
from openai import AsyncOpenAI
import uvicorn
import logging
from dotenv import load_dotenv
from pydantic import BaseModel
from typing import List, Optional

# Load environment variables from .env file
load_dotenv()

# Retrieve API key from environment
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
if not OPENAI_API_KEY:
    raise ValueError("OPENAI_API_KEY not found in environment variables")

app = fastapi.FastAPI()
oai_client = AsyncOpenAI(api_key=OPENAI_API_KEY)

class Message(BaseModel):
    role: str
    content: str

class ChatCompletionRequest(BaseModel):
    messages: List[Message]
    model: str
    temperature: Optional[float] = 0.7
    max_tokens: Optional[int] = None
    stream: Optional[bool] = False
    user_id: Optional[str] = None
    elevenlabs_extra_body: Optional[dict] = None

@app.post("/v1/chat/completions")
async def create_chat_completion(request: ChatCompletionRequest) -> StreamingResponse:
    oai_request = request.dict(exclude_none=True)
    print(oai_request)
    if "user_id" in oai_request:
        oai_request["user"] = oai_request.pop("user_id")

    if "elevenlabs_extra_body" in oai_request:
        oai_request.pop("elevenlabs_extra_body")

    chat_completion_coroutine = await oai_client.chat.completions.create(**oai_request)

    async def event_stream():
        try:
            async for chunk in chat_completion_coroutine:
                chunk_dict = chunk.model_dump()
                yield f"data: {json.dumps(chunk_dict)}\n\n"
            yield "data: [DONE]\n\n"
        except Exception as e:
            logging.error("An error occurred: %s", str(e))
            yield f"data: {json.dumps({'error': 'Internal error occurred!'})}\n\n"

    return StreamingResponse(event_stream(), media_type="text/event-stream")

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8013)
```

### リクエスト例

このカスタムメッセージ設定では、LLMは次の形式でリクエストを受け取ります。

```json
{
  "messages": [
    {
      "role": "system",
      "content": "\n  <Redacted>"
    },
    {
      "role": "assistant",
      "content": "Hey I'm currently unavailable."
    },
    {
      "role": "user",
      "content": "Hey, who are you?"
    }
  ],
  "model": "gpt-4o",
  "temperature": 0.5,
  "max_tokens": 5000,
  "stream": true,
  "elevenlabs_extra_body": {
    "UUID": "123e4567-e89b-12d3-a456-426614174000",
    "parameter-1": "value-1",
    "parameter-2": "value-2"
  }
}
```