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사람이 하는 일주의

한눈에 보기

Studio 를 부르는 일은 여섯 갈래입니다. 문서 분석(분류·추출)과 AI 보고서 생성(LLM 탭1→2→3)은 워크플로 단계에서 원장에 작업을 적고 폴러가 Studio 에 보내고 결과를 받아 옵니다. 그룹 요약(Agent D)은 워크플로 단계가 아닌 곁가지로, 추출을 보낼 때 시작해 따로 돕니다. 지식컬렉션과 채팅은 화면 요청이 Studio 를 직접 부릅니다.

분류 → 분류 검수 ─┬─► 추출 → 추출 검수 → 분석 조건 → [AI 보고서 생성: LLM 탭1 → 탭2 → 탭3]
                 └─► 그룹 요약(Agent D, 그룹마다 1회) ─► (결과 보관)
AI 보고서 확인 = 탭1~3 + 마지막 「요약」 탭(그룹 요약 결과) → 생성 완료(잠금) → 보고서 검수 → 항목 선택 완료(조립) → 전송
파이프라인 v2 그림(결정 20261021). 화면 단계와 처리 상태(S-004)는 v1 과 같고, 「AI 보고서 생성」 단계 안에서 부르는 것만 바뀌었습니다.
분류
사건 묶음 PDF → 문서종·쪽 범위
사건 낱장 전부를 여러 쪽 PDF 로 묶어(500쪽마다 나눔, Job 당 파일 100개 이하) 분류 Job 1개. 결과는 분류 Export Collection 에도 쌓인다.
추출
분류 Job 을 이어받아 항목값
source_job_id = 분류 Job. 사람이 고친 분류는 먼저 Studio 에 반영(보정 왕복)한 뒤 보낸다.
AI 보고서 생성 (v2)
LLM 직접 호출, 탭마다 1회
탭1 = 추출 Job 을 input_job 으로 첨부 + 탭 프롬프트, 탭2·3 = 앞 탭 JSON. 출력은 text.format JSON Schema.
그룹 요약 (v2 곁가지)
요약 그룹마다 Agent D 1회
분류 Job 의 해당 문서만 review_selector 로 골라 보낸다. 결과는 AI 보고서 마지막 「요약」 탭.
지식컬렉션
Vector Store 색인·검색
공용은 기준 폴더(2depth)마다 Store 1개, 개인은 사용자당 Store 1개.
채팅
질문 하나에 Studio 세 호출
① 의도 게이트 → ② 컬렉션 라우팅(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.5engine 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 등)만 봅니다.

HWSS 백엔드 이미지 연동 수신 ImageIntegrationController 워크플로 엔진 INVESTIGATION_MAIN v1 · v2 v2: …EXTRACT→GENERATE_REPORT_TABS AI 작업 서비스 · 폴러 AiPipelineJobPoller (5초) ScanPdfPreparationService AiClassification·ExtractionJob… AiReportTabsJobService (v2) AiSummaryGroupJobService (v2 곁가지) AiPipelineResultSyncService 원장 (DB) ai_pipeline_jobs · _files · _syncs report_draft_llm_tabs · summary_* 지식컬렉션 · 채팅 CollectionVectorGateway CollectionIndexingPoller (10초) CollectionSearchService ChatService (의도 분류 → 답변) ChatAttachmentPreparationService aiplatform-core / studio StudioDocumentFileAdapter 사건 묶음 PDF 업로드·상태 StudioClassificationAdapter 분류 Job 제출·조회 StudioExtractionAdapter 추출 Job (source_job_id) StudioReportLlmAdapter v2 LLM 탭 (input_job · text.format) StudioGroupSummaryAdapter v2 그룹 요약 (review_selector) StudioAnalysisAdapter v1 요약 Job · 실행별 지시서 StudioResultCorrectionAdapter 보정 반영 — 제자리 교체(임대 서버) └ StudioJobReviewRoundtrip 보정 반영 — 검수 JSON(SaaS) StudioCollectionResolver 지시서 → Export Collection id StudioVectorStoreAdapter Store·파일·검색 StudioChatAdapter 채팅 3호출 (기본 꺼짐) StudioDocumentErrors 오류 → 재시도 판정 StudioTargetEnvironmentPostProcessor Studio (/v2) Files POST /files · GET /files/{id} 분류 에이전트 parse → classify → export 문서 = 파일 + 쪽 범위 분류 Collection 검수 JSON · 문서 id 추출 에이전트 import → extract → export 추출 Collection 요약 에이전트 (Agent D) import → merge → instruct · v2 는 그룹마다 1회 v2: review_selector = 분류 Job 의 문서 id · v1: source_job_id LLM 직접 호출 (Responses) model=auto · 탭1 input_job=추출 Job · 탭2·3 input_text text.format = JSON Schema · stream Collection 검수·결과 왕복 review-capabilities · review-document · review-imports result-snapshots · file-collection-imports (임대 서버) Vector Stores /vector_stores · /files · /search Studio Chat POST /responses stream · conversation · file_search
점선 상자는 Studio 가 지시서의 Export 스텝으로 만드는 Collection, 점선 화살표는 Studio 안에서 앞 Job 결과를 Import 하는 관계입니다(추출 ← 분류 Collection, 요약 ← 분류 Collection). 백엔드는 Collection id 를 실행에 넘기지 않고 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 안)에 반영합니다.

