> For the complete documentation index, see [llms.txt](https://docs.maiagent.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.maiagent.ai/tech/ja/api-integration/dui-hua-yu-xun-xi-hui-fu-chuan-liu-tong-bu.md).

# 対話とメッセージへの返信（ストリーミング／同期）

completions API による Agent の応答取得、ストリーミング（SSE）のイベントフィールド、reset の処理と終了条件

Agent にメッセージを送信し、応答を取得します。`isStreaming` が `false` の場合は、応答の生成が完了してから JSON を一括で返します。`true` の場合は、Server-Sent Events（SSE）で生成しながら返します。

```bash
curl -N -X POST \
  'https://api.maiagent.ai/api/v1/chatbots/{chatbot_id}/completions/' \
  -H 'Authorization: Api-Key YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "conversation": null,
    "message": {
      "content": "こんにちは、自己紹介をしてください"
    },
    "isStreaming": true
  }'
```

**パラメータ：**

* `chatbot_id`（パスパラメータ）：Agent ID
* `conversation`（リクエストボディ）：対話 ID。`null` を指定するか省略すると新しい対話を作成し、既存の ID を指定するとその対話を継続します
* `message.content`（リクエストボディ）：送信するメッセージの内容です
* `message.attachments`（リクエストボディ）：添付ファイルの配列（任意）です
* `isStreaming`（リクエストボディ）：ストリーミング応答を使用するかどうかを指定します。デフォルトは `false` です（`is_streaming` も使用できます）

その他のリクエストフィールド（`queryMetadata`、`toolIds`、`waitForAttachments` など）は、API ドキュメントサイトの[対話とメッセージ](https://docs.maiagent.ai/api/api-reference/dui-hua-he-xun-xi)をご覧ください。

**応答（非ストリーミング）：**

```json
{
  "conversationId": "string",
  "content": "応答の全文",
  "done": true,
  "reset": false,
  "citations": null,
  "citationNodes": null,
  "contentPayload": {
    "content": "応答の全文",
    "items": [{ "type": "text", "text": "応答の全文" }],
    "metadata": null
  },
  "messageId": "string"
}
```

各フィールドの意味は、以下のストリーミングイベントと同じです。

## ストリーミング応答の形式 <a href="#streaming-format" id="streaming-format"></a>

`isStreaming: true` の場合、応答の `Content-Type` は `text/event-stream` です。各イベントは `data: <JSON>` の 1 行で、イベント間は空行で区切られます。

```
data: {"conversationId": "c6a4…", "content": "こんにちは", "done": false, "reset": false, "citations": null, "citationNodes": null, "contentPayload": {"content": "こんにちは", "items": [{"type": "text", "text": "こんにちは", …}], "metadata": null}, "messageId": null}

data: {"conversationId": "c6a4…", "content": "、私は", "done": false, "reset": false, …, "messageId": null}

data: {"conversationId": "c6a4…", "content": "", "done": true, "reset": false, "citations": […], "citationNodes": […], "contentPayload": {}, "messageId": "9f1e…"}
```

### イベントのフィールド <a href="#frame-fields" id="frame-fields"></a>

すべてのフィールド名は camelCase です。

| フィールド            | 型                 | 説明                                                                                                                                                                    |
| ---------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `conversationId` | string            | 対話 ID です。新しい対話の作成時にここから取得し、以降のリクエストで `conversation` に指定すると対話を継続できます                                                                                                   |
| `content`        | string            | このイベントで**追加**されるテキストの断片です（差分であり、累積した全文ではありません）。思考過程、ツール呼び出し、引用、ハートビートのイベントでは `content` は空文字列です                                                                        |
| `done`           | boolean           | `true` は最後のイベントであることを示し、その後サーバーが接続を閉じます                                                                                                                               |
| `reset`          | boolean           | `true` の場合、このイベントの `content` で表示中のテキストを**すべて置き換えます**。[reset が発生するタイミング](#reset)をご覧ください。一部のイベントにはこのフィールドがなく、その場合は `false` として扱います                                      |
| `citations`      | array または `null`  | 応答で引用したナレッジベースのファイルです。`done: true` のイベントでのみ値が入り、Agent の API 対話プラットフォームで引用表示を有効にする必要があります。それ以外は `null` です                                                              |
| `citationNodes`  | array または `null`  | 応答で引用した段落です。`done: true` のイベントに値が入ります。インライン引用が有効な場合、検索完了後に `content` が空で `citationNodes` のみを含むイベントを先に送ることもあります                                                        |
| `contentPayload` | object            | 構造化された内容です。`items` 配列の各項目は `type` で区別され、`text`、`thinking`（思考過程の表示を有効にする必要があります）、`tool_use`、`tool_result`、`client_tool_call` などがあります。テキストのみが必要な連携では、このフィールドを省略して処理できます |
| `messageId`      | string または `null` | AI の応答メッセージの ID です。応答の保存後に値が入るため、ストリーミング中は `null` で、通常は `done: true` のイベントでのみ取得できます                                                                                   |

### フロントエンドの処理ルール <a href="#client-handling" id="client-handling"></a>

各イベントを順番に処理します。

1. `reset` が `true` の場合、このイベントの `content` で現在の累積テキストを置き換えます（`content` が空文字列の場合はテキストを消去します）。
2. その他のイベントでは、`content` を累積テキストの末尾に追加します。`content` が空でもそのまま追加でき、特別な判定は不要です。
3. `done` が `true` の場合、このイベントを処理してから終了し、`messageId`、`citations`、`citationNodes` を取得します。

`done: true` のイベントでは、テキストは前のイベントですでに送信済みのため、通常 `content` は空文字列です。ただし、応答全体で断片が一度も送信されなかった場合（入力がブロックされた場合など）は、`content` に応答全文が入ります。Hook が応答を書き換えた場合は、`reset: true` と全文が含まれます。上記のルールで処理すれば、どちらの場合も正しい結果になります。

### reset が発生するタイミング <a href="#reset" id="reset"></a>

* **出力 Hook が応答を書き換えたりブロックした場合**：最後のイベントは `done: true`、`reset: true` となり、`content` には書き換え後の全文またはブロックの通知が入ります。フロントエンドが `content` を末尾に追加するだけだと、すでに送信された元の内容（ブロックされた内容も含みます）が画面に残ります。
* **モデルが応答を再生成する場合**：ストリーミング中に `reset: true`、`content` が空のイベントを送信し、表示中のテキストを消去します。以降のイベントには再生成された内容が入ります（モデルの出力に異常があり、エラー説明に切り替える場合など）。

{% hint style="warning" %}
`reset` は必ず処理してください。`content` を末尾に追加するだけの連携では、Hook が応答をブロックした際に、表示してはいけない内容がユーザーの画面に残ります。
{% endhint %}

### ハートビート <a href="#heartbeat" id="heartbeat"></a>

ストリーミング中は、サーバーが 15 秒ごとに `content` が空で `done: false` のイベントを送信し、中継プロキシやロードバランサーが接続をアイドル状態と判断して切断するのを防ぎます。処理ルールに従い、空文字列をそのまま追加してください。

### 終了条件とエラー <a href="#end-and-errors" id="end-and-errors"></a>

* **正常終了**：`done: true` のイベントを受信すると、サーバーが接続を閉じます。
* **応答生成中のエラーやタイムアウト**：多くの場合、エラーやタイムアウトの説明が応答テキストとして送信され、最後は `done: true` で終了します。処理ルールに従えば正しく表示できます。
* **予期しないエラー**：サーバーは `event: error` イベントを送信してから接続を閉じ、`done: true` は**送信しません**。

  ```
  event: error
  data: {"error": "Internal server error", "conversationId": "c6a4…"}
  ```
* **接続が途中で切断された場合**：`done: true` または `event: error` を受信する前に切断された場合は、未完了として扱ってください。サーバー側では応答の生成と保存を完了します。後で `GET /api/v1/messages/?conversation={conversationId}` を使って対話のメッセージを取得し、結果を確認できます。

### 逐次ストリーミングにならない場合 <a href="#non-streaming-cases" id="non-streaming-cases"></a>

`isStreaming` が `true` でも、以下の場合は応答が逐次 SSE になりません。

| 状況                                          | 応答                                                                                                                                                                                            |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| リクエスト検証の失敗や Credits 不足などにより、応答生成前にブロックされた場合 | 通常の JSON エラー応答と対応する 4xx ステータスコードを返します。SSE ではありません                                                                                                                                             |
| 添付ファイルのインデックス作成中の場合                         | HTTP `202` を返し、JSON には `status: "pending_attachments"`、`conversationId`、`message`、`pendingAttachments`（未完了の添付ファイル ID）が含まれます。SSE ではありません                                                       |
| 対話が有人サポートに引き継がれた場合                          | SSE は `done: true` のイベント 1 件のみです。`content` は待機または転送の通知、`messageId` は `null` で、さらに `progressStatus` が含まれます。[外部カスタマーサポートシステムとの連携](/tech/ja/serve/customer-service-system-integration.md)をご覧ください |
| Agent が JSON 構造化出力に設定されている場合                | 応答全体を先に生成し、SSE は `done: true` のイベント 1 件のみで、`content` に全文が入ります                                                                                                                                 |

### サンプルコード <a href="#examples" id="examples"></a>

以下の例では応答テキストを累積し、`reset` と `event: error` を処理して、`done: true` の時点で結果を返します。ご自身のバックエンドで実行し、ブラウザーのフロントエンドに API Key を置かないでください。

{% tabs %}
{% tab title="Python" %}
`requests` が必要です（`pip install requests`）。

```python
import json

import requests

API_KEY = "YOUR_API_KEY"
CHATBOT_ID = "YOUR_CHATBOT_ID"


def stream_reply(content, conversation_id=None):
    response = requests.post(
        f"https://api.maiagent.ai/api/v1/chatbots/{CHATBOT_ID}/completions/",
        headers={"Authorization": f"Api-Key {API_KEY}"},
        json={
            "conversation": conversation_id,
            "message": {"content": content},
            "isStreaming": True,
        },
        stream=True,
        timeout=(10, 300),
    )
    response.raise_for_status()
    if response.status_code == 202:
        raise RuntimeError(f"添付ファイルのインデックスは未作成です：{response.json()}")

    text = ""
    event = "message"
    # 応答に charset の指定がないため、各行を bytes として読み、UTF-8 でデコードします
    for raw_line in response.iter_lines(chunk_size=None):
        line = raw_line.decode("utf-8")
        if not line:
            event = "message"
            continue
        if line.startswith("event:"):
            event = line[len("event:"):].strip()
            continue
        if not line.startswith("data:"):
            continue

        frame = json.loads(line[len("data:"):])
        if event == "error":
            raise RuntimeError(f"ストリーミングエラー：{frame['error']}（対話 {frame['conversationId']}）")

        if frame.get("reset"):
            text = frame["content"]
        else:
            text += frame["content"]

        if frame["done"]:
            return {
                "conversation_id": frame["conversationId"],
                "message_id": frame.get("messageId"),
                "text": text,
                "citations": frame.get("citations"),
            }

    raise RuntimeError("done を受信する前に接続が切断されました")


result = stream_reply("こんにちは、自己紹介をしてください")
print(result["text"])
```

{% endtab %}

{% tab title="JavaScript（Node.js 18+）" %}
組み込みの `fetch` を使用するため、パッケージのインストールは不要です。

```javascript
const API_KEY = "YOUR_API_KEY";
const CHATBOT_ID = "YOUR_CHATBOT_ID";

async function streamReply(content, conversationId = null, onUpdate = () => {}) {
  const response = await fetch(
    `https://api.maiagent.ai/api/v1/chatbots/${CHATBOT_ID}/completions/`,
    {
      method: "POST",
      headers: {
        Authorization: `Api-Key ${API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        conversation: conversationId,
        message: { content },
        isStreaming: true,
      }),
    },
  );
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  }
  if (response.status === 202) {
    throw new Error(`添付ファイルのインデックスは未作成です：${await response.text()}`);
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder("utf-8");
  let buffer = "";
  let text = "";

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });

    let boundary;
    while ((boundary = buffer.indexOf("\n\n")) !== -1) {
      const rawEvent = buffer.slice(0, boundary);
      buffer = buffer.slice(boundary + 2);

      let eventName = "message";
      let data = "";
      for (const line of rawEvent.split("\n")) {
        if (line.startsWith("event:")) eventName = line.slice(6).trim();
        else if (line.startsWith("data:")) data += line.slice(5).trim();
      }
      if (!data) continue;

      const frame = JSON.parse(data);
      if (eventName === "error") {
        throw new Error(`ストリーミングエラー：${frame.error}（対話 ${frame.conversationId}）`);
      }

      // reset が true の場合は全文を置き換え、それ以外は末尾に追加します
      text = frame.reset ? frame.content : text + frame.content;
      onUpdate(text);

      if (frame.done) {
        return {
          conversationId: frame.conversationId,
          messageId: frame.messageId,
          text,
          citations: frame.citations,
        };
      }
    }
  }
  throw new Error("done を受信する前に接続が切断されました");
}

streamReply("こんにちは、自己紹介をしてください").then((result) => console.log(result.text));
```

`onUpdate` はイベントを受信するたびに現在の全文を受け取るため、画面の更新に使用できます。
{% endtab %}
{% endtabs %}

注意事項：

* 応答の `conversationId` を以降のリクエストの `conversation` に指定すると、同じ対話を継続できます
* ストリーミング中に接続が切断されても、サーバー側の応答はキャンセルされません。「再試行」のために同じメッセージをすぐに再送すると、対話内に重複メッセージが作成されるため、再送しないでください

## ツール呼び出し中のイベント <a href="#tool-call-frames" id="tool-call-frames"></a>

Agent モードでは、モデルが回答前にツールを呼び出すことがあります。その場合、ストリーミングと同期応答は次のようになります。

* **ストリーミング**：ツール呼び出しは `contentPayload.items` の `tool_use`、`tool_result` 項目として送信され、これらのイベントの `content` は空文字列です。モデルがツール呼び出しの前後に生成したテキストは、`content` の差分として送信されます。[フロントエンドの処理ルール](#client-handling)に従って `content` を追加すると、応答全文を取得できます。
* **同期**（`isStreaming: false`）：`content` はツール呼び出し前後のテキストを含む応答全文です。ツール呼び出しの過程は `contentPayload.items` に発生順で記録されます。
* 「最後の部分だけを返す」パラメータはありません。最後の回答のみを表示する場合は、`contentPayload.items` の最後の `type: "text"` 項目を取得してください。
* `done: true` は常に、すべてのツール呼び出しを含むターン全体の終了を表します。ツール呼び出しの途中には現れません。

ツール呼び出しのラウンド数上限とタイムアウトは Agent の詳細設定で決まります。[Function Calling：実行上限](/tech/ja/advanced-genai-tech/function-calling.md#limits)をご覧ください。

## API で Code Interpreter を使用する <a href="#code-interpreter" id="code-interpreter"></a>

Code Interpreter はツール ID ではないため、`message.toolIds` に指定しないでください。Agent の設定として、管理画面で有効にする（ユーザーマニュアルの [Code Interpreter](https://docs.maiagent.ai/build/agent/code-interpreter#what-do-i-need-to-do) を参照）か、`PATCH /api/v1/chatbots/{chatbotId}/` で `enableCodeInterpreter` を `true` に設定します。Agent の `replyMode` は `agent` にする必要があります。設定後、この Agent への `completions` リクエストに追加フィールドは不要で、モデルがコードを実行するタイミングを判断します。Canvas や Agent Teams は前提条件ではありません。

生成されたファイルは、応答内に添付ファイルオブジェクトとして含まれません。取得方法は[対話の生成物一覧 API](/tech/ja/api-integration/endpoint-behaviors/conversation-outputs-api.md)をご覧ください。`/api/v1/chatbots/{chatbotId}/completions/` は Agent の対話エンドポイントです。名前が似ている `/api/v1/chat/completions/` は [AI Gateway](/tech/ja/ai-gateway/ai-gateway-openai-compatible.md) の OpenAI 互換エンドポイントで、Agent の設定を経由せずモデルを直接呼び出すため、Code Interpreter も提供しません。

## `message.sender` を省略した場合のユーザー識別 <a href="#anonymous-sender" id="anonymous-sender"></a>

`message.sender` に連絡先 ID を指定すると、「誰が質問しているか」を指定できます。省略した場合も匿名にはならず、次のように扱われます。

* **新しい対話を作成する場合**（`conversation` が `null`）、対話の連絡先は、**API キーに紐づくメンバー**の、この対話プラットフォーム上の連絡先になります（初回呼び出し時に自動作成され、`sourceId` はそのメンバーのユーザー ID です）。同じキーで `sender` を省略して作成した対話は、すべてこの連絡先に紐づきます。対話履歴、永続メモリ、連絡先の `queryMetadata`、ツール認証情報はこの連絡先に基づき、使用量と Credits もこのメンバーに帰属します。
* **既存の対話を継続する場合**（`conversation` を指定）、元の連絡先が引き継がれ、`sender` では変更されません。

エンドユーザーごとに独立した対話履歴と権限を持たせるには、先に連絡先を作成し、毎回のリクエストに `sender` を指定してください。[連絡先（Contact）の概要と連携](/tech/ja/serve/contacts.md)をご覧ください。

## コンテンツの長さとコンテキスト <a href="#content-limits" id="content-limits"></a>

* `message.content` は文字列で、プラットフォームは追加の文字数上限を設定していません。添付ファイルがある場合は空文字列にできますが、添付ファイルがない場合は空にできません（`400` を返します）。
* 実際の上限はモデルのコンテキストウィンドウです。このメッセージ、対話履歴、検索されたナレッジベースの段落、添付ファイルの解析後のテキスト、ロール指示がすべて含まれます。上限を超えるとモデル側がエラーを返し、プラットフォームはエラー説明を応答テキストとして送信して `done: true` で終了します（[終了条件とエラー](#end-and-errors)を参照）。`413` は返しません。
* 長文は添付ファイルとしてアップロードするか、複数のメッセージに分割してください。応答の長さは Agent の「応答の長さと Agent の実行上限」設定で制御されます。ユーザーマニュアルの[応答の長さと Agent の実行上限](https://docs.maiagent.ai/build/agent/custom-max-tokens)をご覧ください。

## 対話 ID の有効期間とエラー <a href="#conversation-lifecycle" id="conversation-lifecycle"></a>

* 対話には有効期限もターン数の上限もありません。削除されない限り、`conversationId` を使って継続できます。対話の添付ファイルには別途保存期間があり、期限後にファイルが削除されます。[対話の添付ファイル管理 API](/tech/ja/api-integration/endpoint-behaviors/conversation-attachments.md)をご覧ください。
* `conversation` に存在しない ID または削除済みの ID を指定すると、`400` とフィールドエラー `{"conversation": ["Invalid pk \"…\" - object does not exist."]}` を返します。このキーの組織に属していない対話も拒否されます。このエラーを受け取った場合は、同じ ID で再試行せず、`null` で新しい対話を作成してください。
* 継続する対話が有人サポートに引き継がれている場合、通常の AI 応答にはなりません。[逐次ストリーミングにならない場合](#non-streaming-cases)をご覧ください。

## 人間の返信を対話に書き込む <a href="#outgoing-message" id="outgoing-message"></a>

自社のカスタマーサポートシステムから人間の返信を MaiAgent の対話に書き込む場合は、AI に質問する Completions エンドポイントではなく、`POST /api/v1/messages/outgoing/` を使用します。必要な権限、フィールド、呼び出し例、有人対応への引き継ぎライフサイクルは、[外部カスタマーサポートシステムとの連携：連携フロー](/tech/ja/serve/customer-service-system-integration.md#scenario-b-flow)をご覧ください。

## 連携結果

{% embed url="<https://drive.google.com/file/d/1sCIoxxASZ-SnHPAmkjDT270B3dz0LyHo/view?usp=sharing>" %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

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

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.maiagent.ai/tech/ja/api-integration/dui-hua-yu-xun-xi-hui-fu-chuan-liu-tong-bu.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
