응답과 스트리밍
- 규범적
- 초안 1.0
- 갱신일
- Markdown으로 보기
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 구조를 사용하므로, 하나의 요청 형태로 다음을 담을 수 있다.
- 여러 턴의 메시지
- 텍스트, 이미지, 오디오, 파일
- 도구 호출과 도구 결과
- 구조화된 출력
- 추론 요약
- 멀티모달 출력
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), 그리고 각각 text를 가진 output_text 파트의 배열인 content. |
function_call |
id, call_id, name, 그리고 JSON으로 인코딩된 문자열인 arguments. |
reasoning |
id와, 각각 text를 가진 summary_text 파트의 배열인 summary. |
생성에 실패하면 동기 요청에는 응답 객체가 아니라 오류 상태와 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 |
하나의 응답에 묶이지 않는 문제로, 이후 스트림을 계속할 수 없다. | 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\"}"
}