レスポンスとストリーミング

6.1 リクエスト

POST /lmsp/v1/responses
Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: 01J8M5T7Z2K4N6Q8S0V3X5B9DG
LMSP-Version: 1.0
{
  "model": "qwen3:14b-q4_k_m",
  "input": [
    {
      "type": "message",
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "Describe a local model service in one sentence."
        }
      ]
    }
  ],
  "stream": false,
  "max_output_tokens": 512,
  "temperature": 0.7,
  "response_format": {
    "type": "text"
  },
  "store": true
}

コアプロトコルは、メッセージ文字列の単純なリストではなく、Responses 形式の input と output の構造を使う。これにより、1 つのリクエスト形式で次のものを運べる。

  • 複数ターンのメッセージ
  • テキスト、画像、音声、ファイル
  • ツール呼び出しとツール結果
  • 構造化出力
  • 推論の要約
  • マルチモーダル出力

LMSP 1.0 が定義するのはテキストコンテンツだけである。画像、音声、ファイルのコンテンツタイプは、将来のバージョンのために予約されている。

フィールド 必須 意味
model はい 使用するモデルの ID。
input はい 入力アイテムの配列。message アイテムは role(system、user、assistant)と、input_text などのパートからなる content 配列を持つ。ツールを使う場合は function_call アイテムと function_call_output アイテムが加わる(6.5 節)。
stream いいえ true にするとサーバー送信イベントを受け取る。既定は false。
max_output_tokens いいえ 生成するトークン数の上限。
temperature いいえ サンプリング温度。
response_format いいえ {"type": "text"}(既定)、または構造化出力のための {"type": "json_schema", "json_schema": {"name": "...", "schema": {...}}}。structured_output 機能が必要。
tools, tool_choice いいえ モデルが呼び出せる関数ツール(6.5 節)。tools 機能が必要。
store いいえ サービスにレスポンスを保存するよう求める。これにより、後で取得したり、previous_response_id として使ったりできる。既定は false。conversations 機能を持たないサービスは無視する。
previous_response_id いいえ 保存済みのレスポンスから続ける。conversations 機能が必要。
extensions いいえ 非公開の拡張(9.1 節)。

モデルが持たない機能を必要とするリクエストは、400 と unsupported_capability で拒否される。コンテキストウィンドウに収まらないリクエストは、400 と context_length_exceeded で拒否される。

会話を保存できるサービスは、previous_response_id にも対応してもよい。そうでない場合、すべてのリクエストが完全なコンテキストを携えなければならない。サービスは、会話の状態を保持するために HTTP 接続に依存してはならない。

6.2 同期レスポンス

stream が false のとき、サービスは生成の完了後に、レスポンスオブジェクトを付けて 200 OK を返す。

{
  "id": "resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF",
  "object": "response",
  "created_at": 1794200000,
  "status": "completed",
  "model": "qwen3:14b-q4_k_m",
  "output": [
    {
      "id": "msg_01J8M6Q4W5C8H2K9P3S7Y1A6ZE",
      "type": "message",
      "role": "assistant",
      "content": [
        {
          "type": "output_text",
          "text": "A local model service is a resident interface layer that discovers, manages and calls models on the user's own device."
        }
      ]
    }
  ],
  "usage": {
    "input_tokens": 28,
    "output_tokens": 31,
    "total_tokens": 59
  },
  "error": null,
  "incomplete_details": null
}
フィールド 意味
id レスポンス識別子。例: resp_...。
object 常に "response"。
created_at 作成時刻。Unix エポックからの秒数。
status in_progress、completed、incomplete、failed、cancelled。同期レスポンスは最終ステータスで返される。
model レスポンスを生成したモデルの ID。
output 出力アイテムの配列。
usage トークン数: input_tokens、output_tokens、total_tokens。
error null。status が failed の場合は problem オブジェクト(9.3 節)。
incomplete_details null。status が incomplete の場合は、max_output_tokens などの reason を持つオブジェクト。

出力アイテムの種類:

アイテムの種類 形
message id、role(assistant)、content。content は、それぞれ text を持つ output_text パートの配列。
function_call id、call_id、name、arguments。arguments は JSON エンコードされた文字列。
reasoning id と summary。summary は、それぞれ text を持つ summary_text パートの配列。

生成に失敗した場合、同期リクエストへの応答は、レスポンスオブジェクトではなく、エラーステータスと problem ドキュメント(9.3 節)になる。failed ステータスが現れるのは、ストリーム、タスク、保存済みレスポンスである。

GET /lmsp/v1/responses/{response_id} は、store: true で保存されたレスポンスを返す。これは任意であり、conversations 機能が必要である。未知の ID では 404 と response_not_found を返す。

6.3 ストリーミング

ストリーミングするには、リクエストで stream を設定する。

{
  "stream": true
}

サービスは Content-Type: text/event-stream を付けて 200 OK で応答し、意味を持つイベントを送る。各イベントは id、event 名、JSON の data オブジェクトを持つ。

id: 1
event: response.created
data: {"response_id":"resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF","status":"in_progress"}

id: 2
event: response.output_item.added
data: {"response_id":"resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF","output_index":0,"item":{"id":"msg_01J8M6Q4W5C8H2K9P3S7Y1A6ZE","type":"message","role":"assistant","content":[]}}

id: 3
event: response.output_text.delta
data: {"response_id":"resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF","item_id":"msg_01J8M6Q4W5C8H2K9P3S7Y1A6ZE","delta":"A local model "}

