Studio 연동
업스테이지 Studio(Document Agent API, /v2)와 우리 백엔드가 어디서 어떻게 맞물리는지 정리한 페이지입니다. 흐름은 사고조사 파이프라인 v2(결정 20261021 🟢)를 기준으로 하고, 계약은 SaaS(api.upstage.ai) 규약 v1.6.5 와 업스테이지 통합 설계 v0.4(리포 사본 docs/external/studio/)를 따릅니다. v1 과 임대 서버(pipeline 1.1.1)에서 다른 점은 따로 표시했습니다. 「임대 서버」의 동작은 운영(pipeline 1.6.12) 동작이 아닙니다.
한눈에 보기
Studio 를 부르는 일은 여섯 갈래입니다. 문서 분석(분류·추출)과 AI 보고서 생성(LLM 탭1→2→3)은 워크플로 단계에서 원장에 작업을 적고 폴러가 Studio 에 보내고 결과를 받아 옵니다. 그룹 요약(Agent D)은 워크플로 단계가 아닌 곁가지로, 추출을 보낼 때 시작해 따로 돕니다. 지식컬렉션과 채팅은 화면 요청이 Studio 를 직접 부릅니다.
분류 → 분류 검수 ─┬─► 추출 → 추출 검수 → 분석 조건 → [AI 보고서 생성: LLM 탭1 → 탭2 → 탭3]
└─► 그룹 요약(Agent D, 그룹마다 1회) ─► (결과 보관)
AI 보고서 확인 = 탭1~3 + 마지막 「요약」 탭(그룹 요약 결과) → 생성 완료(잠금) → 보고서 검수 → 항목 선택 완료(조립) → 전송
source_job_id = 분류 Job. 사람이 고친 분류는 먼저 Studio 에 반영(보정 왕복)한 뒤 보낸다.input_job 으로 첨부 + 탭 프롬프트, 탭2·3 = 앞 탭 JSON. 출력은 text.format JSON Schema.review_selector 로 골라 보낸다. 결과는 AI 보고서 마지막 「요약」 탭.POST /vector_stores/route) → ③ 본 답변(file_search·첨부·conversation 이어가기). 기본은 꺼짐(스텁).현재 상태 (2026-11-03 기준, 판은 10/30 측정)
| 항목 | SaaS | 운영 (k0s aipack) | 임대 서버 |
|---|---|---|---|
| 판 | engine 1.4.0 · pipeline 1.7.0 (10/30 실측), 규약 사본 v1.6.5 | engine 1.3.0 · pipeline 1.6.12 (10/30 실측) — 호출 동작 미확인 | engine 1.2.1 · pipeline 1.1.1 (10/1 실측, 10/30 접속 안 됨) |
| 파이프라인 정의 | 시드는 v1(active)·v2. AI_PLATFORM_STUDIO_PIPELINE_VERSION=2 를 준 서버만 새 실행이 v2 로 돈다. v1 으로 시작한 실행은 v1 으로 끝나고, 재분석·추가 전송으로 새 실행을 만들어도 첫 실행의 정의를 쓴다(결정 20261028) | ||
| v2 전 구간(분류 → 추출 → LLM 탭1~3 + 그룹 요약) | 실연동 E2E 10/21~22 — 검수 수정값 반영을 고친 뒤 다시 확인(결정 20261021 「추가」) | 실호출 전 | 확인 안 함 |
| 업스테이지 제공 에이전트 | 분류·추출·요약(2-2 수정본, import → merge → instruct)을 업스테이지 제공 에이전트로 교체(결정 20261015) | 실호출 전 | 우리가 만든 지시서 |
| 검수 보정 반영 경로 | Job 검수 JSON — review-document 동기 내려받기 + 본문 업로드 apply=manual, 쪽 단위 | 규약상 있음, 실호출 전 미확인 | 제자리 교체(result-snapshots → file-collection-imports → plan/commit), 파일 단위만 |
| 자동 실연동 점검 · 분류 벤치마크 | 결과는 /quality/ 신뢰 점검표. 실서류 자동 E2E(SaaS, 평일 30분 간격 — 평소 빠른 확인 사건 1건, 12:30·17:30·배포 직후 전체 회귀 사건 3건)는 /e2e/ | ||
| Studio 채팅 | 어댑터 있음(세 호출, 기본 꺼짐). 사고조사 챗봇은 input_job 으로 분류·추출 Job 첨부 | 실호출 전 | response.failed(tiktoken 내려받기 실패, 10/1 실측) |
전체 구조
왼쪽이 우리 백엔드, 오른쪽이 Studio 입니다. 백엔드 안에서 Studio 를 직접 아는 곳은 aiplatform-core 의 studio 패키지뿐이고, 나머지 모듈은 플랫폼 중립 포트(DocumentClassificationPort 등)만 봅니다.
source_job_id·review_selector(Job id + 문서 id)·input_job(Job id)으로만 잇습니다.Job 사슬과 Collection
보고서 하나에서 Studio Job 이 사슬로 이어집니다(v2). 분류 Job 을 추출 Job 이 이어받고, AI 보고서 탭1 이 추출 Job 을 첨부해 LLM 을 부르고, 탭2·3 은 앞 탭 JSON 을 받습니다. 그룹 요약은 같은 분류 Job 의 문서를 그룹별로 골라 따로 돕니다. 사람이 고친 값은 다음 호출 직전에 앞 Job 의 결과(Collection 안)에 반영합니다.
CLASSIFY 구성이면 ② 의 Collection 반영 없이 지시문 수정값 줄만 싣습니다.이어 실행 규칙
- 추출은 단수
source_job_id만 씁니다. 입력 File 목록은 source Job 의 파일 집합과 정확히 같아야 합니다(빠짐·추가·중복은 400). - Import 판정과 이어 실행은 원본 Job 의 파일을 100개까지만 읽습니다. 넘으면
incomplete_job_lineage·import_input_invalid로 이어받지 못하는데 분류 Job 생성은 막지 않습니다. 그래서 사건 서류를 여러 쪽 PDF 로 묶어 파일 수를 줄입니다(결정 20261021-case-documents-job-100-files 🟢). - 그룹 요약은
source_job_id대신review_selector로 분류 Job 의 일부 문서만 보냅니다. 둘을 함께 보내면400 review_selector_conflict입니다. - LLM 탭1 의
input_job은 이어 실행이 아니라 Job 결과 첨부입니다(include_parse:false). 탭2·3 은 Job 을 첨부하지 않습니다. - 복수
source_job_ids는 쓰지 않습니다.
Collection 을 찾는 방법 (보정 반영·그룹 요약 문서 읽기에 필요)
- 설정
…_COLLECTION_ID가 있으면 그 값. - 없으면
GET /agents/{a}/configs/{c}의 Export 스텝table_key를 읽고 GET /search-collections?agent_id=에서 같은table_key이고 종류가agent_export(임대 서버는source.type)인 항목을 고릅니다. 결과는 프로세스 안에 캐시합니다.
StudioCollectionResolver — 그룹 요약은 AI_PLATFORM_STUDIO_SUMMARY_GROUP_CLASSIFICATION_COLLECTION_ID 가 있으면 그 값, 없으면 분류 지시서로 찾고, 모르면 AI_SUMMARY_COLLECTION_UNKNOWN 으로 실패합니다.
접수부터 AI 보고서까지
워크플로 INVESTIGATION_MAIN v2 의 단계 순서대로, 각 걸음에서 누가 무엇을 부르고 무엇이 남는지 적었습니다. v2 는 v1 행을 그대로 베끼고 seq 40 하나만 바꿉니다. 초록 줄은 사람이 화면에서 하는 일입니다.
| seq | 단계 | 시작 상태 → 성공 상태 | 다음 단계 자동 | Studio | 멈추는 곳 |
|---|---|---|---|---|---|
| 10 | RECEIVE_TRANSFER | READY_FOR_CONFIRM → | 아니오 | 없음 | 확정(API-05 또는 화면 「AI 분류 시작」) |
| 20 | CLASSIFY | CLASSIFYING → EXTRACTING | 예 | 사건 묶음 PDF 업로드 + 분류 | 미분류·저신뢰가 있으면 「미분류검출」로 EXTRACT 진입 게이트에서 멈춤 |
| 30 | EXTRACT | EXTRACTING → EXTRACTION_COMPLETED | 아니오 | 분류 보정 반영 + 추출 (v2: 제출 때 그룹 요약 곁가지 시작) | 분석 요청 |
| 40 | GENERATE_REPORT_TABS v1: ANALYZE | ANALYZING → ANALYSIS_COMPLETED | 아니오 | v2: 추출 보정 반영 + LLM 탭1→2→3 · v1: 추출 보정 반영 + 요약 Instruct 1회 | — |
| 50 | GENERATE_DRAFT | ANALYSIS_COMPLETED → | 아니오 | 없음 — enabled=false(10/22, 활성 단계 4개) | — |
걸음별 상세 (v2)
ImageTransferReceptionService.startimage_transfers, transfer_pagesIMAGE_ZIP_EXTRACTION(image_transfer_background_jobs) → 검증·해제 → WorkflowLauncherPort.startIfAbsent(INVESTIGATION_MAIN) (정의 버전은 WorkflowDefinitionSelector)report_pages, 실행 1건, 보고서 READY_FOR_CONFIRMPOST …/confirmation → CLASSIFY → AiClassificationJobService.request. API-05 로 자동 시작한 분류는 1인 AI 작업 한도 이상이면 조사자별 대기열에 섰다가 자리가 나면 시작(결정 20261022)ai_pipeline_jobs CLASSIFY · REQUESTEDScanPdfPreparationService → PdfDocumentAssembler (활성 낱장 전부를 문서함 순서로, 500쪽마다 나눔)POST /files, GET /files/{id}?view=status&pages=noneai_pipeline_documents·_document_pages(쪽 ↔ 낱장), 스냅샷 해시가 같으면 기존 묶음 재사용StudioClassificationAdapter.submitPOST /responses (분류 에이전트, 묶음 File 들, background:true)external_job_id, ai_pipeline_job_inputs(파일 순서)poll → StudioClassificationOutputParser → materializeFromAiGET /responses/{id}?include=last → 완료면 ?include=allclassification_results LIVE, 낱장별 CLASSIFIED / REVIEW_REQUIRED / UNCLASSIFIED…/manual-assignments, AI 확정 …/ai-confirmations, 「정보 추출 시작」 …/extraction-startclassification_corrections(MANUAL_ASSIGN, 반영 PENDING), EXTRACT 작업 REQUESTEDAiPipelineResultSyncService.prepareClassification (라벨이 바뀐 보정이 있을 때만)ai_pipeline_result_syncs → RESOLVEDStudioExtractionAdapter.submit → ExtractionSubmittedEventPOST /responses (source_job_id = 분류 Job, 같은 File, Idempotency-Key)StudioExtractionOutputParser → ExtractionResultService.recordGET /responses/{id}?include=last → ?include=allextraction_results LIVE, 항목별 output_pointer·신뢰도…/field-corrections(선택), 분석 요청 …/analysis-requestsextraction_field_corrections, analysis_requests, GENERATE_REPORT_TABS 작업 REQUESTEDAiReportTabsJobService → prepareExtraction (출처 EXTRACT 구성, 바뀐 값이 있을 때만)ai_pipeline_result_syncs EXTRACTION → RESOLVED, 탭1 이 첨부할 추출 Job 고정ReportDraftLlmService → StudioReportLlmAdapter (탭1 → 탭2 → 탭3)POST /responses LLM 직접 호출 — AI 보고서 생성report_draft_llm_tabs 탭마다 1행(ai_pipeline_job_id 에 묶임)SummaryOutputProjector)근거: V202608190005·V202608220002(v1 정의 시드), V202610220938(v2 정의), V202610221430(GENERATE_DRAFT 끔), ReportTabsStepWorkPortImpl, WorkflowStepCompletionPortImpl, ExtractionPreconditionPortImpl, AiPipelineJobLedger
분류 호출 상세
보내는 것
POST /responses
{
"model": "<분류 에이전트 id>",
"config_id": "<분류 지시서 id>",
"input": [{ "role": "user", "content": [
{ "type": "input_file", "file_id": "file_…" }, … ← 묶음 PDF, 순서대로 이어 붙인 쪽 = 전체 쪽 번호
]}],
"background": true, "stream": false,
"metadata": { "report_no": "…", "step_code": "CLASSIFY" }
}
업로드: POST /files multipart(purpose=user_data), 파일 이름 {접수번호}-part-001.pdf. 파일명은 기본 한글 그대로(AI_PLATFORM_STUDIO_UPLOAD_FILENAME=utf8), 한글을 못 받는 Studio 로 되돌릴 때만 ascii. Content-Length 로 보냅니다(chunked 면 임대 서버 게이트웨이가 415).
읽는 것
output[]중step_type = document-classify의content[]하나 = 문서 하나.text= 라벨.additional_values는 JSON 문자열이라 한 번 더 파싱합니다.- 신뢰도:
confidence→document_type.confidence_score→confidence_score순으로 찾고, 0~1 밖이면 버립니다. - 낱장 대응: 쪽 번호는 입력 파일을 순서대로 이어 붙인 전체 쪽입니다.
page_ranges(1부터, 끝 포함)를 색인InputPages(입력 파일 순서 × 파일 안 쪽 → 낱장)로 낱장에 옮깁니다. 결과가 파일 id 만 가리키면 그 파일이 한 쪽일 때만 파일로 되짚습니다 — 묶음 PDF 는 파일 하나에 문서가 여럿이라 쪽 범위로 가릅니다. - 업스테이지 제공 분류 에이전트는 연속된 쪽을 문서 하나로 묶기도 합니다(문서 = 파일 + 쪽 범위). 그래서 문서 하나가 낱장 여러 장을 덮을 수 있습니다.
- 판정: 라벨이 미분류 목록(
기타·미분류등)이면 UNCLASSIFIED, 신뢰도가 없거나 0.700 미만이면 REVIEW_REQUIRED, 나머지 CLASSIFIED.
label_not_in_config / collection_result_schema_mismatch) 추출이 실패합니다. 분류 문서종은 에이전트 스튜디오 1단계에서 관리하고, 배포할 때 수동분류체계-자동분류체계 매칭에 반영합니다(결정 20261020-studio-deploy-pending-classification-mapping).검수 보정 반영 왕복
사람이 고친 분류 라벨·추출값을 Studio 의 앞 Job 결과에 써 넣는 과정입니다. 어댑터가 Collection 마다 review-capabilities 를 물어 두 경로 중 하나를 고릅니다(1시간 캐시). 설정 변경은 필요 없습니다.
ai_pipeline_result_syncs 의 phase 는 EXPORT_REQUESTED → EXPORT_READY → IMPORT_UPLOADED → IMPORT_COMMITTED → IMPORT_READY → RESOLVED(또는 FAILED) 로 진행합니다. 검수 경로의 참조값은 review: 접두어(review:document, review:<collection>:<import>)로 구분합니다.언제 도는가
- 분류: 추출을 보내기 직전, 이전 값과 다른 문서종으로 수동 지정한 보정이 있을 때만. AI 값을 그대로 확정한 것은 보낼 것이 없습니다.
- 추출: AI 보고서 탭1(v1 은 요약)을 보내기 직전, 분석 출처가
EXTRACT(기본,ANALYSIS_SOURCE_STEP)일 때. 저장된(STORED) 항목 중output_pointer가 있는 값만 보내고, 마스킹·미저장 항목은 SKIPPED. - 쪽 단위: 보정이 여러 쪽 파일의 낱장을 가리키면 파일 안 쪽 번호(
pageInFile)를 함께 보냅니다. 검수 JSON 경로는 문서의source_files[].page_ranges를 쪽으로 펴서 그 쪽만 새 문서로 떼고 이어진 쪽은 다시 접습니다. 제자리 교체 경로(임대 서버)는 파일 단위 자리만 알아 쪽 단위 보정을 건너뜁니다(경고 로그). - 같은 결과에 이미 RESOLVED 된 반영이 있고 보정이 더 없으면 다시 돌지 않습니다.
실패하면 — AI_PIPELINE_CORRECTION_SYNC
REQUIRED(기본): 반영 실패 = 단계 실패. 사람이 고친 값이 빠진 채 진행하지 않습니다.BEST_EFFORT: 원 Job 으로 그냥 진행.OFF: 반영하지 않음.- 제한 시간
AI_PIPELINE_CORRECTION_SYNC_TIMEOUT기본 30분.
apply=auto 를 쓰지 않나. 10/12 문서에 preview 승인 없이 바로 반영하는 apply=auto 가 생겼지만, 그러면 지시서 체계 밖 라벨(label_not_in_config)이 경고만 남고 반영됩니다. 우리는 그 경우를 거절해야 해서 manual(preview 확인 후 반영)을 유지합니다.409 original_replace_retired 입니다. 임대 서버(1.1.1)에는 검수 JSON 경로가 없어 그 시점부터 보정 반영 방법이 없어집니다. 운영(1.6.12)은 규약상 검수 JSON 경로가 있으나 실호출 전이라 미확인입니다. 지금 코드는 무한 재시도만 막아 둔 상태입니다(#834).AI 보고서 생성 (LLM 탭)
v2 의 「AI 보고서 생성」(단계 GENERATE_REPORT_TABS)은 Studio 에이전트가 아니라 LLM 을 탭마다 1번씩 직접 부릅니다(결정 20261020-report-generation-llm-input-job, 통합 설계 v0.3 ⑥). 위탁사 템플릿의 탭 순서대로 부르고, 앞 탭이 실패하면 뒤 탭도 함께 실패합니다.
보내는 것 — StudioReportLlmAdapter
POST /responses
{
"model": "auto", ← 탭별로 바꿀 수 있음
"input": [{ "role": "user", "content": [
{ "type": "input_job", "job_id": "<추출 Job>",
"include_parse": false }, ← 탭1 만
{ "type": "input_text", "text": "<탭 프롬프트>" }
]}],
"text": { "format": { "type": "json_schema", "name": "…",
"strict": true, "schema": { … } } },
"stream": true, "background": false
}
응답은 SSE 로 끝까지 읽습니다. Response id 를 받는 즉시 원장에 남기고, 응답을 잃으면 다시 보내지 않고 GET /responses/{id}(X-Use-Studio-Chat: true)로 다시 받습니다.
탭별 입력
- 탭1: 추출 Job 을
input_job으로 첨부 + 탭1 프롬프트. 첨부할 Job 은 단계가 고정합니다 — 출처EXTRACT(기본)면 결과 왕복으로 고친 값을 Studio 추출 결과에 되돌린 뒤 그 Job,CLASSIFY면 왕복 없이 고정합니다. - 수정값 줄: 두 구성 모두 탭1 지시문 끝에 「원문 값 → 수정값」 줄과 「수정값을 사실로 보고 그 값만 쓴다」 지시를 붙입니다(
AnalysisSourceContext.correctionsBlock). 같은 문서종·낱장·항목은 마지막 값 하나로 합치고, 원문과 같으면 뺍니다. 10/22 E2E 에서 왕복만으로는 모델이 다른 문서 값을 써서 넣었습니다. - 탭2·탭3: Job 첨부 없이 앞 탭 JSON 을
{{이전결과}}로 받습니다. - 템플릿: 관리자 에이전트 스튜디오 3단계 INSTRUCT 설정(DB)의 건에 고정한 판(인입 시점, 결정 20261028)에서 그 위탁사의 사용 중인 탭을 표시 순서대로 읽습니다(
StudioInstructTemplateSource, 결정 20261023-report-llm-studio-instruct-source). 지시문 = 탭 프롬프트 → 고른 보고서 유형의 탭 본문(조건별 작성지침) → 공통 작성 규칙, 출력 JSON 골격은text.format스키마로 보냅니다. 화면에서 고치면 그 뒤 인입된 건부터 쓰입니다. 골격 칸이 JSON Schema 면 변환 없이 그대로 보냅니다(결정 20261029). - 결과: 탭 출력 JSON 을 요약 결과(탭 원형)로 앉힙니다. 구역 키는 화면의 탭 키
tab-{위탁사 코드}-{탭 키}(스마트심사 매핑 기준), 구역 이름·반영 방식은 스튜디오 탭에서 옵니다. 키 순서는 출력 JSON 골격 순서로 맞추고, 탭1 의 근거 필드(근거)는 본문 카드 뒤로 갑니다. 신한 10/29 판은 최상위근거대신 사실 항목마다요약근거[{job_id, occurrence_id}]를 달고 낱장 출처로 잇습니다(결정 20261029-shinhan-summary-evidence-occurrence).
AI_PLATFORM_STUDIO_REPORT_*, 업스테이지 보고서 탭 에이전트)는 v2 가 쓰지 않습니다 — 설정이 남아 있어도 기동은 막지 않습니다. 청구병원 선택 자리가 아직 없어 {{선택된병원}} 이 비면 「앞 결과에 나온 병원을 모두 대상으로 한다」로 채웁니다(TODO(decision-needed)).근거: AiReportTabsJobService, ReportTabsStepWorkPortImpl, ReportDraftLlmService, StudioReportLlmAdapter, 원장 report_draft_llm_tabs(V202610211526·V202610220939)
그룹 요약 (Agent D)
문서 요약은 요약 그룹마다 1번 업스테이지 요약 에이전트(2-2 수정본, import → merge → instruct)를 부릅니다(결정 20261019-studio-summary-tab-doc-groups). 워크플로 단계가 아니라 곁가지라 본선 상태를 바꾸거나 막지 않고, 결과는 AI 보고서 확인~검수 화면의 마지막 「요약」 탭에 보입니다.
걸음 — AiSummaryGroupJobService · StudioGroupSummaryAdapter
- 추출 Job 을 제출할 때(
ExtractionSubmittedEvent) 그 분류 Job 으로 묶음(summary_group_batches) 1개를 만듭니다. v2 이고 포트가 있을 때만입니다. GET …/review-capabilities로selective_import_supported를 확인하고(1시간 캐시, 거짓이면 그룹 실패),GET …/review-document?job_ids=<분류 Job>로 문서 id·라벨·구조 버전을 읽습니다.- 건에 고정한 판(
summary_group_versions, 결정 20261028)의 그룹 마스터(summary_groups·summary_group_document_types, 한 문서종은 한 그룹에만)로 문서를 그룹에 나눠 그룹마다 1회 제출합니다. - 폴러가 결과를 받아
summary_group_runs에 글과 인용(쪽·좌표)을 남깁니다.
POST /responses (Idempotency-Key)
{ "model": "<요약 에이전트>", "config_id": "…",
"review_selector": [{ "job_id": "<분류 Job>",
"structure_version": N, "document_ids": ["rdoc_…"] }],
"input": [{ "role": "user", "content": [
{ "type": "input_text", "text": "<그룹 프롬프트>" } ]}],
"background": true, "stream": false }
다시 만들기 · 재사용
- 분류 Job 이 바뀌면(다시 분석·추가 서류) 새 묶음을 만들고 앞 진행 중 묶음은 SUPERSEDED 로 닫습니다.
- 같은 분류 Job 으로 진행 중·완료된 묶음이 있으면 만들지 않습니다(추출부터 다시 분석해도).
- 실패한 묶음은 다음 추출 제출 때 다시 만들고, 같은 입력(fingerprint)으로 성공했던 그룹은 다시 부르지 않습니다(REUSED).
source_job_id·input_file은 싣지 않습니다.- 에이전트·지시서를 따로 주지 않으면 요약(
analysis) 설정의 것을 씁니다.
화면 · API
GET /api/v1/investigation-reports/{reportNo}/summary-groups/latest— 진행 중이면 화면이 10초마다 다시 받습니다.- 관리자 그룹 편집
GET·PUT /api/v1/admin/agent-studio/summary-groups
v1 요약(Instruct) 호출
v1 정의(ANALYZE)로 시작한 실행만 이 길을 탑니다. 요약 에이전트를 사건당 1회, 추출 Job(출처 CLASSIFY 면 분류 Job)을 source_job_id 로 이어받아 부릅니다. v2 는 이 단계 대신 LLM 탭을 부릅니다.
지시문 전달 — AI_PLATFORM_STUDIO_ANALYSIS_INSTRUCTION_DELIVERY
auto(기본):input_text로 먼저 보내고,400 import_input_invalid(임대 서버)이면 실행별 지시서 방식으로 넘어갑니다. 출력 골격이 있으면 처음부터 지시서 방식.input-text:input_text만. 거절되면 실패.config-version: 기준 지시서를GET /agents/{a}/configs/{c}로 읽어 Instruct 의queries·response_format(json_schemahwss_summary_sections)을 바꾼 비기본 지시서를POST /agents/{a}/configs(이름hwss-run …)로 만든 뒤 그 지시서로 제출.
결과 읽기 — StudioAnalysisOutputParser
- 진행 중에는
?include=last로 가볍게 보고,completed일 때만?include=all로 받아step_type = instruct출력을 읽습니다(분류·추출도 같은 방식,StudioJobPoll). - SaaS 의
Import → Merge → Instruct는 같은 글이 문서 수만큼 반복되어 와서 한 번만 읽습니다. - Merge 없는 구성(임대 서버)은 문서마다 Instruct 가 돌아 카드를 순서대로 이어 붙입니다.
text는 두 겹까지 감싼 JSON 을 벗기고, 인용 표식【†n】을 걷어냅니다.- 형태: 우리 골격
sections[]/ 지시서가 정한 탭 객체 / 카드 배열 / 그 밖의 글(요약 카드 1장).
지식컬렉션 선택은 요약 지시문에 컬렉션 id 를 글로 적는 데까지만 쓰입니다(벡터 검색 연동 없음). 실행별 지시서가 계속 쌓이는 문제는 열린 항목입니다.
지식컬렉션 (Vector Store)
컬렉션 도메인이 Studio 로 나가는 통로는 CollectionVectorGateway 하나이고, 구현은 StudioVectorStoreAdapter 입니다. Store 는 에이전트에 묶지 않은 standalone 입니다.
| 우리 동작 | Studio 호출 | 메모 |
|---|---|---|
| 공용 컬렉션 생성(승인 때) | POST /vector_stores (기준 폴더의 첫 컬렉션 때만) | Store 는 기준 폴더(2depth)마다 1개 collection_folder_vector_stores, 3depth 는 파일 description 으로만 구분(결정 20261013-shared-collection-2depth-vector-store). metadata {"scope":"공용"}(10/22 이전 Store 는 demo_scope 그대로). 실패하면 컬렉션 생성도 함께 502 |
| 개인 컬렉션 생성 | POST /vector_stores (사용자 첫 컬렉션 때만) | 사용자당 Store 1개 personal-<사번> 공유, collection_owner_vector_stores (#829) |
| 파일 올리기·색인 | POST /files → POST /vector_stores/{vs}/files | 속성 attributes {"description":"폴더/경로/파일명"} 하나(#860). 경로까지 이 속성으로 보존(업로드 파일명은 기본 UTF-8, ascii 설정 때만 한글이 _). 기본 COLLECTION_INDEXING_MODE=batch 는 등록·승인을 처리 대기로 두고 21:00~06:00 창에 보내고, immediate 는 커밋 뒤 작업자가 처리 |
| 색인 상태 | GET /vector_stores/{vs}/files/{f} | 색인 폴러 10초 간격, 30분 제한(작업자·배치는 문서 조각 단위로 잰다) |
| 파일·폴더 이름 변경 | POST /vector_stores/{vs}/files/{f} | 속성만 통째로 교체, 다시 색인하지 않음 |
| 파일 삭제 | DELETE /vector_stores/{vs}/files/{f} → DELETE /files/{f} | 404 는 이미 지워진 것으로 봄 |
| 공용 전환 | POST /vector_stores → 같은 file_id attach → 사용자 Store 에서 detach | attach 가 거절되면 다시 업로드. 같은 File 재사용은 아직 실측 전 |
| 검색 | POST /vector_stores/{vs}/search | Store 마다 1회 후 점수순 병합. 사용자 Store 는 필터 없이 5배(최대 50)를 받아 DB 의 파일↔컬렉션 대응으로 거름 |
| 원본 내려받기 | GET /files/{id} → file_url | 서명 URL 이라 인증 헤더 없이 받음 |
{"type":"eq","key","value"} / and 트리만 받습니다(평평한 맵은 400). 지식컬렉션 Store 에는 접속 대상(온프렘·SaaS) 구분이 없어, 대상을 바꾸면 기존 Store id 가 「없음」이 됩니다.채팅
채팅은 기본이 스텁(StubChatModelAdapter)이고, AI_PLATFORM_PROVIDER=studio 에 AI_PLATFORM_STUDIO_CHAT_ENABLED=true 면 StudioChatAdapter 가 질문 하나에 Studio 를 세 번 부릅니다(결정 20261021-chat-two-stage-intent-file-search 10항, 통합 설계 v0.4). 화면은 답변 출처를 STUB/LIVE 로 표시합니다.
① 의도 게이트
POST /responses,stream:false, conversation 없음,reasoning.effort:"none"·temperature:0, function 툴 1개(기본 이름intention-classify, 인자mode만)를tool_choice로 강제합니다. 모델 기본solar-pro4.- 답:
mode하나(GENERAL·FOLLOWUP·DOCUMENT). 세 값이 아니거나 실패하면DOCUMENT입니다.
② 컬렉션 라우팅
POST /vector_stores/route.GENERAL이면 부르지 않습니다. 요청은query, 공용 Store 전부(vector_store_ids),missing_description:"exclude",include_reasons:true, 있으면conversation.model은 서버 설정이 정합니다.- 실패하면 고른 것 없이 이어 갑니다. 지정·개인 Store 는 라우팅과 무관하게 ③ 에 합칩니다.
③ 본 답변
stream:true,background:false,instructions= 답변 규칙 + 고정 전제 + 범위 밖 정책.- 검색이 필요한 턴에만
file_search(vector_store_ids,max_num_results30)를, 첨부가 있는 턴에만doc_chat:auto를 선언합니다. 출처는include:["file_search_call.results"]로 받습니다. - 후속 질문은 이번 질문만 싣고
conversation:{id}(객체 — 문자열이면 400)로 잇습니다. SSE 의 마지막response.conversation.id를chat_sessions.external_conversation_id에 둡니다(결정 20261020-chat-studio-conversation-continuation).
사고조사 챗봇
- API
/api/v1/investigation-reports/{reportNo}/chat(InvestigationChatController) — 질문·첨부·지식 선택·삭제, 답변의 AI 보고서 반영(…/messages/{id}/reflect). 저장 구조는 결정 20261023-investigation-chat-session-structure. InvestigationTurnPrompt가 턴 지시문을 조립하고, 어댑터가 같은 보고서의 분류·추출 Job 을input_job으로 첨부합니다. 사건 서류 Store 는 분류 에이전트의 Store 입니다(StudioCaseDocumentStoreAdapter,GET /agents/{a}).- 범용 챗봇과 같은 세 호출입니다. 통합 설계 v0.4 는 「의도 분류는 없다」, 업스테이지 회신은 「동일하게 3단계」라 확인 중입니다(
docs/external/README.md).
- 첨부는
ChatAttachmentPreparationService가 파일당 50MB·대화 누적 100쪽 미만(파일 수 제한 없음, 결정 20261026·20261030)을 검사하고 PDF 로 바꿔POST /files로 올린 뒤(원장chat_attachments), 질문 때input_file(file_id)로 싣습니다. - 임대 서버 Studio Chat 은 tiktoken 인코딩 내려받기 실패로
response.failed입니다(9/29·10/1 실측).
사용 API 계약 (SaaS 기준)
백엔드가 실제로 호출하는 것만 모았습니다. 경로는 base URL(…/v2) 기준입니다. 모든 요청은 Authorization: Bearer, project-id 설정이 있으면 x-project-id 를 싣습니다. 추출·요약·내보내기·검수 업로드에는 원장에 저장한 무작위 Idempotency-Key 를 붙입니다(키는 API 키 단위 전역이라 조립형 키는 422 idempotency_key_reused). 예외로 POST /vector_stores/{id}/files 는 스토어·파일·속성의 SHA-256 결정적 키를 씁니다.
| 메서드 · 경로 | 용도 · 핵심 필드 | 호출하는 클래스 |
|---|---|---|
| 에이전트 · 지시서 · Collection 찾기 | ||
| GET /agents/{a}/configs/{c} | 요약 실행별 지시서의 바탕 / Export 스텝 table_key | StudioAnalysisAdapter, StudioCollectionResolver |
| POST /agents/{a}/configs | 실행별 비기본 지시서 is_default:false | StudioAnalysisAdapter |
| GET /search-collections?agent_id= | table_key + 종류 agent_export 로 Collection id 찾기(after 페이지) | StudioCollectionResolver |
| GET /agents/{a} | default_config_id, 사건 서류 Store(vector_store_id) | StudioAgentDefaultConfigs, StudioCaseDocumentStoreAdapter |
| GET /agents/{a}/configs | 지시서 버전 목록 | StudioAgentConfigVersions |
| GET /agents/{a}/jobs | 접수 결과를 모르는 분류 제출 대조 | StudioClassificationAdapter |
| Files | ||
| POST /files | multipart file + purpose=user_data. 사건 묶음 PDF·컬렉션 문서·채팅 첨부, 파일명 기본 UTF-8, Content-Length | StudioDocumentFileAdapter, StudioVectorStoreAdapter, 채팅 첨부 준비 |
| GET /files/{id}?view=status&pages=none | 준비 상태(PROCESSING / UPLOADED·READY / FAILED), hash, file_url | 같음, StudioResultCorrectionAdapter |
| DELETE /files/{id} | 컬렉션 문서 제거 때 원본 삭제 | StudioVectorStoreAdapter |
| DELETE /files/{id}?purge_derived=true | 파기 — 그 File 을 입력으로 쓴 Job 까지 지움 | StudioDocumentFileAdapter |
| Responses — 분류 · 추출 · 그룹 요약 · v1 요약 Job | ||
| POST /responses | model=에이전트, config_id, input[].content[](input_file · 요약은 input_text), background:true, 추출·v1 요약은 source_job_id | StudioClassificationAdapter, StudioExtractionAdapter, StudioAnalysisAdapter |
POST /responses (review_selector) | 그룹 요약 — [{job_id: 분류 Job, structure_version, document_ids}], Idempotency-Key. source_job_id 와 함께 보내면 400 | StudioGroupSummaryAdapter |
| GET /responses/{id}?include=last → ?include=all | 진행 상태는 last 로, completed 면 all 로 결과. failed → AI_JOB_FAILED, cancelled → AI_JOB_CANCELLED. 검수 반영 뒤에는 반영본과 review_versions | 같음 |
| DELETE /responses/{id} | 파기 때 Job 삭제 / 채팅 턴을 대화 맥락에서 뺌(404 는 성공으로 봄) | StudioDocumentFileAdapter, StudioChatAdapter |
| Responses — LLM 직접 호출 · Studio Chat | ||
| POST /responses (stream) | AI 보고서 탭 — model(기본 auto), 탭1 input_job(include_parse:false), input_text, text.format json_schema strict, SSE | StudioReportLlmAdapter |
| GET /responses/{id} | 스트림을 잃은 LLM 탭 응답 다시 받기, 헤더 X-Use-Studio-Chat: true | 같음 |
| POST /responses (chat) | ① 의도 게이트 tools+tool_choice, ③ 답변 stream:true·instructions·file_search·doc_chat·input_job(사고조사)·conversation:{id} | StudioChatAdapter |
| POST /vector_stores/route | ② 컬렉션 라우팅 — query, vector_store_ids, missing_description, include_reasons, conversation | 같음 |
| DELETE /conversations/{id} | 대화 삭제(404 는 성공으로 봄) | 같음 |
| Job 검수 JSON — SaaS 1.4.1+ | ||
| GET /search-collections/{c}/review-capabilities | 지원 판정, max_document_jobs·max_inline_import_bytes·max_import_part_bytes·result_source_supported·apply_modes | StudioJobReviewRoundtrip |
| GET …/review-document?job_ids= | 동기 내려받기(최대 100 Job · 16 MiB, 약한 ETag). 그룹 요약은 문서 id·구조 버전을 여기서 읽는다(selective_import_supported 확인 뒤) | 같음, StudioGroupSummaryAdapter |
| POST …/review-exports · GET …/review-exports/{e} | 비동기 내려받기(옛 판·진행 중 왕복 호환), download_url 15분 | 같음 |
| POST …/review-imports?apply=manual&sha256=&client_reference= | 본문 업로드(octet-stream, 8 MiB 이하) | 같음 |
| POST …/review-imports → PUT …/parts/{n} → POST …/complete | 분할 업로드 | 같음 |
| GET …/review-imports/{i} | preview · ETag | 같음 |
| POST …/review-imports/{i}/apply · …/cancel | If-Match: <ETag> 반영 / 거절 때 취소 | 같음 |
| 결과 왕복(제자리 교체) — 임대 서버 | ||
| POST /search-collections/{c}/result-snapshots · GET …/{e} | Collection 결과 내보내기 → download_url(인증 없이 받음) | StudioResultCorrectionAdapter |
| POST /search-collections/{c}/result-context/search | 파일 ↔ output_pointer·editable_paths | 같음 |
| POST /file-collection-imports · GET …/{i} | 고친 JSON 업로드 → ready_for_plan | 같음 |
| PUT /file-collection-imports/{i}/plan · POST …/commit | target_collection_id = 원본. 막히면 409 original_replace_retired | 같음 |
| POST /search-collections/{c}/source-jobs/search | 교체 뒤 새 Job 찾기(최신순) | 같음 |
| Vector Stores | ||
| POST /vector_stores · POST /vector_stores/{id} · DELETE … | 생성(metadata) · 이름 변경 · 삭제(File 은 남음) | StudioVectorStoreAdapter |
| POST /vector_stores/{id}/description/generate | 공용 Store 설명 생성(라우팅 재료) | StudioVectorStoreAdapter |
| POST /vector_stores/{id}/files | {file_id, attributes} 색인 / 기존 File 붙이기 | 같음 |
| GET · POST · DELETE /vector_stores/{id}/files/{f} | 상태 / 속성 교체 / 떼기. 떼기·삭제가 409 vector_store_file_delete_conflict 면 Retry-After(없으면 2초) 뒤 2회까지 다시 보냄 | 같음 |
| POST /vector_stores/{id}/search | query, max_num_results(1~50), filters 트리 | 같음 |
부르지 않는 것
GET /version(점검 도구만 부름), POST /responses/{id}/cancel(점검 도구만), source_job_ids, apply=auto, source-jobs/batch-delete, result_container 계열(10/12 제거). Agent 방식 보고서 탭(StudioReportAdapter, 업스테이지 보고서 탭 에이전트 + 빈 Job)은 코드는 있지만 v2 에서 부르지 않습니다.
계약 변경 이력
업스테이지 문서 번들(studio-api-v260922 → v261006 → v261012), 그 뒤 리포 사본(docs/external/studio/, v1.6.4 → v1.6.5, 개발자 가이드, 통합 설계 v0.3 → v0.4)과 실측으로 알게 된 변화, 그리고 우리가 한 조치입니다. 최신이 위입니다.
| 날짜 | 바뀐 것 | 우리 조치 |
|---|---|---|
| 2026-10-29 — 통합 설계 v0.4 | ||
| 10-29 | 통합 설계 v0.4(「챗봇 & 컬렉션」 탭만 변경): 질문 하나에 ① 의도 게이트 → ② POST /vector_stores/route → ③ 답변, 라우팅 재료는 컬렉션 설명 앞 600자 | 채팅 3호출(결정 20261021 10항) — #1626. 설명 앞 600자 품질 확인은 후속 |
| 2026-10-21 ~ 10-22 — 통합 설계 v0.3 · 파이프라인 v2 | ||
| 10-22 | (우리 쪽) v2 실연동 E2E — EXTRACT 구성은 Studio 추출 결과에 고친 값이 있어도 모델이 다른 문서 값을 씀 | 탭1 지시문에 수정값 줄을 두 구성 모두 싣는다 — 결정 20261021 「추가」 개정 |
| 10-22 | 업스테이지 회신: Job 당 파일 100개 상한은 파일 수 기준(이어받기에서만 검사, 분류 Job 생성은 통과) | 사건 단위 여러 쪽 PDF(500쪽마다 나눔)로 업로드, 쪽 단위 보정 왕복 — 결정 20261021-case-documents 🟢 #1304 |
| 10-22 | (우리 쪽) 빈 단계 GENERATE_DRAFT | v1·v2 모두 enabled=false — #1326 |
| 10-21 | 통합 설계 v0.3: 보고서 초안 = LLM 탭마다 1회, 문서 요약(Agent D) = 분류 검수 직후 요약 그룹마다 1회 | 파이프라인 v2(GENERATE_REPORT_TABS + 그룹 요약 곁가지) — 결정 20261021 🟢 #1263 #1271 |
| 10-21 | 통합 설계 v0.3: 범용 챗봇 2단계(의도 분류 → 답변) | 라우터 + file_search·doc_chat·첨부 인용 — #1262 #1287 |
| 10-21 | (우리 쪽) 409/412 경합 일시 오류가 일반 재시도 한도(5회)에 같이 묶여 있음 | 경합은 별도 한도 AI_PIPELINE_MAX_CONFLICT_ERRORS(15) — #1286 |
| 10-20 | Studio Chat conversation 은 객체 {id} 로만 받음 | 후속 질문을 Studio conversation 으로 잇기 — #1231 |
| 2026-10-20 — 규약 v1.6.5 (Prod 배포 10-16) · 개발자 가이드 10-20 판 | ||
| 10-20 | 가이드: 보고서 생성1·2~ 을 LLM 호출(input_job=추출 Job, 이전 답변 input_text, text.format JSON Schema)로, 사고조사 챗봇은 분류·추출 Job 을 input_job 으로 첨부(베타) | 보고서 LLM 방식 결정 #1230 → 10/21 v2 로 확정. 사고조사 챗봇 Job 첨부는 미구현 |
| 10-20 | Vector Store 파일 삭제 409 vector_store_file_delete_conflict(부분 삭제 없음, 같은 요청 재전송 가능) | 문서 제거·연결 해제가 2회까지 다시 보냄 — #1223 |
| 10-20 | Collection 목록 기본값이 자동 생성 기본 Collection 을 숨김(listed 기본 true, include_default) | Export Collection(agent_export)은 그대로 나와 해석 영향 없음 |
| 10-20 | Studio Chat 첨부 인용이 태그 대신 file_citation annotation, 서버 툴 루프 상한, input_job·reasoning.effort·text.format | 태그 해석 코드가 없어 영향 없음. annotation·input_job 은 후보 |
| 10-20 | review_selector 문서·파일 선택 이어 실행(selective_import_supported) | v2 그룹 요약이 문서 선택으로 씀(지원 여부를 Collection 마다 확인) — #1271 |
| 2026-10-12 — 번들 v261012 (Collection 문서 갱신) | ||
| 10-12 | GET review-document 동기 내려받기(100 Job · 16 MiB, ETag/304) | 동기 경로로 전환, capabilities 키로 판별 — #836 |
| 10-12 | 본문 업로드 review-imports?apply=manual|auto(8 MiB, octet-stream; JSON 이면 415) | apply=manual 로만 사용 — #836, 실접수 실측 #839 |
| 10-12 | apply=auto(전부 아니면 무), units[].applied_versions | 쓰지 않음(체계 밖 라벨 거절 불가) |
| 10-12 | review-capabilities 에 result_source_supported·apply_modes | false 면 미지원 판정 — #834 |
| 10-12 | 원본 Collection 제자리 교체가 막히면 409 original_replace_retired | 영구 409 3종 비재시도 — #834. 온프렘 보정 반영 경로 소멸 위험은 남음 |
| 10-12 | Job 조회가 검수 반영본과 review_versions 를 돌려줌, review_selector 형식 공개 | 반영본 확인(10/12 실접수), review_selector 는 불필요 |
| 10-12 | result_container 와 전용 API 제거, batch-undelete 퇴역, agent_link 동기화 제거 | 쓰지 않아 영향 없음 |
| 10-12 | Collection 종류는 kind 가 정본, source_type deprecated. Agent 기본 Collection(is_default) | 아직 source_type/source.type 을 읽음(계속 내려옴). 필드가 사라지면 바꿔야 함 |
| 10-12 | source_job_ids 오류 코드 세분화, Import collection_ids(1~10) | v2 설계 때 반영 |
| 10-12 | (우리 쪽) 개인 컬렉션 사용자당 Store 1개, 파일 속성 description | #829 #860 |
| 2026-10-06 — 번들 v261006 (Job 검수 JSON SaaS 배포, pipeline 1.4.1) | ||
| 10-06 | review-capabilities·review-exports·review-imports·apply SaaS 배포. 온프렘은 404 | 검수 JSON 경로 추가, 지원 여부로 경로 선택, review: 접두어 — #711 |
| 10-06 | 반영해도 Job id·File id 불변, source_job_id 이어 실행이 반영값을 읽음(실측) | 검수 경로는 새 Job 찾기 생략 |
| 10-06 | 합친 Job 재 Import 결함(1.3.1) | 1.4.1 에서 재현 안 됨, v1 영향 없음 |
| 2026-10-01 ~ 10-02 — SaaS 구성 | ||
| 10-01 | SaaS(1.2.3/1.2.0) 전 구간 실측: input_text 수락, Import→Merge→Instruct 동작(반복 글) | 접속 대상 전환·플랫폼 식별자·반복 글 접기 — #669 |
| 10-02 | 흐름 변경은 정의 버전으로 나란히 | AI_PLATFORM_STUDIO_PIPELINE_VERSION — #678 |
| 2026-09-22 ~ 09-29 — 번들 v260922 기준 첫 연동 | ||
| 09-29 | 결과 왕복(제자리 교체 v2.1): Job·File id 새로 발급 | 보정 반영 1~3단계 #615 #618 #626 #621, Collection 자동 해석 #629 |
| 09-29 | 결과 조회가 최대 83초, 본문 읽다 끊김 | IOException 재시도, timeout 기본 120s — #622 |
| 09-28 | Instruct 실측: 온프렘 input_text 거절, text 이중 인코딩·【†n】 | 지시서 버전 폴백 #599, 출력 골격 #619 |
| 09-28 | Vector Store standalone · 파일 직접 색인 | #601 |
| 09-25 | 추출 이어 실행 source_job_id | #590 #591 #594 |
| 09-23 | 분류 응답 형태 확인, 업로드 415(chunked·비 ASCII 파일명) | #576 |
임대 서버 · SaaS 차이
| 항목 | 임대 서버 (pipeline 1.1.1) | SaaS (규약 v1.6.5) | 우리 처리 |
|---|---|---|---|
| 인증 | Keycloak 오프라인 토큰 | up_… API 키 | 같은 Bearer 헤더 |
요약 input_text | 400 import_input_invalid | 수락 | instruction-delivery=auto 폴백 |
Import→Merge→Instruct | 실행 실패 | 동작, 같은 글 반복 | 파서가 반복 접기 |
| 검수 JSON | review-capabilities 404 | 지원(review-document·본문 업로드 포함) | 응답으로 경로 선택 |
| 제자리 교체 | 동작(id 재발급) | 막히면 409 original_replace_retired | 비재시도 |
| 결과 스냅샷 형식 | collection-results/2 | v2.1 | 둘 다 처리 |
| Collection 종류 필드 | 최상위 source_type=null, source.type | kind 정본 + source_type | 두 자리 모두 읽음 |
| 결과 조회 시간 | 보통 1~5초, 최대 83초 | 약 1초 | timeout 120s |
Store file_counts | 갱신 안 됨 | 갱신 | 쓰지 않고 파일 단건 조회 |
| Studio Chat | response.failed | response.completed | 채팅 어댑터는 SaaS 기준(기본 꺼짐) |
| 쪽 단위 보정(묶음 PDF) | 제자리 교체는 파일 단위 자리만 — 건너뜀(경고 로그) | 검수 JSON page_ranges 로 쪽 단위 | 경로별 처리 |
v2 (review_selector·LLM input_job) | 확인 안 함 | 실연동 E2E 10/21~22 | selective_import_supported 가 거짓이면 그룹 요약 실패로 닫음 |
설정 · 접속 대상 전환
AI_PLATFORM_STUDIO_TARGET=<대상> 을 주면 AI_PLATFORM_STUDIO_<대상>_* 값을 모두 AI_PLATFORM_STUDIO_* 로 옮겨 씁니다(StudioTargetEnvironmentPostProcessor). 플랫폼 식별자는 기본 studio-<대상> 이 되어 원장 provider 열에 남고, 대상을 바꾸면 진행 중 작업은 AI_PROVIDER_CHANGED 로 닫힙니다(낱장은 새 대상에 다시 올라갑니다).
| 환경변수 | 기본 | 뜻 |
|---|---|---|
| AI_PLATFORM_PROVIDER | none | none = 메모리 스텁, studio = 실연동 |
| AI_PLATFORM_STUDIO_TARGET | — | 대상 이름(예 saas, onprem) |
| AI_PLATFORM_STUDIO_PROVIDER_ID | studio | 원장 플랫폼 식별자 |
| AI_PLATFORM_STUDIO_BASE_URL / _API_KEY | 필수 | /v2 까지 포함한 주소, 키 |
| AI_PLATFORM_STUDIO_PROJECT_ID | 비움 | x-project-id 헤더(운영 필요 여부 미정) |
| AI_PLATFORM_STUDIO_TIMEOUT | 120s | 접속·읽기 공통 |
| AI_PLATFORM_STUDIO_CLASSIFICATION_{ENABLED,AGENT_ID,CONFIG_ID,COLLECTION_ID} | false | 분류. 켰는데 id 가 없으면 기동 실패. Collection id 는 선택(지시서로 찾음) |
| AI_PLATFORM_STUDIO_EXTRACTION_{…같은 4개} | false | 추출(Import-first 지시서) |
| AI_PLATFORM_STUDIO_ANALYSIS_{ENABLED,AGENT_ID,CONFIG_ID} | false | 요약 에이전트(v1 요약, 그룹 요약의 기본값) |
| AI_PLATFORM_STUDIO_ANALYSIS_INSTRUCTION_DELIVERY | auto | auto / input-text / config-version |
| AI_PLATFORM_STUDIO_PIPELINE_VERSION | 비움(active = v1) | INVESTIGATION_MAIN 정의 버전 고정, 없는 버전이면 기동 실패. 2 면 새 실행이 v2 로 돌고 LLM 탭·그룹 요약 어댑터가 각자의 ENABLED 없이 등록되며 초안은 조립만(StudioPipelineV2Active) |
| AI_PLATFORM_STUDIO_REPORT_LLM_{ENABLED,MODEL} | false / auto | AI 보고서 LLM 탭(v2 면 ENABLED 없이도 켜짐) |
| AI_PLATFORM_STUDIO_SUMMARY_GROUP_{ENABLED,AGENT_ID,CONFIG_ID} | false / 비움 | 그룹 요약(v2 면 ENABLED 없이도 켜짐). id 를 비우면 ANALYSIS_* 값 |
| AI_PLATFORM_STUDIO_SUMMARY_GROUP_CLASSIFICATION_COLLECTION_ID | 비움 | 그룹 요약이 문서를 읽을 분류 Export Collection(비우면 지시서로 찾음) |
| AI_PLATFORM_STUDIO_REPORT_{ENABLED,FIRST_TAB_*,TAB_*,EMPTY_SOURCE_JOB_ID} | false | Agent 방식 보고서 탭(v1 초안 경로). v2 는 쓰지 않음 |
| AI_PLATFORM_STUDIO_CHAT_{ENABLED,MODEL,ROUTER_MODEL,MODEL_GRANT} | false / auto / solar-pro4 / 비움 | 채팅 실연동, ③ 답변 모델, ① 의도 게이트 모델, Model Grant 헤더. ① 툴 이름은 …_CHAT_ROUTER_TOOL_NAME(기본 intention-classify). 문구는 …_CHAT_{ROUTER_ROLE,ANSWER_RULES,FIXED_PREMISE,OUT_OF_SCOPE_ENABLED} |
| AI_PLATFORM_STUDIO_UPLOAD_FILENAME | utf8 | 업로드 파일명 한글 유지 / ascii |
| ANALYSIS_SOURCE_STEP | EXTRACT | 탭1(v1 은 요약)이 이어받는 앞 Job — EXTRACT / CLASSIFY |
| AI_PLATFORM_STUDIO_AGENT_ID | 비움 | Vector Store 를 묶을 에이전트(비우면 standalone) |
| AI_PLATFORM_STUDIO_FILE_READY_TIMEOUT / _POLL_INTERVAL | 15s / 1s | 업로드 File 준비 대기 |
| AI_PIPELINE_POLLER_ENABLED / _POLL_INTERVAL | true / PT5S | AI 작업 폴러 |
| AI_PIPELINE_JOB_TIMEOUT | PT60M | 작업 제한 시간 |
| AI_PIPELINE_MAX_CONFLICT_ERRORS | 15 | 409/412 경합 재시도 한도(약 13분). 단계 작업에만, 그룹 요약은 일반 한도 |
| AI_PIPELINE_SCAN_PDF_{UPLOAD_ENABLED,DPI,MAX_PART_PAGES} | true / 150 / 1000 | 사건 묶음 PDF 업로드, 쪽 크기 기준, 묶음 하나의 쪽 상한 |
| AI_PIPELINE_CORRECTION_SYNC / _TIMEOUT | REQUIRED / PT30M | 보정 반영 정책 |
| ANALYSIS_INSTRUCT_OUTPUT_SCHEMA | skeleton | skeleton = 우리 골격을 response_format 으로, config = 지시서가 소유 |
| CLASSIFICATION_LOW_CONFIDENCE_THRESHOLD | 0.600 | 분류 저신뢰 기준(추출도 같은 값의 별도 변수) |
| COLLECTION_INDEXING_POLLER_{ENABLED,POLL_INTERVAL,TIMEOUT} | true / PT10S / PT30M | 지식컬렉션 색인 폴러 |
바인딩: StudioProperties, StudioReportLlmProperties, StudioSummaryGroupProperties, AiPipelineProperties, AnalysisProperties · 값: apps/hwss-api/src/main/resources/application.yml · 안내: docs/guides/studio-target-switch-guide.md
원장 테이블
| 테이블 | 한 줄의 뜻 | 핵심 열 | 상태값 |
|---|---|---|---|
| ai_pipeline_jobs | 단계 실행 1회의 Studio Job 1개 | report_no, step_code, attempt_no, provider, external_job_id, input_file_ids(순서 = 쪽), next_check_at, lease_until, idempotency_key | REQUESTED → SUBMITTED → APPLIED / FAILED / DISCARDED |
| ai_pipeline_files | 낱장 1장의 Studio File(묶음 PDF 이전 경로) | provider, report_no, source_page_id, sha256, external_file_id | PROCESSING / READY / FAILED |
| ai_pipeline_documents / _document_pages | 올린 묶음 PDF 1개 / 그 쪽 ↔ 낱장 | first_page, page_count · page_in_part, global_page_no, source_page_id | — |
| ai_pipeline_job_inputs | 분류 요청 당시 입력 파일 순서 | 파일 id, source_page_id(첫 낱장), file_name | — |
| report_draft_llm_tabs | LLM 탭 1회 호출(v2 는 단계 작업에 묶임) | report_no, ai_pipeline_job_id(또는 report_draft_id), tab_key, tab_order, model, input_job_id, response_id, output_json, extra_instruction | REQUESTED → RUNNING → SUCCEEDED / FAILED |
| summary_group_batches | 분류 Job 하나에 대한 그룹 요약 묶음 | report_no, source_job_id, structure_version, next_check_at, lease_until | REQUESTED / RUNNING / COMPLETED / FAILED / SUPERSEDED |
| summary_group_runs | 그룹 1개의 요약 호출과 결과 | batch_id, group_code, document_ids, prompt, fingerprint, external_job_id, result_text, citations_json, reused_from_run_id | PENDING / SUBMITTED / SUCCEEDED / REUSED / FAILED |
| summary_groups / summary_group_document_types | 요약 그룹 마스터 / 묶은 문서종(한 문서종은 한 그룹) | group_code, prompt, display_order | — |
| summary_group_versions | 요약 그룹 마스터의 판 — 건은 인입 시점 판을 읽는다 | version_no | — |
| investigation_settings_snapshots | 건에 고정한 설정 판(결정 20261028) | report_no, 설정별 판(요약 그룹 판 포함) | — |
| ai_pipeline_result_syncs | 보정 반영 왕복 1회 | target_step_code, source_result_kind, corrections_through_id, collection_ref, export_ref, import_ref, resolved_source_job_id, resolved_file_ids | EXPORT_REQUESTED → … → RESOLVED / FAILED |
| classification_results / extraction_results | 결과 본체 | provenance(LIVE/STUB), external_job_id | — |
| classification_corrections / extraction_field_corrections | 사람 보정 | sync_status, external_reference | PENDING / APPLIED / SKIPPED / FAILED |
| collection_owner_vector_stores | 사용자별 개인 Store | 사번, vector_store_id | — |
| chat_attachments · chat_sessions.external_conversation_id | 채팅 첨부의 Studio File / Studio conversation id | file_id, 상태 · conversation id | — |
폴러 걸음(AiPipelineJobLedger.drive): 임대 2분 → 단계·플랫폼 식별자 확인 → 제한 시간 → 진행. 첫 확인 5초 뒤, 2배씩 늘려 최대 60초 간격, 일시 오류 5번(409/412 경합은 15번) 넘으면 실패.
오류 처리 · 재시도
Studio 오류 본문은 {"error":{code,param,type,message}} 이고 분기는 code 로만 합니다. 판정은 StudioDocumentErrors 한 곳에서 합니다. 우리 기록에는 code 만 남기고 message 본문은 남기지 않습니다.
| Studio 응답 | 우리 사유 코드 | 재시도 |
|---|---|---|
| 429 | AI_RATE_LIMITED | 예 |
| 401 · 403 | AI_PLATFORM_AUTH_FAILED | 아니오 |
| 404 | AI_RESOURCE_NOT_FOUND | 아니오 |
| 409 · 412 | AI_RESOURCE_CONFLICT | 예(한도 AI_PIPELINE_MAX_CONFLICT_ERRORS) — 단 original_replace_retired·collection_source_type_unsupported·collection_not_replayable·derived_jobs_limit_exceeded 은 아니오 |
409 import_shape_unavailable | AI_IMPORT_SHAPE_UNAVAILABLE | 아니오 — 앞 단계 Job 형태가 뒤 단계 Config 와 맞지 않음, 화면은 분류부터 다시 분석 안내 |
| 408 · 5xx | AI_PLATFORM_UNAVAILABLE | 예 |
| 연결 실패 · 본문 읽기 중 끊김(IOException) | AI_PLATFORM_UNREACHABLE | 예 |
| 그 밖의 4xx | AI_REQUEST_REJECTED | 아니오 |
Job failed · import_no_routed_document | AI_NO_EXTRACTABLE_DOCUMENT | 아니오 — 건 서류 전체에 추출 Step 에 연결된 문서종이 없음, 화면은 분류부터 다시 분석 안내 |
Job failed / cancelled | AI_JOB_FAILED / AI_JOB_CANCELLED | 아니오 |
| 결과 형태를 읽을 수 없음 | AI_OUTPUT_UNPARSEABLE | 아니오 |
| preview 거절(체계 밖 라벨 등) | AI_CORRECTION_REJECTED | 아니오 |
| 진행 중에 접속 대상이 바뀜 | AI_PROVIDER_CHANGED | 아니오 |
Retry-After 를 따르라고 하지만, 우리 폴러는 그 헤더를 읽지 않고 자체 간격(5초 → 최대 60초)으로 다시 봅니다.한도 · 시간 값
| Studio 문서(v1.6.5 · 통합 설계 v0.4) | |
|---|---|
review-document | 100 Job · 16 MiB, 병렬 2개 이하 권장 |
| 본문 업로드 · 업로드 조각 | 8 MiB |
| 검수 가져오기 전체 | 450 MiB, 100 Job |
download_url | 15분(다시 조회하면 새 URL), 내보내기 24시간 보관 |
source_job_ids | 1~100개 |
| Job 당 파일 수(이어받기) | 100개 — 넘으면 Import·이어 실행 실패(쪽 수 아닌 파일 수, 10/20 실측·10/22 회신) |
| 파일 하나의 쪽 수 | 1,000쪽 |
| Vector Store 검색 | max_num_results 1~50(기본 5), 필터 깊이 5·leaf 32 |
| 청킹 | auto 1500 토큰 + 300 겹침 |
| 우리 쪽 | |
|---|---|
| Studio 호출 타임아웃 | 120s |
| AI 폴러 | 5초 주기, 한 번에 20건, 임대 2분 |
| 작업 확인 간격 | 5초 → 2배 → 최대 60초 |
| 작업 제한 시간 | 60분 |
| 보정 반영 제한 시간 | 30분, 한 번에 최대 8걸음 |
| 검수 지원 캐시 | Collection 별 1시간 |
| 색인 폴러 | 10초 주기, 30분 제한(작업자·배치는 문서 조각 단위) |
| 묶음 PDF | 사건 1건 = 분류 Job 1개, 500쪽마다 파일을 나눔(파일 100개 넘으면 업로드 전 실패). 점검 도구의 벤치마크만 20장씩 나눔 |
| 409/412 경합 재시도 | 15회(약 13분) |
| 채팅 첨부 | 파일당 50MB, 대화 누적 100쪽 미만, 파일 수 제한 없음 |
실연동 점검 장치
모두 개발용 러너에서만 돌고, 실행기(apps/hwss-verify)는 배포 이미지에 들어가지 않습니다. 운영 Studio 호스트 목록(HWSS_VERIFY_DENY_HOSTS)에 걸리면 아무것도 부르지 않습니다. 결과 보는 곳은 팀 문서 사이트 /quality/ 입니다.
| 장치 | 언제 | 무엇을 확인하나 | Studio 호출 |
|---|---|---|---|
Studio Probe studio-probe.yml | 평일 08:30 + 수동(saas/onprem/both) | 버전, 분류 에이전트·지시서 존재, 합성 진단서 1장 분류 → 라벨이 스키마 안인가, 조회 응답 시간, (SaaS) 검수 API 지원, Store 검색, 만든 것 정리 | GET /version, GET /agents…, POST /files, POST·GET·DELETE /responses, review-capabilities, /vector_stores/…/search, DELETE /files |
분류 벤치마크 classification-benchmark.yml | 일요일 04:00 + 수동(표본 수) | 마스킹 실서류로 정확도·혼동표·저신뢰율(보고 전용) | 20장 묶음마다 업로드 → 분류 → 정리 |
Studio 계약 테스트 *StudioContractTest | 백엔드 검사(PR 마다) | 녹화한 실제 응답(studio-contract/onprem-1.1.1, saas-1.4.1)으로 운영과 같은 어댑터 코드가 읽는가(분류·추출·v1 요약·오류 모양, 10/15 include=last 반영). LLM 탭·그룹 요약·채팅 세 호출용 녹화는 아직 없음 | 없음(WireMock) |
| 실서류 자동 E2E | 평일 09:30~18:30 30분 간격 | Studio SaaS 실연동 — 평소 빠른 확인(사건 1건 + 핵심 시나리오), 12:30·17:30·배포 직후 전체 회귀(사건 3건 + 화면 순회 전부). 결과 /e2e/ | 접수부터 보고서까지 전부 |
수동 스크립트 http/studio-review-roundtrip.sh | 필요할 때 | 검수 JSON 왕복(FLOW=document|export, KIND=classification|extraction, 기본은 반영 없이 취소) | 검수 API 전부 |
수동 스크립트 http/studio-correction-roundtrip.sh | 필요할 때 | 제자리 교체 왕복(임대 서버) | 결과 왕복 API 전부 |
설정은 GitHub Environment verification-dev(secret 2개 · 변수 대상별 주소·분류 에이전트·지시서). 점검용 Store 변수(STUDIO_*_PROBE_VECTOR_STORE_ID)는 비어 있어 검색은 응답 여부만 봅니다.
열린 항목
업스테이지 확인 · 운영 환경
- 운영 1.6.12 실호출 —
selective_import_supported등 기능이 켜져 있는지, 호출 동작(판은 10/30 측정, 동작은 미확인) - 임대 서버 원본 교체 퇴역 시점(퇴역하면 임대 서버 보정 반영 불가)
- 운영 API 키 종류와
x-project-id필요 여부, 오프라인 토큰 유출 시 무효화 절차 - 실서류 SaaS 전송 — 개발 서버는 허용(결정 20261014 개정), 위탁사 승인 범위·운영 환경은 확인 필요
- 통합 설계 v0.3 문서 안 불일치 7건 회신(
docs/external/README.md「통합 설계 v0.3 수령 검토」) - v2 경로(
review_selector·LLMinput_job)의 운영(1.6.12) 실호출 확인. 임대 서버는 대상 아님 - 통합 설계 v0.4 — 사고조사 챗봇에 ① 의도 게이트를 두는가(
docs/external/README.md「통합 설계 v0.4 수령 검토」)
우리 설계
- 탭 JSON 을 화면 블록으로 그리는 규칙(결정 20261007 개정 대기) — 지금은 탭 원형 투영 그대로
- 「요약」 탭 출력 형식, 그룹 안 나눔 기준(결정 20261019 「추가」 3항 🔴), 초안 단계 「요약」 탭 텍스트박스
- 청구병원 선택 자리(
{{선택된병원}}) - v1 사건 데이터를 v2 로 옮길지(정하지 않음), v1 실행별 지시서
hwss-run …누적 정리 - 지식컬렉션의 접속 대상 구분, 같은 File 의 두 Store 사용
- 새 어댑터(LLM 탭·그룹 요약·채팅 세 호출)의 계약 테스트 녹화, 그룹 요약에 경합 재시도 한도 적용 여부
근거 문서
Studio 원문 문서는 리포 사본 docs/external/studio/(규약 v1.6.5 · 개발자 가이드 10/20 판 · 통합 설계 v0.4, 판은 git 이력)에 있고, 수령 검토는 docs/external/README.md 에 남깁니다.