사건 묶음 PDF 낱장 1장 = 1쪽 POST /files 분류 Job POST /responses 분류 에이전트·지시서 추출 Job source_job_id=분류 Job 같은 File 목록 LLM 탭1 input_job=추출 Job + 수정값 줄 LLM 탭2 → 탭3 input_text = 앞 탭 JSON 분류 Collection 추출 Collection 분류 검수 수동 지정 추출 검수 값 수정 ① 추출 보내기 직전분류 Collection 에 반영 ② 탭1 보내기 직전 추출 Collection 에반영(EXTRACT 구성) + 지시문에 수정값 줄 그룹 요약 Job × 요약 그룹 수 (곁가지) review_selector=[분류 Job · 그룹 문서 id] 추출 Job 을 제출할 때 시작 — 본선을 막지 않는다 우리 DB 요약 결과 (탭 원형) 마지막 「요약」 탭
검수 JSON 경로(SaaS)는 반영해도 Job id·File id 가 그대로라 원 Job 으로 이어 실행합니다. 제자리 교체 경로(임대 서버)는 반영하면 Job id·File id 가 새로 발급되어, 파일 해시 순서로 새 Job 을 찾아 그 Job 으로 이어 실행합니다. 분석 출처가 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 을 찾는 방법 (보정 반영·그룹 요약 문서 읽기에 필요)

  1. 설정 …_COLLECTION_ID 가 있으면 그 값.
  2. 없으면 GET /agents/{a}/configs/{c} 의 Export 스텝 table_key 를 읽고
  3. 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멈추는 곳
10RECEIVE_TRANSFERREADY_FOR_CONFIRM →아니오없음확정(API-05 또는 화면 「AI 분류 시작」)
20CLASSIFYCLASSIFYING → EXTRACTING예사건 묶음 PDF 업로드 + 분류미분류·저신뢰가 있으면 「미분류검출」로 EXTRACT 진입 게이트에서 멈춤
30EXTRACTEXTRACTING → EXTRACTION_COMPLETED아니오분류 보정 반영 + 추출 (v2: 제출 때 그룹 요약 곁가지 시작)분석 요청
40GENERATE_REPORT_TABS
v1: ANALYZE
ANALYZING → ANALYSIS_COMPLETED아니오v2: 추출 보정 반영 + LLM 탭1→2→3 · v1: 추출 보정 반영 + 요약 Instruct 1회—
50GENERATE_DRAFTANALYSIS_COMPLETED →아니오없음 — enabled=false(10/22, 활성 단계 4개)—

걸음별 상세 (v2)