id: 4
event: response.output_text.delta
data: {"response_id":"resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF","item_id":"msg_01J8M6Q4W5C8H2K9P3S7Y1A6ZE","delta":"service is a resident interface layer."}

id: 5
event: response.output_item.done
data: {"response_id":"resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF","output_index":0,"item":{"id":"msg_01J8M6Q4W5C8H2K9P3S7Y1A6ZE","type":"message","role":"assistant","content":[{"type":"output_text","text":"A local model service is a resident interface layer."}]}}

id: 6
event: response.completed
data: {"response_id":"resp_01J8M6Q4W2T7N5R3V9X1Z8B0DF","status":"completed","usage":{"input_tokens":28,"output_tokens":12,"total_tokens":40}}

少なくとも次のイベントが定義されている。

イベント 意味 データフィールド
response.created レスポンスが作成された。 response_id、status(in_progress)
response.output_item.added 新しい出力アイテムが始まった。 response_id、output_index、item
response.output_text.delta message アイテムにテキストが追加された。 response_id、item_id、delta
response.output_item.done 出力アイテムが完了した。 response_id、output_index、item
response.completed レスポンスが完了した。最終イベント。 response_id、status、usage
response.incomplete 生成が早めに止まった。たとえばトークン上限による。最終イベント。 response_id、status、incomplete_details、usage
response.failed 生成に失敗した。最終イベント。 response_id、status、error
response.cancelled レスポンスがキャンセルされた。最終イベント。 response_id、status
error 1 つのレスポンスに結び付かない問題。この後ストリームは継続できない。 error(problem オブジェクト)

サービスは、最終イベントまたは error イベントの後にストリームを閉じる。

サービスは、たとえばツール呼び出しの引数、画像、音声、構造化 JSON の差分などのために、追加のイベントを送ってもよい。クライアントは、知らないイベントタイプを無視しなければならない。

ストリーミングリクエストの接続をクライアントが閉じた場合、サービスは生成を停止するべきである。

6.4 イベント ID と再接続

イベントの id は、レスポンス内で厳密に増加する、1 から始まる整数のシーケンス番号でなければならない。

接続が切れた後、クライアントは、受信した最後の ID を送ることで再開する。ヘッダーで送る方法:

Last-Event-ID: 2

クエリパラメーターで送る方法:

GET /lmsp/v1/tasks/{task_id}/events?after=2

どちらも、タスクのイベントエンドポイント(7.4 節)に送る。サービスは、シーケンス番号が 2 より大きいイベントだけを再送する。両方が指定された場合は after が優先される。

要求されたイベントが保持期間より古い場合、サービスは 410 Gone とエラー event_history_expired で応答する。クライアントは代わりに、GET /lmsp/v1/tasks/{task_id} で最終結果を取得する。

POST /lmsp/v1/responses から直接ストリーミングされたレスポンスは、その HTTP 接続に属しており、再開できない。再開可能な出力が必要なクライアントは、タスクを作成してそのイベントを追うべきである。

6.5 ツール呼び出し

LMSP 1.0 は、クライアントが実行する関数ツールを定義する。サービス自身はツールを実行しない。tools:execute スコープは、ツールを実行する拡張のために予約されている(10.3 節)。

まず、クライアントがツールを列挙する。各エントリーは、function に設定された type、name、description、そして JSON Schema オブジェクトである parameters を持つ。tool_choice は auto(既定)、none、required、または {"type": "function", "name": "get_weather"} である。

{
  "model": "qwen3:14b-q4_k_m",
  "input": [
    {
      "type": "message",
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "What is the weather in Hangzhou?"
        }
      ]
    }
  ],
  "tools": [
    {
      "type": "function",
      "name": "get_weather",
      "description": "Get the current weather for a city.",
      "parameters": {
        "type": "object",
        "properties": {
          "city": { "type": "string" }
        },
        "required": ["city"]
      }
    }
  ],
  "tool_choice": "auto"
}

モデルがツールを呼び出すと決めると、レスポンスには function_call アイテムが含まれる。その arguments は JSON エンコードされた文字列である。ストリームでは、アイテムは response.output_item.done で完全な形で届く。

{
  "id": "resp_01J8M7B3N6Q9T2V5X8Z1C4D7FH",
  "object": "response",
  "created_at": 1794200050,
  "status": "completed",
  "model": "qwen3:14b-q4_k_m",
  "output": [
    {
      "id": "fc_01J8M7B3N8R1S4W7Y0A3E6G9JK",
      "type": "function_call",
      "call_id": "call_01J8M7B3N9T2V5X8Z1C4D7F0HM",
      "name": "get_weather",
      "arguments": "{\"city\":\"Hangzhou\"}"
    }
  ],
  "usage": {
    "input_tokens": 74,
    "output_tokens": 18,
    "total_tokens": 92
  },
  "error": null,
  "incomplete_details": null
}

クライアントはツールを実行し、完全なコンテキストを携えた新しいリクエストを送る。そこには、以前の入力アイテム、受け取ったままの function_call アイテム、同じ call_id を持つ function_call_output アイテムが含まれる。その output は文字列で、通常は JSON エンコードされる。

{
  "type": "function_call_output",
  "call_id": "call_01J8M7B3N9T2V5X8Z1C4D7F0HM",
  "output": "{\"temperature_c\":17,\"condition\":\"light rain\"}"
}