응답 계약
응답 계약은 child의 DataPart.data를 작은 facts로 투영합니다. JSON Schema 검증기나 LLM 검색 지식이 아닙니다. schema로 계약을 고른 뒤 include, lists, dynamic에 허용한 값만 라우팅·요약·프롬프트에 전달합니다.
원본 API 응답, OCR 전문이나 깊은 JSON 전체를 WorkHistory에 넣지 마세요. 계약으로 안전한 projection을 만들 수 없다면 답변 생성을 중단하거나 권한 있는 child가 원문을 다시 조회해야 합니다.
위치는 소비자가 정합니다
섹션 제목: “위치는 소비자가 정합니다”| 소비자 | 기본 위치 | 이유 |
|---|---|---|
OKF routing rule과 route preview | okf/contract__*.md | rule과 contract를 한 디렉터리에서 읽음 |
response_contract_facts prompt transform | okf-contracts/contract__*.md | 검색용 OKF와 실행 계약을 분리 |
ResponseContractSummaryAdapter | okf-contracts/contract__*.md | data-summary 기본 contract_root |
okf-contracts/ 기본 위치에 계약이 없으면 기존 프로젝트 호환을 위해 okf/를 한 번 fallback으로 읽습니다. 새 프로젝트는 소비자 기준으로 위치를 명시하세요. 라우팅 rule 옆의 계약과 data-summary 계약이 같은 wire shape를 읽어도 서로 다른 배포 단위일 수 있습니다.
ResponseContractSummaryAdapter의 조립과 summary_schema는 Agent 구성에서 다룹니다. 원격 snapshot과 cache도 이 페이지 아래에서 함께 다룹니다.
짧은 중첩 예제
섹션 제목: “짧은 중첩 예제”child가 다음 DataPart를 반환한다고 가정합니다.
{ "schema": "facility-inspection.result.v1", "inspectionStatus": "action_required", "summary": {"failedChecks": 2}, "zones": [ { "zoneId": "B1-machine-room", "result": "fail", "checks": [ {"item": "pipe", "status": "fail", "value": 0.8} ], "operatorSignals": { "managerName": "private", "memo": "night noise", "sensorFlags": ["vibration_high"] } } ]}라우팅에 함께 쓸 계약은 okf/에 둡니다.
---type: contracttitle: facility-inspection-resultschema: facility-inspection.result.v1
include: # DataPart.data 루트의 확정 필드만 선택합니다. - source: inspectionStatus label: 점검 상태 - source: summary.failedChecks label: 부적합 수
lists: # []를 끝까지 적어 중첩 check 객체를 행으로 평탄화합니다. - path: zones[].checks[] output: checkResults include: - source: item label: 점검 항목 - source: status label: 상태 - source: value label: 측정값
- path: zones output: zoneResults include: - source: zoneId label: 구역 - source: result label: 결과 dynamic: # 내부 key가 열려 있는 봉투만 bounded dynamic으로 투영합니다. source: operatorSignals label: 현장 신호 exclude: - managerName compact: max_depth: 4 max_string_length: 200 max_list_items: 6 max_object_items: 16generated: by: process:llamon-contract-draft at: 2026-07-16T00:00:00+09:00---lists[].path는 DataPart.data 루트 기준이고 lists[].include.source는 각 행 기준입니다. zones[].checks[]의 마지막 []를 빼면 행이 list로 남아 빈 결과가 될 수 있습니다. 알려진 key는 include로 whitelist하고, dynamic은 내부 key를 실제로 모르는 객체에만 사용하세요.
dynamic.exclude는 dynamic.source 내부의 상대 경로입니다. compact 상한을 넘으면 요청 전체를 실패시키지 않고 값을 자르며 _compact metadata에 제거·절단 정보를 남깁니다. 기본 key 필터도 raw, OCR, credential, 흔한 PII성 이름을 제거하지만 도메인 PII 정책을 대신하지는 않습니다.
DSL 기준점
섹션 제목: “DSL 기준점”| 위치 | 기준점 |
|---|---|
root include.source | DataPart.data 루트 |
root dynamic.source | DataPart.data 루트 |
lists[].path | DataPart.data 루트 |
lists[].include.source | 찾은 각 list item |
lists[].dynamic.source | 찾은 각 list item |
include에는 선택적 label과 render를 붙일 수 있습니다. label은 프롬프트에 보이는 key 이름을, render는 enum·boolean 같은 값을 사람이 읽을 표현으로 바꿉니다. 알 수 없는 DSL 키는 조용히 무시하지 않고 로딩 단계에서 오류가 납니다.
전체 필드
섹션 제목: “전체 필드”문서 바깥 형식은 일반 OKF Markdown을 따릅니다.
| 위치 | 키 | 기본값·제약 | 의미 |
|---|---|---|---|
| frontmatter | type | contract; 필수 | response contract 문서 유형 |
| frontmatter | title | 문자열 | 사람이 읽는 제목 |
| frontmatter | description | 문자열; 선택 | 계약 설명. projection에는 사용하지 않음 |
| frontmatter | generated.by | actor 문자열 | 작성 주체. 새 draft 기본값은 process:llamon-contract-draft |
| frontmatter | generated.at | ISO 8601 시각 | OKF의 마지막 의미 있는 변경 시각 |
| 파일명 | contract__<name>.md | 고유해야 함 | 확장자를 뺀 상대 경로가 contract name |
| body | Markdown 본문 | 선택 | 운영 메모. projection에는 사용하지 않음 |
0.5.x loader는 기존 timestamp를 계속 읽습니다. draft_contract_from_sample()의
timestamp=도 deprecated alias로 남지만 출력은 항상 generated만 사용합니다.
contract root는 다음 키를 받습니다.
| 키 | 기본값·제약 | 의미 |
|---|---|---|
schema | schema ID; 선택 | 정확히 같은 DataPart schema와 매칭 |
schema_prefixes | 문자열 또는 문자열 배열; 선택 | prefix로 여러 schema 버전을 매칭 |
include | 배열; 기본 [] | 알려진 root 필드를 whitelist |
dynamic | table; 선택 | key가 열린 root 객체를 bounded projection |
lists | 배열; 기본 [] | list item을 별도 facts 배열로 투영 |
summary | { template: ... }; 선택 | projection으로 짧은 결정론 summary 생성 |
runtime에서 자동 선택할 contract에는 schema나 schema_prefixes가 필요합니다. 정확한 schema 계약이 먼저 선택되고, 같은 exact schema를 선언한 contract가 둘이면 시작 단계에서 거부합니다. prefix 계약끼리는 가장 긴 prefix가 우선하며 길이까지 같으면 contract name을 사전순으로 비교합니다. 원격 snapshot은 범위가 예상보다 넓어지는 것을 막기 위해 schema_prefixes를 허용하지 않습니다.
include와 lists
섹션 제목: “include와 lists”| 위치 | 키 | 기본값·제약 | 의미 |
|---|---|---|---|
include[] | source | 문자열; 필수 | root 기준 field 또는 dotted path |
include[] | label | 문자열; 선택 | prompt에 표시할 이름 |
include[] | render | key-value mapping; 기본 {} | scalar 값을 읽기 쉬운 문자열로 변환 |
lists[] | path | 문자열; 필수 | root 기준 list 경로 |
lists[] | output | 문자열; 기본 results | 투영된 배열을 저장할 facts key |
lists[] | include | 배열; 기본 [] | 각 list item에서 선택할 필드 |
lists[] | dynamic | table; 선택 | 각 list item 안의 열린 객체 projection |
source와 path는 dotted path와 [] wildcard를 지원합니다. 예를 들어 documents[].metadata.fileId는 여러 문서의 fileId를 모읍니다. lists[].path는 최종 값이 list여야 하며, 같은 output을 쓰는 여러 list 규칙의 결과는 선언 순서대로 합칩니다.
render의 key는 boolean과 null도 문자열 true, false, none으로 정규화합니다. 매핑에 없는 값은 원래 값을 유지합니다.
dynamic과 compact
섹션 제목: “dynamic과 compact”root dynamic은 DataPart root를 기준으로, lists[].dynamic은 찾은 각 list item을 기준으로 읽습니다. 두 위치에서 받는 필드는 같습니다.
| 키 | 기본값·제약 | 의미 |
|---|---|---|
source | 문자열; 필수 | 투영할 열린 객체의 상대 경로 |
label | 문자열; 선택 | prompt에 표시할 이름 |
exclude | 문자열 배열; 기본 [] | source 내부에서 제거할 상대 경로 |
compact | table; 아래 기본값 | key 제거와 크기 상한 |
exclude는 account.number처럼 중첩 경로를 받고, 배열 안의 경로에는 members[].name처럼 []를 적습니다.
compact 키 | DSL 기본값 | 의미 |
|---|---|---|
drop_keys | [] | 이름이 정확히 일치하는 key를 SDK 기본 제거 목록에 추가 |
drop_key_fragments | [] | 정규화한 key 이름에 포함되면 제거할 조각을 SDK 기본 목록에 추가 |
max_string_length | 1200 | 문자열 길이 상한 |
max_list_items | 12 | 배열 항목 수 상한 |
max_object_items | 40 | object key 수 상한 |
max_depth | 5 | 중첩 깊이 상한 |
모든 크기 상한은 1 이상의 정수입니다. 사용자 설정은 기본 제거 규칙을 교체하지 않고 더합니다. 따라서 drop_keys: []로 OCR·raw·debug 기본 필터를 끌 수 없고, drop_key_fragments: []로 계좌·주소·주민번호·전화번호처럼 SDK가 보수적으로 막는 이름을 되살릴 수도 없습니다. 도메인에서 추가로 금지할 key만 선언하세요.
dynamic: source: providerPayload exclude: # source 내부의 명시적 비공개 경로를 먼저 제거합니다. - customer.profile.name - household.members[].residentNumber compact: # 도메인 원문과 내부 진단 key를 기본 필터에 추가합니다. drop_keys: [originalResponse] drop_key_fragments: [internalToken] max_string_length: 300 max_list_items: 8 max_object_items: 20 max_depth: 4결정론 summary
섹션 제목: “결정론 summary”summary.template은 LLM을 호출하지 않고 projection 값으로 짧은 문장을 만듭니다. {field}는 field 값을 넣고, {items|count}는 배열·object 항목 수를 넣습니다.
summary: # 누락 문서 수와 전체 판정만 사용해 짧은 summary를 만듭니다. template: "판정 {status} — 누락 문서 {missingDocuments|count}건"placeholder는 root facts key를 먼저 찾고, 없으면 results 첫 항목에서 같은 key를 찾습니다. —, -, , 로 나뉜 구간에 값이 없으면 그 구간을 통째로 생략합니다. 조건문과 반복문은 지원하지 않습니다. 복잡한 요약은 contract에 넣지 말고 summary Agent가 projection만 받아 작성하게 하세요.
원격 snapshot과 cache
섹션 제목: “원격 snapshot과 cache”ResponseContractCache는 이미지의 로컬 계약을 immutable baseline으로 유지하면서 okf-syncer가 제공한 검증된 snapshot을 schema별로 덧씌웁니다. 원격 기능을 끄면 로컬 계약만 사용하며 CONTRACT_ROOT를 생성하거나 수정하지 않습니다.
| 환경변수 | 역할 |
|---|---|
CONTRACT_ROOT | 로컬 baseline. 기본 okf-contracts |
CONTRACT_SNAPSHOT_URL | 실행 중인 okf-syncer 조회 URL. 비우면 원격 기능 off |
CONTRACT_SNAPSHOT_TRANSPORT | rest 또는 jsonrpc |
CONTRACT_SET | 원격의 논리적 계약 묶음 |
CONTRACT_PREFETCH_SCHEMAS | 시작할 때 미리 조회할 schema 목록 |
CONTRACT_CACHE_DIR | 검증된 마지막 snapshot 저장 위치 |
CONTRACT_BACKGROUND_REFRESH_ENABLED | 유휴 중 주기 refresh; 기본 false |
CONTRACT_REFRESH_INTERVAL_SECONDS | 중복 조회 제한과 주기, 30..3600초 |
CONTRACT_CACHE_DIR이 있으면 새 프로세스는 네트워크 refresh보다 먼저 마지막 snapshot을 검증해 복원합니다. 컨테이너 재생성 뒤에도 유지하려면 persistent volume에 마운트하고 생성 파일은 Git에 커밋하지 마세요.
startup은 prefetch schema를 조회하고, 요청 경로는 새 DataPart.data.schema를 관측했을 때 refresh합니다. 잘못된 JSON, 크기 초과, 계약 검증 실패는 활성 snapshot으로 승격하지 않으며 현재 memory snapshot과 로컬 baseline을 유지합니다. 원격 snapshot은 예상보다 넓은 계약이 활성화되지 않도록 schema_prefixes를 허용하지 않습니다.
운영에서는 cache volume 권한, revision·ETag, 마지막 refresh 오류, syncer 장애 중 재시작 복원을 확인하세요. CONTRACT_SNAPSHOT_URL에는 GitLab 저장소가 아니라 실행 중인 syncer의 HTTP 주소를 넣습니다.
draft와 preview
섹션 제목: “draft와 preview”샘플에서 초안을 만들 수 있습니다.
# 기존 계약을 덮어쓰지 않도록 draft 파일을 따로 만듭니다.llamon okf contract draft \ --sample samples/facility-inspection-result.json \ --out okf/contract__facility-inspection-draft.mddraft는 출발점일 뿐 중첩 lists, dynamic, render, PII exclude를 완성하지 못할 수 있습니다. 직접 검토한 뒤 같은 샘플로 projection을 확인하세요.
# 최종 facts 모양과 누락된 중첩 경로를 확인합니다.llamon okf contract preview \ --sample samples/facility-inspection-result.json \ --contract okf/contract__facility-inspection-result.md \ --output json라우팅까지 함께 확인하려면 contract와 rule이 있는 디렉터리를 지정합니다.
# 같은 okf/에서 contract projection 뒤 routing rule을 평가합니다.llamon okf route preview \ --sample samples/facility-inspection-result.json \ --rules okf \ --allowed-alias facility_maintenance \ --text "점검 결과를 확인해 줘" \ --output json계약이 없거나 schema가 맞지 않으면 DataPart는 unmatched[]의 bounded fallback으로 내려갑니다. unmatched는 의미를 아는 상태가 아니므로 자동 승인이나 side effect 근거로 쓰지 마세요.