#
누가
우리 코드
Studio 호출
남는 것
1
이미지 시스템
API-01 ImageTransferReceptionService.start
—
image_transfers, transfer_pages
2
이미지 시스템
API-02 → DB 큐 작업 IMAGE_ZIP_EXTRACTION(image_transfer_background_jobs) → 검증·해제 → WorkflowLauncherPort.startIfAbsent(INVESTIGATION_MAIN) (정의 버전은 WorkflowDefinitionSelector)
—
report_pages, 실행 1건, 보고서 READY_FOR_CONFIRM
3
사람 / API-05
확정 POST …/confirmation → CLASSIFY → AiClassificationJobService.request. API-05 로 자동 시작한 분류는 1인 AI 작업 한도 이상이면 조사자별 대기열에 섰다가 자리가 나면 시작(결정 20261022)
—
ai_pipeline_jobs CLASSIFY · REQUESTED
4
폴러
ScanPdfPreparationService → PdfDocumentAssembler (활성 낱장 전부를 문서함 순서로, 500쪽마다 나눔)
POST /files, GET /files/{id}?view=status&pages=none
ai_pipeline_documents·_document_pages(쪽 ↔ 낱장), 스냅샷 해시가 같으면 기존 묶음 재사용
5
폴러
StudioClassificationAdapter.submit
POST /responses (분류 에이전트, 묶음 File 들, background:true)
작업 SUBMITTED, external_job_id, ai_pipeline_job_inputs(파일 순서)
6
폴러
poll → StudioClassificationOutputParser → materializeFromAi
GET /responses/{id}?include=last → 완료면 ?include=all
classification_results LIVE, 낱장별 CLASSIFIED / REVIEW_REQUIRED / UNCLASSIFIED
7
사람
수동 지정 …/manual-assignments, AI 확정 …/ai-confirmations, 「정보 추출 시작」 …/extraction-start
—
classification_corrections(MANUAL_ASSIGN, 반영 PENDING), EXTRACT 작업 REQUESTED
8
폴러
AiPipelineResultSyncService.prepareClassification (라벨이 바뀐 보정이 있을 때만)
ai_pipeline_result_syncs → RESOLVED
9
폴러
StudioExtractionAdapter.submit → ExtractionSubmittedEvent
POST /responses (source_job_id = 분류 Job, 같은 File, Idempotency-Key)
EXTRACT 작업 SUBMITTED. 이 이벤트로 그룹 요약 묶음이 시작된다
10
폴러
StudioExtractionOutputParser → ExtractionResultService.record
GET /responses/{id}?include=last → ?include=all
extraction_results LIVE, 항목별 output_pointer·신뢰도
11
사람
값 수정 …/field-corrections(선택), 분석 요청 …/analysis-requests
—
extraction_field_corrections, analysis_requests, GENERATE_REPORT_TABS 작업 REQUESTED
12
폴러
AiReportTabsJobService → prepareExtraction (출처 EXTRACT 구성, 바뀐 값이 있을 때만)
검수 보정 반영 왕복(추출 Collection)
ai_pipeline_result_syncs EXTRACTION → RESOLVED, 탭1 이 첨부할 추출 Job 고정
13
폴러
ReportDraftLlmService → StudioReportLlmAdapter (탭1 → 탭2 → 탭3)
POST /responses LLM 직접 호출 — AI 보고서 생성
report_draft_llm_tabs 탭마다 1행(ai_pipeline_job_id 에 묶임)
14
폴러
탭 출력 JSON → 요약 결과(탭 원형, SummaryOutputProjector)
—
요약 섹션·카드·항목, 보고서 ANALYSIS_COMPLETED. 마지막 「요약」 탭은 그룹 요약 결과
어느 단계든 실연동 조건(해당 포트가 켜져 있고, 원장의 플랫폼 식별자가 같고, 앞 단계 결과가 LIVE)이 맞지 않으면 그 자리에서 스텁 결과를 만들고 화면에 STUB 배지가 붙습니다. v2 의 AI 보고서 생성도 LLM 어댑터가 없거나 위탁사 템플릿이 없으면 스텁 요약을 만듭니다. 실연동 설정이 빠진 서버가 조용히 스텁으로 도는 일이 있으니(9/29 실제 사례) 결과의 출처 표시를 먼저 봅니다.

근거: 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시간 캐시). 설정 변경은 필요 없습니다.

