# 응답과 스트리밍

> LMSP의 핵심 대화 인터페이스. Responses 형식의 요청과 출력 항목, 동기 생성과 스트리밍 생성, 서버 전송 이벤트 유형, 이벤트 id, 재연결, 도구 호출을 정의합니다.

Source: https://madeforagent.com/ko/lmsp/1.0/responses/
Language: ko
Specification: LMSP 1.0 (draft)
Updated: 2026-11-08

## 6.1 요청 {#request}

```http
POST /lmsp/v1/responses
Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: 01J8M5T7Z2K4N6Q8S0V3X5B9DG
LMSP-Version: 1.0
```

```json
{
  "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` 구조를 사용하므로, 하나의 요청 형태로 다음을 담을 수 있다.

- 여러 턴의 메시지
- 텍스트, 이미지, 오디오, 파일
- 도구 호출과 도구 결과
- 구조화된 출력
- 추론 요약
- 멀티모달 출력

LMSP 1.0은 텍스트 콘텐츠만 정의한다. 이미지, 오디오, 파일 콘텐츠 유형은 이후 버전을 위해 예약되어 있다.

| 필드 | 필수 | 의미 |
| --- | --- | --- |
| `model` | 예 | 사용할 모델의 id. |
| `input` | 예 | 입력 항목의 배열. `message` 항목은 `role`(`system`, `user`, `assistant`)과 `input_text` 같은 파트로 이루어진 `content` 배열을 가진다. 도구를 사용하면 `function_call` 항목과 `function_call_output` 항목이 추가된다([6.5절](https://madeforagent.com/ko/lmsp/1.0/responses.md#tools)). |
| `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절](https://madeforagent.com/ko/lmsp/1.0/responses.md#tools)). `tools` 기능이 필요하다. |
| `store` | 아니요 | 서비스에 응답을 보관하도록 요청해, 나중에 조회하거나 `previous_response_id`로 쓸 수 있게 한다. 기본값은 `false`. `conversations` 기능이 없는 서비스는 무시한다. |
| `previous_response_id` | 아니요 | 저장된 응답에서 이어 간다. `conversations` 기능이 필요하다. |
| `extensions` | 아니요 | 비공개 확장([9.1절](https://madeforagent.com/ko/lmsp/1.0/common-rules.md#versioning)). |

모델에 없는 기능이 필요한 요청은 `400`과 `unsupported_capability`로 거부된다. 컨텍스트 윈도우에 들어가지 않는 요청은 `400`과 `context_length_exceeded`로 거부된다.

대화를 저장할 수 있는 서비스는 `previous_response_id`도 지원할 **수 있다**. 그렇지 않으면 모든 요청이 전체 컨텍스트를 담**아야 한다**. 서비스는 대화 상태를 유지하는 데 HTTP 연결에 의존해서는 **안 된다**.

## 6.2 동기 응답 {#sync}

`stream`이 `false`이면, 서비스는 생성이 끝난 뒤 응답 객체와 함께 `200 OK`를 반환한다.

```json
{
  "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절](https://madeforagent.com/ko/lmsp/1.0/common-rules.md#errors)). |
| `incomplete_details` | `null`. `status`가 `incomplete`이면 `max_output_tokens` 같은 `reason`을 가진 객체. |

출력 항목 유형:

| 항목 유형 | 형태 |
| --- | --- |
| `message` | `id`, `role`(`assistant`), 그리고 각각 `text`를 가진 `output_text` 파트의 배열인 `content`. |
| `function_call` | `id`, `call_id`, `name`, 그리고 JSON으로 인코딩된 문자열인 `arguments`. |
| `reasoning` | `id`와, 각각 `text`를 가진 `summary_text` 파트의 배열인 `summary`. |

생성에 실패하면 동기 요청에는 응답 객체가 아니라 오류 상태와 problem 문서([9.3절](https://madeforagent.com/ko/lmsp/1.0/common-rules.md#errors))로 응답한다. `failed` 상태는 스트림, 작업, 저장된 응답에서 나타난다.

`GET /lmsp/v1/responses/{response_id}`는 `store: true`로 저장된 응답을 반환한다. 이는 선택 사항이며 `conversations` 기능이 필요하다. 알 수 없는 id는 `404`와 `response_not_found`를 반환한다.

## 6.3 스트리밍 {#streaming}

스트리밍하려면 요청에서 `stream`을 설정한다.

```json
{
  "stream": true
}
```

서비스는 `Content-Type: text/event-stream`과 함께 `200 OK`로 응답하고 의미를 가진 이벤트를 보낸다. 각 이벤트는 `id`, `event` 이름, JSON `data` 객체를 가진다.

```text
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` | 하나의 응답에 묶이지 않는 문제로, 이후 스트림을 계속할 수 없다. | `error`(problem 객체) |

서비스는 최종 이벤트 또는 `error` 이벤트 뒤에 스트림을 닫는다.

서비스는 예컨대 도구 호출 인수, 이미지, 오디오, 구조화된 JSON 델타를 위해 추가 이벤트를 보낼 **수 있다**. 클라이언트는 알지 못하는 이벤트 유형을 무시**해야 한다**.

클라이언트가 스트리밍 요청의 연결을 닫으면, 서비스는 생성을 중단하는 **것이 좋다**.

## 6.4 이벤트 id와 재연결 {#reconnection}

이벤트의 `id`는 응답 안에서 엄격하게 증가하는, 1에서 시작하는 정수 순번이어**야 한다**.

연결이 끊긴 뒤 클라이언트는 마지막으로 받은 id를 보내 재개한다. 헤더로 보내는 방법:

```http
Last-Event-ID: 2
```

쿼리 매개변수로 보내는 방법:

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

둘 다 작업의 이벤트 엔드포인트([7.4절](https://madeforagent.com/ko/lmsp/1.0/tasks.md#events))로 보낸다. 서비스는 순번이 2보다 큰 이벤트만 다시 보낸다. 둘 다 있으면 `after`가 우선한다.

요청한 이벤트가 보존 기간보다 오래되었으면 서비스는 `410 Gone`과 오류 `event_history_expired`로 응답한다. 그러면 클라이언트는 대신 `GET /lmsp/v1/tasks/{task_id}`로 최종 결과를 가져온다.

`POST /lmsp/v1/responses`에서 직접 스트리밍된 응답은 그 HTTP 연결에 속하며 재개할 수 없다. 재개 가능한 출력이 필요한 클라이언트는 [작업](https://madeforagent.com/ko/lmsp/1.0/tasks.md)을 만들어 그 이벤트를 따라가는 **것이 좋다**.

## 6.5 도구 호출 {#tools}

LMSP 1.0은 클라이언트가 실행하는 함수 도구를 정의한다. 서비스는 도구를 직접 실행하지 않는다. `tools:execute` 스코프는 도구를 실행하는 확장을 위해 예약되어 있다([10.3절](https://madeforagent.com/ko/lmsp/1.0/security.md#scopes)).

먼저 클라이언트가 도구를 나열한다. 각 항목은 `function`으로 설정된 `type`, `name`, `description`, JSON Schema 객체인 `parameters`를 가진다. `tool_choice`는 `auto`(기본값), `none`, `required`, 또는 `{"type": "function", "name": "get_weather"}`이다.

```json
{
  "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`에 완전한 형태로 도착한다.

```json
{
  "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으로 인코딩된다.

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