구조화 출력
이 문서는 영문 원고를 AI로 번역한 내용이라 표현이 어색할 수 있습니다.
기본적으로 완료된 Format run은 미디어와 한 단락의 텍스트를 건넵니다. 사람에게는 괜찮고 데이터베이스에는 어색합니다. 스키마를 바인딩하면 typed 객체를 받습니다 — 같은 run이지만, 자체 레코드에 바로 쓸 수 있는 형태입니다.
정확한 요청·응답 스키마는 라이브 OpenAPI
(https://api.sume.com/reference/json)에서 가져옵니다. 여기 표는 읽기 쉬운 요약이며,
두 번째 스키마가 아닙니다.
과는 다른 종류의 것입니다
run 요청에는 JSON 모양의 필드가 둘 있고, 둘은 전혀 다르게 동작합니다. 이 둘을 헷갈리는 것이 첫 연동이 어긋나는 가장 흔한 이유입니다.
input | output_schema | |
|---|---|---|
| 정체 | 호출자 데이터 | receipt에 대한 계약 |
| 방향 | 여러분 → run | run → 여러분 |
| 형태 | 백엔드에 편한 아무 JSON 객체 | 아래 「지원 스키마」 부분집합 안의 JSON Schema |
| 검사 대상 | 객체인지, 키 개수, 바이트 크기 | 부분집합의 모든 규칙 |
| Sume이 예상하지 않은 형태 | 그대로 실행됩니다. 모르는 키는 그냥 데이터입니다 | 400 output_schema_invalid — 아무것도 실행·청구되지 않습니다 |
| 도착지 | 에이전트 프롬프트 안의 울타리 친 데이터 블록 | run 이후의 projection |
즉 input은 유연한 concat이고 output_schema는 엄격한 typed receipt입니다. input은 얼마든지
느슨하게 둘 수 있지만, output_schema는 전혀 느슨하게 만들 수 없습니다 — strict: false로도
안 되고, 다른 탈출구도 없습니다.
둘은 만나지 않습니다. projection은 여러분의 input을 절대 보지 못하므로(아래 참고),
보낸 값은 run이 마무리 텍스트에서 스스로 되풀이하지 않는 한 output으로 돌아올 수 없습니다.
은 어디에서 오는가
채팅 completion과 다른 부분이며, 스키마를 설계하기 전에 이해하는 것이 좋습니다. 경로가 두 가지이고, receipt가 어느 쪽이었는지 알려줍니다.
filled_by: "agent" — run이 직접 답한 경우. 스키마는 run에게 도구로 전달되며,
run은 끝내기 전에 그 도구를 호출해야 합니다. 스키마가 곧 도구의 인자 형태입니다.
작업을 한 모델이, 자기가 무엇을 왜 만들었는지 아직 기억하는 상태에서 객체를 채웁니다.
input과 instruction도 볼 수 있습니다 — run의 일부이기 때문입니다.
filled_by: "projection" — 폴백. run이 유효한 객체를 제출하지 않고 끝나면, 이후 별도의
constrained pass가 run이 남긴 것으로 객체를 만듭니다. 그 pass는 temperature 0의 OpenAI
strict json_schema completion이며, 정확히 두 가지 사실만 주어집니다.
| 사실 | 내용 |
|---|---|
| run이 생성한 미디어 | run이 만든 모든 artifact와 그 durable URL·메타데이터입니다. |
| run의 마무리 텍스트 | 마지막 assistant 메시지이며, 앞 8000자로 잘립니다. |
여러분의 input도, instruction도, Format 본문도, run의 중간 단계도 아닙니다.
이 경로에서는 output에 담고 싶은 것이 위 두 사실 중 하나에 있어야 합니다.
그래서 filled_by를 읽을 가치가 있습니다. run이 직접 쓴 객체인지, 남은 것으로 재구성한
객체인지의 차이이며, 대부분의 의외의 결과가 여기서 설명됩니다.
- projection 경로에서는 자기 식별자가 왕복하지 않습니다.
input으로 보낸order_id는 거기서 보이지 않습니다. 식별자는 여러분 쪽에run.id나 보낸Idempotency-Key로 키를 잡아 두고,output에는 run이 만든 것만 담으세요. - 두 경로 모두
output에는 지어낸 것이 없습니다. 둘 다 아래의 같은 게이트를 거친 뒤에야 전달됩니다. run이 직접 썼다고 해서 더 신뢰받지 않습니다. - projection 경로에서 비어 있는 텍스트 필드는 신호입니다. projection은
input을 보지 못하므로, 제목·설명·식별자를 브리프에서 가져오는 스키마는 미디어 필드만 찬 채 그 자리들이null로 돌아옵니다.filled_by: "projection"+ 빈 문구는 Format이 카피를 빼먹은 모양이 아니라, run이 일찍 멈춘 모양입니다.
run을 자동으로 채점한다면 — 스모크 매트릭스, 파트너 연동, 대시보드 — 전달로 세기 전에
filled_by를 읽고, 비디오라면 옆의 숫자가 아니라 파일 자체를 확인하세요.
primary_output_url에 ffprobe 한 번은 2초면 끝나고, 조립된 완성본과 그렇게 생긴 클립을
가르는 유일한 방법입니다.
따라오는 실용 규칙은 그대로입니다: Format이 실제로 만드는 것만 요구하세요. 레시피가
만들지 않는 필드를 요구하는 스키마는 projection 경로에서 매번 실패하며,
output_error를 읽기 전까지는 조용합니다.
스키마 바인딩
철자 두 가지, 동작은 하나입니다. 클라이언트에 맞는 쪽을 쓰세요.
네이티브 — output_schema:
OpenAI 형태 별칭 — response_format:
response_format은 receipt에서 output_schema로 정규화되므로, 읽어 오는 값은
항상 네이티브 철자입니다. 둘 다 보내면 400 invalid_request입니다.
이 별칭은 Chat Completions 철자를 따릅니다 — type이 맨 위, 바인딩은 json_schema 아래에
중첩됩니다. OpenAI Responses API는 같은 필드를 text.format: { type, name, strict, schema }로
평평하게 폅니다. 그 평평한 형태는 여기서 받지 않습니다. 클라이언트가 Responses 형식 본문을
만든다면 필드를 output_schema로 올리거나 json_schema 아래로 다시 중첩하세요.
| Field | Rules |
|---|---|
name | 필수입니다. 1–64자, ^[A-Za-z0-9._/-]+$. 네임스페이스를 두세요 — 모든 receipt에 나타납니다. |
strict | 기본값 true. 지원 스키마 아래 메모를 참고하세요. |
schema | 필수입니다. 지원 부분집합 안의 JSON Schema 객체입니다. |
Format은 대시보드에 바인딩된 자체 기본 스키마를 가질 수도 있습니다. 요청마다의
output_schema가 그 run에 대해 덮어씁니다. 어느 쪽이 적용됐는지는 receipt의
output_schema.source에 있습니다.
source | Meaning |
|---|---|
default | 바인딩된 것이 없습니다. output은 내장 스키마입니다. |
action_default | Format 자체의 바인딩된 스키마입니다. (action_은 wire 철자이며 Scheduled와 공유합니다.) |
request_override | 이 run 요청에 보낸 output_schema입니다. |
지원 스키마
스키마는 OpenAI strict-mode 부분집합을 만족해야 합니다. 권장이 아니라 실제
부분집합이며, 밖인 스키마는 제출 시 400 output_schema_invalid와 각 문제를 가리키는
details.violations[]로 거절됩니다. 아무것도 실행되지 않으므로 청구도 없습니다.
지원되는 키워드 전체
부분집합은 허용 목록으로 동작합니다. 목록에 없는 키워드는 조용히 무시되는 것이 아니라 위반입니다 — 무시된 제약은 Sume이 지킨다고 약속할 수 없는 스키마이기 때문입니다.
| 그룹 | 허용 |
|---|---|
| 구조 | type, properties, required, additionalProperties, items, $defs, $ref, anyOf |
| 값 | enum, const |
| 문자열 | format, pattern, minLength, maxLength |
| 숫자 | minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf |
| 배열 | minItems, maxItems |
| 주석 | title, description, default, examples, $schema, $id |
타입은 string, number, integer, boolean, object, array, null입니다.
그 밖은 모두 거절됩니다. 실제 연동에서 자주 걸리는 것들입니다.
| 거절 | 대신 |
|---|---|
oneOf | anyOf. 목록에 있는 것은 anyOf뿐인데, OpenAPI에서 옮긴 스키마는 반사적으로 oneOf를 씁니다. |
allOf | 분기를 하나의 object로 평평하게 펴세요. |
not, if / then / else, dependentRequired, dependentSchemas | 표현할 수 없습니다. 대안을 anyOf로 모델링하거나, output을 읽은 뒤 여러분 쪽에서 검증하세요. |
nullable: true | nullable union: "type": ["string", "null"]. |
patternProperties, propertyNames, unevaluatedProperties, additionalItems | 원하는 속성을 선언하세요. 나머지는 additionalProperties: false가 덮습니다. |
type
또는 $ref나 anyOf가 필요합니다. "무엇이든"을 뜻하는 적법한 JSON Schema인
{ "description": "…" }도 missing_type 위반입니다. array는 items도 선언해야 합니다.
$ref와 anyOf는 각각 자기가 놓인 노드를 단락(short-circuit)시킵니다. 옆에 둔 형제
키워드는 허용 목록 검사만 받을 뿐 아무 의미가 없습니다. 제약은 anyOf 분기 안이나 $defs
항목 안에 넣고, $ref 옆에 두지 마세요.
루트는 object여야 합니다
루트의 type은 정확히 "object" 하나여야 하므로 { "type": ["object", "null"] }도
거절됩니다. 최상위 array, string, union도 거절됩니다. 감싸세요.
additionalProperties: false
루트만이 아니라 스키마의 모든 object — array items 안과 $defs 안을
포함합니다.
required
선택 속성은 없습니다. 선언한 속성은 반드시 있어야 하는 속성입니다.
선택성은 대신 nullable union으로 표현하세요.
다른 곳에서 옮긴 스키마가 가장 자주 걸리는 규칙입니다. null을 "run이 여기에 둘
것이 없었다"로 읽으세요. 그것이 optional이 원하던 경우입니다.
크기와 중첩 한도
| Limit | Value | Violation |
|---|---|---|
| Nesting depth | 10 levels | max_depth |
| Total properties | 문서 전체 합산 5000 | max_properties |
| Enum values | enum 하나당 1000 | max_enum_values |
| Total string length | 문서 안의 모든 속성 이름·키·문자열 값을 합산해 120,000자 | max_string_length |
마지막 항목은 필드별 상한이 아니라 문서 전체 예산이라서, 큰 스키마에 긴 description 주석을
달면 개별 문자열이 특별히 길지 않아도 예산을 다 쓸 수 있습니다.
는 제한됩니다
해석되는 대상은 두 가지뿐입니다.
| Target | Use |
|---|---|
#/$defs/* | 스키마 문서 루트에 선언한 자체 정의입니다. |
SumeMediaFile# | Sume의 미디어 형태입니다. 아래를 참고하세요. |
외부 $ref — URL, 형제 문서, #/components/... — 는 거절됩니다. $ref: "#"도 마찬가지입니다.
OpenAI strict 모드는 그 방식의 루트 재귀를 허용하지만 Sume은 허용하지 않습니다. 루트 $defs에
대응 항목이 없는 #/$defs/* 대상도 거절되며, $defs 블록을 루트가 아니라 하위 스키마 안에
넣었을 때 이것에 걸립니다.
이름 붙인 정의를 통한 재귀는 괜찮습니다. $defs 항목은 자기 자신을 $ref할 수 있습니다.
depth 한도는 문서의 실제 중첩만 세므로, 자기 참조 정의는 depth를 쓰지 않습니다.
는 위 규칙을 완화하지 않습니다
수락·저장되지만 위 부분집합에 대해 아무것도 바꾸지 않습니다. 부분집합 밖 스키마는
strict가 true든 false든 거절됩니다. 탈출구로 쓰지 마세요 — 없습니다.
"유효한 JSON이면 형태는 아무래도 좋다"는 OpenAI의 JSON 모드({"type": "json_object"})에
해당하는 것도 없습니다. 스키마를 바인딩하거나 내장 스키마를 쓰거나,
선택지는 이 둘입니다.
읽기
각 항목은 { path, rule, message }입니다. path는
#/properties/scenes/items/properties/clip 같은 JSON Pointer 형식 위치이고, message는 사람이
읽으라고 쓰였으며 바뀔 수 있습니다. rule은 switch로 분기해도 안전한 안정적인 소문자
토큰입니다.
rule | 뜻 |
|---|---|
root_must_be_object | 루트가 없거나, object가 아니거나, type이 정확히 "object"가 아닙니다. |
not_an_object | 스키마 노드가 JSON object가 아닙니다. |
missing_type | 노드에 type·$ref·anyOf가 없습니다. |
unsupported_type | 위에 나열한 일곱 가지 밖의 type입니다. |
unsupported_keyword | 허용 목록 밖 키워드입니다. |
additional_properties_false | additionalProperties: false가 없는 object 노드입니다. |
required_completeness | 선언한 속성이 required에 없거나, required 항목에 대응 속성이 없습니다. |
missing_items | items가 없는 array 노드입니다. |
unsupported_ref | #/$defs/<name>도 SumeMediaFile#도 아닌 $ref이거나, 존재하지 않는 정의를 가리킵니다. |
invalid_defs | $defs가 있지만 이름 붙인 스키마의 object가 아닙니다. |
max_depth, max_properties, max_enum_values, max_string_length | 위 「크기와 중첩 한도」입니다. |
첫 번째 문제만이 아니라 모든 문제가 보고되므로, 400 한 번이면 스키마를 고치기에 충분합니다.
OpenAI structured outputs에서 넘어온다면
response_format: { type: "json_schema", … }나 Responses API의 text.format을 써 봤다면, 아는
것 대부분이 그대로 옮겨집니다. 부분집합 규칙은 같은 가이드에서 인용한 같은 규칙입니다. 다른
것은 스키마가 어디에 적용되는가입니다.
| OpenAI | Sume | 메모 |
|---|---|---|
response_format.json_schema | output_schema, 또는 response_format 그대로 | Chat Completions 철자만 받습니다. text.format은 받지 않습니다. |
json_schema.name | output_schema.name | 양쪽 모두 필수입니다. 네임스페이스를 두세요. 모든 receipt에 남습니다. |
json_schema.strict | output_schema.strict | 수락되고 기본값은 true이며, 아무것도 바꾸지 않습니다 — 부분집합은 언제나 강제됩니다. |
{"type": "json_object"} (JSON 모드) | (없음) | 스키마를 바인딩하거나 내장 스키마를 쓰세요. |
| 모델이 JSON을 냅니다 | run 이후 projection이 냅니다 | 스키마는 projection을 제약하며, run은 절대 제약하지 않습니다. |
메시지의 refusal | receipt의 output_error | 다른 메커니즘입니다. 안전 거절이 아니라 실패한 projection입니다. |
incomplete_details.reason: "max_output_tokens" | (해당 없음) | projection은 작고 한정적이라 잘린 JSON을 처리할 일이 없습니다. |
| 스트리밍 부분 JSON | (해당 없음) | output은 종료 receipt에 한 번 나타납니다. |
$ref: "#" 루트 재귀 | 거절 | 이름 붙인 #/$defs/* 항목으로 재귀하세요. |
| 대응 없음 | URL 게이트 | output의 모든 URL을 run이 실제로 만든 미디어와 대조합니다. |
| 대응 없음 | SumeMediaFile# | run의 미디어를 위한 내장 $ref 대상입니다. |
한 문장으로 요약하면: OpenAI에서는 모델이 말하는 것을 제약하고, 여기서는 끝난 run을
어떻게 되읽을지를 제약합니다. 나머지는 전부 여기서 따라옵니다 — 왜 스키마가 Format에게
비디오를 만들게 하지 못하는지, 왜 URL을 output에 지어낼 수 없는지, 왜 run이 output: null을
줄 수 있는지.
SumeMediaFile
run의 미디어를 출력에 넣고 싶은 곳에 { "$ref": "SumeMediaFile#" }로 참조하세요.
모든 필드가 필수이며, type과 url을 제외한 모든 필드는 nullable입니다.
| Field | Type | Notes |
|---|---|---|
type | "image" | "video" | "audio" | "file" | |
url | string (uri) | 이 run이 실제로 만든 URL이어야 합니다 — URL 게이트를 참고하세요. |
content_type | string | null | 예: video/mp4. |
file_name | string | null | |
size_bytes | integer | null | |
width, height | integer | null | 이미지와 비디오입니다. |
duration_ms | integer | null | 비디오와 오디오입니다. |
expires_at | string (date-time) | null | **내구성 있는 media.sume.com URL에서는 null**이며, 그것이 일반적입니다. 서명 URL이 반환될 때만 채워집니다. |
Sume 호스팅 미디어는 media.sume.com에서 public, max-age=31536000, immutable로
제공되며 만료되지 않습니다. 자체 레코드에 URL을 저장하고 나중에 렌더하세요 —
refresh 절차가 없습니다. 내구성 URL은 공개 URL이기도 합니다. 제품에 중요하면
artifact를 UI에 매핑하기를
참고하세요.
내장 스키마
아무것도 바인딩하지 않으면 output은 sume/action-run-output/v1에 투영됩니다.
text는 nullable이고, 네 배열은 항상 존재하며 비어 있을 수 있습니다.
내장 스키마는 run이 만든 미디어와 최종 텍스트에서 결정적으로 채워집니다. 모델이 관여하지 않으므로 커스텀 스키마처럼 실패할 수 없습니다. 스키마를 설계하지 않고 미디어만 원한다면, 이것으로도 출시할 수 있습니다.
URL 게이트
커스텀 output이 전달되기 전에, 안의 모든 URL이 이 run이 실제로 만든 미디어
집합과 대조됩니다. 비교는 정확한 문자열 일치입니다.
두 번의 패스가 이를 잡아냅니다. 첫 패스는 객체 안 어느 깊이에 있든, 여러분이 그 필드를
미디어로 선언했든 아니든 모든 http(s):// 문자열을 모읍니다. 두 번째 패스는
SumeMediaFile 형태 값의 url을 내용과 무관하게 모읍니다 — 그래서
url에 놓인 "none"이나 "" 같은 자리표시자가 URL처럼 보이지 않는다는 이유로 빠져나갈 수
없습니다.
이 run이 만들지 않은 URL — 형태가 그럴듯한 media.sume.com이라도 — 은 반환되지
않고 투영이 실패합니다. 그다음 객체가 스키마에 대해 검증됩니다.
그래서 완료된 run은 실제 미디어 URL과 함께 스키마에 맞는 출력을 반환하거나,
output: null과 이유를 반환합니다. 스키마 형태의 추측을 반환하지 않습니다.
완성본은 그 부분들 중 하나가 아닙니다
장면·컷·구간 같은 부분들로 이루어진 전체를 기술하는 스키마에는 보통 조립이 끝난 파일을 담는 필드도 있습니다. 둘은 서로 다른 파일이며, 전체 자리를 자기 부분 중 하나로 채운 receipt는 거부됩니다.
이 검사는 의도적으로 좁습니다. "status": "succeeded"이면서 각자의 비디오를 가진 부분이
둘 이상 있고, 모든 부분 바깥에 있는 비디오가 그중 한 파일을 다시 쓸 때만 걸립니다.
클립 하나가 정말로 완성본인 Format, 그리고 한 부분에서 의도적으로 가져온 포스터·썸네일은
그대로 통과합니다.
조립하지 못한 run에게도 정직한 답이 있으며, 그것은 "여기 장면 하나"가 아닙니다. 만든
부분들을 보고하고 완성본 필드를 null로 두거나, 조립의 실제 상태를 보고하세요.
duration은 파일과 대조됩니다
SumeMediaFile 안의 duration_ms는 artifacts[]를 채우는 것과 같은
원장에서 옵니다. 그 원장이 길이를 기록해 둔 파일이라면, output의 값은 10% 이내로 그것과
일치해야 합니다 — 그렇지 않은 주장은 다른 파일을 설명하고 있는 것이며, 전달되지 않고
투영이 실패합니다.
원장에 길이가 없으면 아무것도 검사하지 않습니다. 거기의 null은 "0"이 아니라 "측정하지
않음"이고, 아무도 측정하지 않은 사실로 run을 실패시키지는 않습니다. 즉 읽어 온
duration_ms는 artifact 자신의 값이거나 미검증 값이며, 다른 것에서 계산된 숫자는 결코
아닙니다. 여러분이 직접 선언한 duration_seconds 숫자는 projection이 씁니다.
와
대부분의 연동에는 보여줄 것이 하나 있습니다. 이름을 붙이면 receipt가 해석해 줍니다.
receipt의 primary_output_url은 그 키의 URL입니다. 해석 순서:
- run 요청의
primary_output_key. - Format 자체의
primary_output_key. - 미디어 객체를 담은 첫 최상위 키, 또는 첫 요소가 미디어인 배열.
내장 스키마에서는 videos → images → audio → files 순으로 폴백합니다.
primary_output_key는 최대 64자입니다. 두 필드는 종료가 아닌 상태에서 null이고,
output_error가 설정된 때도 null입니다.
출력을 만들 수 없을 때
API로 실행한 run에서는 투영 실패가 곧 run 실패입니다. 이런 run은 무인 실행이므로,
output이 null인 completed receipt는 성공처럼 읽히지만 성공이 아닙니다. status는
failed가 되고 error는 output_error와 같은 이유를 담습니다. 사람이 스레드를 읽는
Agents UI에서는 같은 형태가 completed로 남습니다 — 거기서는 receipt가 아니라 초안입니다.
어느 쪽이든 artifacts[]에는 run이 만든 모든 것이 그대로 채워집니다. 형태가 맞지
않아도 미디어는 항상 있습니다.
예외는 run을 들여다보지도 못한 투영입니다. output_extraction_failed는 판정이 아니라
전송 실패를 기록하므로 completed로 남고, 다음 읽기에서 스스로 다시 투영됩니다.
output_error.code | Meaning | details |
|---|---|---|
output_schema_unsatisfied | 투영이 스키마와 맞지 않거나, 이 run이 만들지 않은 미디어를 참조했습니다. | rejected_urls[](앞 10개만) 또는 violations[], 그리고 미디어 유형별 harvested 수입니다. |
output_extraction_failed | 투영을 실행할 수 없었습니다. status는 completed로 남습니다. reason이 harvest_unavailable이면 run이 종료될 때 미디어를 읽지 못한 것이며, 다음 읽기에서 receipt가 채워집니다. | reason |
이렇게 처리하세요.
- 같은 Format에서
output_schema_unsatisfied가 반복될 때. 거의 항상 레시피가 만들지 않는 미디어를 요구하는 스키마입니다.details.harvested를 필수 필드와 비교하세요 — 위 예시는 이미지를 요구해 하나를 받았지만 두 번째를 원했습니다. 필드를 nullable union으로 완화하거나, run이 만들도록instruction을 바꾸세요. violations[]가 있는output_schema_unsatisfied. 미디어가 아니라 형태입니다. violation이 문제 경로를 가리킵니다.output_extraction_failed. 일시적입니다. 먼저 run을 한 번 더 읽어 보세요 —harvest_unavailable은 그것만으로 해소됩니다. 계속된다면 새 멱등성 키로 run을 재시도하세요. 이전 키는 이미 가진 receipt에 묶여 있습니다.- 어느 코드든, UI에서.
artifacts[]는 여전히 있습니다. 비디오가 있는 고객에게 오류를 보여 주기보다, 미디어를 보여 주고 형태 실패를 로그하는 편이 낫습니다.
제출 시 실패
아무것도 실행되기 전에 잡히는 스키마 문제입니다. 청구되지 않습니다.
| Code | Status | What to do |
|---|---|---|
output_schema_invalid | 400 | 스키마가 지원 부분집합 밖입니다. details.violations[]가 각 문제를 가리킵니다. |
invalid_request | 400 | output_schema와 response_format을 둘 다 보내는 경우를 포함합니다. |
체크리스트
- 루트는 정확히
{"type": "object"}입니다.$defs를 포함해 모든 object에additionalProperties: false입니다. - 모든 노드에
type·$ref·anyOf중 하나가 있고, 모든array가items를 선언합니다. - 선언한 모든 속성이
required에 있고, 선택성은 nullable union입니다. -
oneOf,allOf,not,if/then/else,nullable: true가 없습니다. -
$ref대상은 (루트에 선언한)#/$defs/*와SumeMediaFile#뿐이며,#은 아닙니다. - 스키마는 Format이 실제로 만드는 미디어만 요구합니다.
-
input으로 보낸 값을 기대하는 필드가 없습니다 — projection은 그것을 보지 못합니다. -
name은 네임스페이스가 있고 안정적이어서 receipt를 검색하기 쉽습니다. -
primary_output_key는 UI가 보여 줄 하나를 가리킵니다. - 리더는
failedrun과completedrun 모두에서output_error가 설정된output: null을 처리합니다. - 리더는
output이 null일 때artifacts[]로 폴백합니다.
다음
input— 호출자 데이터 — run 본문의 다른 절반이자, 스키마가 없는 쪽- 실행과 결과 — 이 모든 것을 담는 receipt
- Format 호출하기 — invoke 계약
- 제품에 Format 임베드하기 — 출력을 자체 레코드에 매핑하기