GET …/review-capabilities 200 이고 format=collection-results/11, result_source_supported≠false ? 예 (SaaS 1.4.1+) 404 등 (임대 서버 1.1.1) A. Job 검수 JSON 경로 ① 내려받기 GET …/review-document?job_ids=<우리 Job> 동기, 응답 본문 = 검수 파일. 옛 판은 review-exports(비동기) ② 고치기 — 분류 documents[].label · 추출 content[0] 의 항목 우리 Job 부분만 바꾼다 ③ 올리기 POST …/review-imports?apply=manual octet-stream 본문 1번(8 MiB 이하) · 넘으면 분할 업로드 ④ preview 확인 GET …/review-imports/{i} 충돌·이동 불가·label_not_in_config 면 cancel 후 거절 ⑤ 반영 POST …/apply If-Match: <ETag> 412 는 다시 시도 → Job id·File id 그대로, 원 Job 으로 이어 실행 B. 제자리 교체 경로 (임대 서버) ① POST …/result-snapshots → ready 면 download_url ② POST …/result-context/search (파일↔결과 대응) ③ Collection JSON 에서 우리 Job 만 남기고 고치기 ④ POST /file-collection-imports → ready_for_plan ⑤ PUT …/plan (원본) → POST …/commit ⑥ POST …/source-jobs/search + 파일 hash 로 새 Job → Job id·File id 새로 발급, 새 Job 으로 이어 실행
원장 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 확인 후 반영)을 유지합니다.
임대 서버 위험. Studio 문서상 원본 Collection 제자리 교체는 퇴역 중이고, 환경 설정으로 막히면 plan·commit 이 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).
「보고서 항목 선택 완료」의 초안은 v2 에서 조립만 합니다(모델 호출 없음). Agent 방식 보고서(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

  1. 추출 Job 을 제출할 때(ExtractionSubmittedEvent) 그 분류 Job 으로 묶음(summary_group_batches) 1개를 만듭니다. v2 이고 포트가 있을 때만입니다.
  2. GET …/review-capabilities 로 selective_import_supported 를 확인하고(1시간 캐시, 거짓이면 그룹 실패), GET …/review-document?job_ids=<분류 Job> 로 문서 id·라벨·구조 버전을 읽습니다.
  3. 건에 고정한 판(summary_group_versions, 결정 20261028)의 그룹 마스터(summary_groups·summary_group_document_types, 한 문서종은 한 그룹에만)로 문서를 그룹에 나눠 그룹마다 1회 제출합니다.
  4. 폴러가 결과를 받아 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
「요약」 탭 출력 형식(그룹 프롬프트의 JSON 모양)과 그룹 안 나눔 기준(간병인 서류 병원별 등)은 아직 정하지 않았습니다(결정 20261019 「추가」 3항 🔴). 지금은 JSON 이면 글자 잎을 줄로, 아니면 줄바꿈으로 보입니다.

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_schema hwss_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 에서 detachattach 가 거절되면 다시 업로드. 같은 File 재사용은 아직 실측 전
검색POST /vector_stores/{vs}/searchStore 마다 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_results 30)를, 첨부가 있는 턴에만 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_keyStudioAnalysisAdapter, StudioCollectionResolver
POST /agents/{a}/configs실행별 비기본 지시서 is_default:falseStudioAnalysisAdapter
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 /filesmultipart file + purpose=user_data. 사건 묶음 PDF·컬렉션 문서·채팅 첨부, 파일명 기본 UTF-8, Content-LengthStudioDocumentFileAdapter, 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 /responsesmodel=에이전트, config_id, input[].content[](input_file · 요약은 input_text), background:true, 추출·v1 요약은 source_job_idStudioClassificationAdapter, StudioExtractionAdapter, StudioAnalysisAdapter
POST /responses (review_selector)그룹 요약 — [{job_id: 분류 Job, structure_version, document_ids}], Idempotency-Key. source_job_id 와 함께 보내면 400StudioGroupSummaryAdapter
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, SSEStudioReportLlmAdapter
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_modesStudioJobReviewRoundtrip
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 · …/cancelIf-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 …/committarget_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}/searchquery, 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_DRAFTv1·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-20Studio 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-20Vector Store 파일 삭제 409 vector_store_file_delete_conflict(부분 삭제 없음, 같은 요청 재전송 가능)문서 제거·연결 해제가 2회까지 다시 보냄 — #1223
10-20Collection 목록 기본값이 자동 생성 기본 Collection 을 숨김(listed 기본 true, include_default)Export Collection(agent_export)은 그대로 나와 해석 영향 없음
10-20Studio Chat 첨부 인용이 태그 대신 file_citation annotation, 서버 툴 루프 상한, input_job·reasoning.effort·text.format태그 해석 코드가 없어 영향 없음. annotation·input_job 은 후보
10-20review_selector 문서·파일 선택 이어 실행(selective_import_supported)v2 그룹 요약이 문서 선택으로 씀(지원 여부를 Collection 마다 확인) — #1271
2026-10-12 — 번들 v261012 (Collection 문서 갱신)
10-12GET 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-12apply=auto(전부 아니면 무), units[].applied_versions쓰지 않음(체계 밖 라벨 거절 불가)
10-12review-capabilities 에 result_source_supported·apply_modesfalse 면 미지원 판정 — #834
10-12원본 Collection 제자리 교체가 막히면 409 original_replace_retired영구 409 3종 비재시도 — #834. 온프렘 보정 반영 경로 소멸 위험은 남음
10-12Job 조회가 검수 반영본과 review_versions 를 돌려줌, review_selector 형식 공개반영본 확인(10/12 실접수), review_selector 는 불필요
10-12result_container 와 전용 API 제거, batch-undelete 퇴역, agent_link 동기화 제거쓰지 않아 영향 없음
10-12Collection 종류는 kind 가 정본, source_type deprecated. Agent 기본 Collection(is_default)아직 source_type/source.type 을 읽음(계속 내려옴). 필드가 사라지면 바꿔야 함
10-12source_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-06review-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-01SaaS(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-28Instruct 실측: 온프렘 input_text 거절, text 이중 인코딩·【†n】지시서 버전 폴백 #599, 출력 골격 #619
09-28Vector 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_text400 import_input_invalid수락instruction-delivery=auto 폴백
Import→Merge→Instruct실행 실패동작, 같은 글 반복파서가 반복 접기
검수 JSONreview-capabilities 404지원(review-document·본문 업로드 포함)응답으로 경로 선택
제자리 교체동작(id 재발급)막히면 409 original_replace_retired비재시도
결과 스냅샷 형식collection-results/2v2.1둘 다 처리
Collection 종류 필드최상위 source_type=null, source.typekind 정본 + source_type두 자리 모두 읽음
결과 조회 시간보통 1~5초, 최대 83초약 1초timeout 120s
Store file_counts갱신 안 됨갱신쓰지 않고 파일 단건 조회
Studio Chatresponse.failedresponse.completed채팅 어댑터는 SaaS 기준(기본 꺼짐)
쪽 단위 보정(묶음 PDF)제자리 교체는 파일 단위 자리만 — 건너뜀(경고 로그)검수 JSON page_ranges 로 쪽 단위경로별 처리
v2 (review_selector·LLM input_job)확인 안 함실연동 E2E 10/21~22selective_import_supported 가 거짓이면 그룹 요약 실패로 닫음

설정 · 접속 대상 전환

AI_PLATFORM_STUDIO_TARGET=<대상> 을 주면 AI_PLATFORM_STUDIO_<대상>_* 값을 모두 AI_PLATFORM_STUDIO_* 로 옮겨 씁니다(StudioTargetEnvironmentPostProcessor). 플랫폼 식별자는 기본 studio-<대상> 이 되어 원장 provider 열에 남고, 대상을 바꾸면 진행 중 작업은 AI_PROVIDER_CHANGED 로 닫힙니다(낱장은 새 대상에 다시 올라갑니다).

환경변수기본뜻
AI_PLATFORM_PROVIDERnonenone = 메모리 스텁, studio = 실연동
AI_PLATFORM_STUDIO_TARGET—대상 이름(예 saas, onprem)
AI_PLATFORM_STUDIO_PROVIDER_IDstudio원장 플랫폼 식별자
AI_PLATFORM_STUDIO_BASE_URL / _API_KEY필수/v2 까지 포함한 주소, 키
AI_PLATFORM_STUDIO_PROJECT_ID비움x-project-id 헤더(운영 필요 여부 미정)
AI_PLATFORM_STUDIO_TIMEOUT120s접속·읽기 공통
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_DELIVERYautoauto / 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 / autoAI 보고서 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}falseAgent 방식 보고서 탭(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_FILENAMEutf8업로드 파일명 한글 유지 / ascii
ANALYSIS_SOURCE_STEPEXTRACT탭1(v1 은 요약)이 이어받는 앞 Job — EXTRACT / CLASSIFY
AI_PLATFORM_STUDIO_AGENT_ID비움Vector Store 를 묶을 에이전트(비우면 standalone)
AI_PLATFORM_STUDIO_FILE_READY_TIMEOUT / _POLL_INTERVAL15s / 1s업로드 File 준비 대기
AI_PIPELINE_POLLER_ENABLED / _POLL_INTERVALtrue / PT5SAI 작업 폴러
AI_PIPELINE_JOB_TIMEOUTPT60M작업 제한 시간
AI_PIPELINE_MAX_CONFLICT_ERRORS15409/412 경합 재시도 한도(약 13분). 단계 작업에만, 그룹 요약은 일반 한도
AI_PIPELINE_SCAN_PDF_{UPLOAD_ENABLED,DPI,MAX_PART_PAGES}true / 150 / 1000사건 묶음 PDF 업로드, 쪽 크기 기준, 묶음 하나의 쪽 상한
AI_PIPELINE_CORRECTION_SYNC / _TIMEOUTREQUIRED / PT30M보정 반영 정책
ANALYSIS_INSTRUCT_OUTPUT_SCHEMAskeletonskeleton = 우리 골격을 response_format 으로, config = 지시서가 소유
CLASSIFICATION_LOW_CONFIDENCE_THRESHOLD0.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_keyREQUESTED → SUBMITTED → APPLIED / FAILED / DISCARDED
ai_pipeline_files낱장 1장의 Studio File(묶음 PDF 이전 경로)provider, report_no, source_page_id, sha256, external_file_idPROCESSING / 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_tabsLLM 탭 1회 호출(v2 는 단계 작업에 묶임)report_no, ai_pipeline_job_id(또는 report_draft_id), tab_key, tab_order, model, input_job_id, response_id, output_json, extra_instructionREQUESTED → RUNNING → SUCCEEDED / FAILED
summary_group_batches분류 Job 하나에 대한 그룹 요약 묶음report_no, source_job_id, structure_version, next_check_at, lease_untilREQUESTED / 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_idPENDING / 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_idsEXPORT_REQUESTED → … → RESOLVED / FAILED
classification_results / extraction_results결과 본체provenance(LIVE/STUB), external_job_id—
classification_corrections / extraction_field_corrections사람 보정sync_status, external_referencePENDING / APPLIED / SKIPPED / FAILED
collection_owner_vector_stores사용자별 개인 Store사번, vector_store_id—
chat_attachments · chat_sessions.external_conversation_id채팅 첨부의 Studio File / Studio conversation idfile_id, 상태 · conversation id—

폴러 걸음(AiPipelineJobLedger.drive): 임대 2분 → 단계·플랫폼 식별자 확인 → 제한 시간 → 진행. 첫 확인 5초 뒤, 2배씩 늘려 최대 60초 간격, 일시 오류 5번(409/412 경합은 15번) 넘으면 실패.

오류 처리 · 재시도

Studio 오류 본문은 {"error":{code,param,type,message}} 이고 분기는 code 로만 합니다. 판정은 StudioDocumentErrors 한 곳에서 합니다. 우리 기록에는 code 만 남기고 message 본문은 남기지 않습니다.

Studio 응답우리 사유 코드재시도
429AI_RATE_LIMITED예
401 · 403AI_PLATFORM_AUTH_FAILED아니오
404AI_RESOURCE_NOT_FOUND아니오
409 · 412AI_RESOURCE_CONFLICT예(한도 AI_PIPELINE_MAX_CONFLICT_ERRORS) — 단 original_replace_retired·collection_source_type_unsupported·collection_not_replayable·derived_jobs_limit_exceeded 은 아니오
409 import_shape_unavailableAI_IMPORT_SHAPE_UNAVAILABLE아니오 — 앞 단계 Job 형태가 뒤 단계 Config 와 맞지 않음, 화면은 분류부터 다시 분석 안내
408 · 5xxAI_PLATFORM_UNAVAILABLE예
연결 실패 · 본문 읽기 중 끊김(IOException)AI_PLATFORM_UNREACHABLE예
그 밖의 4xxAI_REQUEST_REJECTED아니오
Job failed · import_no_routed_documentAI_NO_EXTRACTABLE_DOCUMENT아니오 — 건 서류 전체에 추출 Step 에 연결된 문서종이 없음, 화면은 분류부터 다시 분석 안내
Job failed / cancelledAI_JOB_FAILED / AI_JOB_CANCELLED아니오
결과 형태를 읽을 수 없음AI_OUTPUT_UNPARSEABLE아니오
preview 거절(체계 밖 라벨 등)AI_CORRECTION_REJECTED아니오
진행 중에 접속 대상이 바뀜AI_PROVIDER_CHANGED아니오
Studio 문서는 429·503 에 Retry-After 를 따르라고 하지만, 우리 폴러는 그 헤더를 읽지 않고 자체 간격(5초 → 최대 60초)으로 다시 봅니다.

한도 · 시간 값

Studio 문서(v1.6.5 · 통합 설계 v0.4)
review-document100 Job · 16 MiB, 병렬 2개 이하 권장
본문 업로드 · 업로드 조각8 MiB
검수 가져오기 전체450 MiB, 100 Job
download_url15분(다시 조회하면 새 URL), 내보내기 24시간 보관
source_job_ids1~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쪽 미만, 파일 수 제한 없음
파일 수가 아주 많은 Job 하나가 들어온 뒤 분류 Collection 의 결과 내보내기가 실패한 실측(10/2, 임대 서버)과 114장 분류 Job 이 추출로 이어지지 않은 실측(10/20, SaaS)이 있습니다. 둘 다 사건 묶음 PDF 로 파일 수를 줄여 해결합니다.

실연동 점검 장치

모두 개발용 러너에서만 돌고, 실행기(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·LLM input_job)의 운영(1.6.12) 실호출 확인. 임대 서버는 대상 아님
  • 통합 설계 v0.4 — 사고조사 챗봇에 ① 의도 게이트를 두는가(docs/external/README.md 「통합 설계 v0.4 수령 검토」)

우리 설계

  • 탭 JSON 을 화면 블록으로 그리는 규칙(결정 20261007 개정 대기) — 지금은 탭 원형 투영 그대로
  • 「요약」 탭 출력 형식, 그룹 안 나눔 기준(결정 20261019 「추가」 3항 🔴), 초안 단계 「요약」 탭 텍스트박스
  • 청구병원 선택 자리({{선택된병원}})
  • v1 사건 데이터를 v2 로 옮길지(정하지 않음), v1 실행별 지시서 hwss-run … 누적 정리
  • 지식컬렉션의 접속 대상 구분, 같은 File 의 두 Store 사용
  • 새 어댑터(LLM 탭·그룹 요약·채팅 세 호출)의 계약 테스트 녹화, 그룹 요약에 경합 재시도 한도 적용 여부

근거 문서

문서내용
decisions/20260922-ai-classification-async-integration.md중립 포트 + 원장 + 폴러 구조
decisions/20260929-pipeline-correction-roundtrip.md보정 반영(제자리 교체)
decisions/20261006-collection-review-json-roundtrip.md보정 반영(Job 검수 JSON), §8·§9 10/12 갱신
decisions/20261001-studio-target-switch.md접속 대상 전환, 온프렘·SaaS 실측표
decisions/20261002-pipeline-version-coexistence.md파이프라인 정의 버전 병행
decisions/20261021-investigation-pipeline-v2-composition.md파이프라인 v2 구성(LLM 탭1→2→3, 그룹 요약 곁가지)
decisions/20261020-report-generation-llm-input-job.md보고서 생성 LLM 직접 호출(input_job·text.format)
decisions/20261019-studio-summary-tab-doc-groups.md요약 그룹 단위·표시 위치
decisions/20261021-case-documents-job-100-files.md사건 단위 여러 쪽 PDF, Job 당 파일 100개
decisions/20261015-studio-saas-v2-agents-adoption.md업스테이지 제공 SaaS 에이전트 도입·실측
decisions/20261021-chat-two-stage-intent-file-search.md채팅 호출 구성(10항 — 의도 게이트 → 라우팅 → 답변)
decisions/20261020-chat-studio-conversation-continuation.md채팅 후속 질문 conversation 잇기
decisions/20260928-knowledge-collection-vector-store-integration.md지식컬렉션 Vector Store
decisions/20261012-personal-collection-owner-vector-store.md개인 컬렉션 사용자당 Store
decisions/20261012-collection-file-description-attribute.md파일 속성 description
notes/20261012-studio-collection-docs-update.md10/12 Collection 문서 갱신 정리
notes/20260923-studio-classify-response-shape.md분류 응답 형태·업로드 실측
external/studio/integrated-design.md업스테이지 통합 설계 v0.4 사본(바뀐 곳은 「챗봇 & 컬렉션」 탭뿐)
external/studio/dev-guide/화면 흐름별 개발자 가이드(10/14~10/20 판)
external/studio-api-process-mapping.md업스테이지 프로세스별 호출 시퀀스(사본 v6, 통합 설계 이전)
aiplatform-core/…/studio/Studio 어댑터 코드

Studio 원문 문서는 리포 사본 docs/external/studio/(규약 v1.6.5 · 개발자 가이드 10/20 판 · 통합 설계 v0.4, 판은 git 이력)에 있고, 수령 검토는 docs/external/README.md 에 남깁니다.