diff --git a/.gitignore b/.gitignore index b7faf40..be9e090 100644 --- a/.gitignore +++ b/.gitignore @@ -205,3 +205,4 @@ cython_debug/ marimo/_static/ marimo/_lsp/ __marimo__/ +.codex diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7a867f8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,90 @@ +## Project Rules + +- Before adding new logic, check whether the same behavior already exists elsewhere and extend or reuse it instead of duplicating it. +- Do not leave the same responsibility implemented in two places unless the duplication is temporary and explicitly documented. + +## Python / FastAPI / Sonar Rules + +When writing or modifying Python code in this repo, proactively avoid patterns that have previously failed SonarCloud quality gates. + +### FastAPI +- Use `Annotated[...]` for dependency injection parameters instead of `param: Type = Depends(...)`. +- Do not specify `response_model=...` when it duplicates the function return type annotation. +- Prefer framework-idiomatic FastAPI signatures and avoid legacy patterns unless required by existing code. + +### Async code +- Do not mark a function `async` unless it actually uses `await` or must satisfy an async interface. +- This applies especially to nested test handlers, mock callbacks, and test doubles. +- If a test double must remain async for interface compatibility, make that explicit in the code. + +### Tests +- Do not compare floating point values with direct equality. +- Use `pytest.approx(...)` for float assertions. +- Prefer `https://` over `http://` in test URLs and example URLs unless plain HTTP is explicitly required by the behavior being tested. +- When a test file covers multiple scenario groups, prefer organizing tests into descriptive pytest classes (for example `TestSearchSuccess`, `TestSearchValidation`) so related cases are easier to scan. +- Do not introduce test classes mechanically in tiny single-purpose files; use them when they improve grouping and readability. + +### Readability / complexity +- Keep functions small and focused. +- If a function mixes external I/O, validation, retry handling, and exception translation, split those responsibilities into helper functions before complexity grows. +- Treat high cognitive complexity as a design problem, not something to ignore. + +### Naming +- Use standard snake_case for local variables and helper names. +- Avoid capitalized local variable names unless they are actual class/type definitions. + +### Cross-service duplication +- Do not copy shared utility or middleware code between services without explicit approval. +- Before duplicating logic from another service, first check whether it should be extracted to a shared module. +- If duplication is temporarily unavoidable, call it out explicitly instead of silently copying large blocks. + +### Before finishing +- Review changed files for likely SonarCloud issues before concluding work. +- In particular, check for: + - duplicate FastAPI dependency signatures + - unnecessary `async` + - float equality in tests + - insecure test URLs + - large copy-pasted blocks across services + - overly complex exception-handling functions + + +# Response Rules + +Apply these rules before lower-priority response habits whenever possible. + +## Style + +- Keep sentences short. +- Keep paragraphs short. +- Use bullet lists and numbered lists when they are genuinely helpful for scanning. +- Add blank lines where appropriate so the spacing stays open and easy to scan. +- Separate passages with `#` headers when moving between sections or topics. +- Answer with the core point first. +- Do not give a long explanation and then repeat it as a conclusion. +- Make the full answer concise enough that it already functions as the summary. + +## Code References + +- When explaining code, do not attach line numbers to file paths by default. +- Mention only the few core files that are truly necessary. +- Avoid cluttering the answer with many file references. + +## Tables + +- Do not use Markdown tables. +- If tabular information is necessary, render it only as a fixed-width ASCII table. +- Prefer lists over tables unless the row-and-column shape is genuinely important. + +## Autonomy + +- Do not say "If you want, I can..." or similar offer-based filler. +- Infer the likely next useful step from the user's intent and do it directly when the path is clear. +- Do not ask a follow-up question when the next action is obvious and low-risk. +- Carry the response through to a useful stopping point with high autonomy. + +## Tone + +- Use a compact, direct, high-signal style. +- Avoid overly long sentences and overly layered explanations. +- Prefer decisive wording over hedging when the answer is clear. \ No newline at end of file diff --git a/README.md b/README.md index c139bce..886a3f2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # Biblio 사용자가 영상을 업로드하면, 해당 영상의 내용을 분석하여 검색 및 질의응답(RAG, Retrieval-Augmented Generation) 기능을 제공하는 백엔드 시스템 구축 프로젝트. + +## Docs + +- [Git Naming Convention](./docs/git-naming-convention.md) diff --git a/docs/Tech_Spec/External_AI_Adapters_Spec.md b/docs/Tech_Spec/External_AI_Adapters_Spec.md deleted file mode 100644 index 4751f19..0000000 --- a/docs/Tech_Spec/External_AI_Adapters_Spec.md +++ /dev/null @@ -1,388 +0,0 @@ -# [External AI Adapters] SPEC - -**Meta** -- **Component ID:** external-ai-adapters -- **SOT References:** `system-design.md`, `PRD.md`, `Search_Service_Spec.md`, `Pipeline_Worker_Spec.md`, `Managed_Embedding_Endpoint_Spec.md` - ---- - -## 1. Context & Scope - -### 1.1 목적 (Purpose) -- **한 줄 요약:** Search Service와 Pipeline Worker가 외부 상용 AI API에 직접 결합되지 않도록, LLM 및 STT 호출을 추상화하고 공통 오류 분류·타임아웃·재시도·서킷 브레이커 정책을 일관되게 적용하는 어댑터 계층이다. -- **비즈니스 목표:** 외부 AI 제공자(Gemini, Google Cloud STT 등)의 SDK 교체나 API 스펙 변경이 상위 서비스의 비즈니스 로직과 프롬프트/파이프라인 로직에 직접 전파되지 않도록 경계를 분리한다. -- **배치 형태:** 별도 네트워크 서비스가 아니라, Search Service 및 Pipeline Worker 프로세스에 in-process library/module 형태로 주입되는 공용 adapter 패키지로 구현한다. - -### 1.2 요구 기술 스택 및 환경 변수 (Tech Stack & Configs) -- **언어/런타임:** Python 3.11+, asyncio 기반 -- **외부 SDK:** - - Gemini 계열 LLM SDK - - `google-cloud-speech` SDK (Google Cloud Speech-to-Text) -- **운영 구현체:** - - `GeminiLLMAdapter` - - `GoogleSTTAdapter` -- **테스트 구현체:** - - `MockLLMAdapter` - - `MockSTTAdapter` -- **필수 환경 변수:** - - `GEMINI_API_KEY` - - `GEMINI_MODEL_NAME` - - `GCP_PROJECT_ID` -- **설정 주입 및 소유권 정책:** - - V1에서 provider 선택은 Search Service와 Pipeline Worker가 서비스 wiring 단계에서 직접 수행한다. - - Search Service는 LLM generation policy(예: `temperature`, `max_output_tokens`)를 소유하며, adapter 생성 시 provider-neutral config/profile 형태로 주입한다. - - 서비스 wiring 시 주입되는 shared config는 LLM safety setting을 소유하며, adapter는 이를 provider SDK 필드로 매핑만 수행한다. - - Pipeline Worker는 STT transcription policy(예: `language_hint`, `punctuation`, `diarization`)를 소유하며, adapter 생성 시 provider-neutral config/profile 형태로 주입한다. - - adapter는 provider SDK 호출 방식, provider별 필드명/enum 매핑, 안전한 fallback default, timeout/retry/circuit breaker 적용 메커니즘만 소유한다. - - V1의 request method 시그니처는 단순하게 유지하며, per-request profile 선택이나 raw provider 파라미터 pass-through는 허용하지 않는다. -- **호출자 바인딩 정책:** - - Search Service가 `GeminiLLMAdapter`를 사용할 때의 timeout/retry/circuit breaker 값은 `Search_Service_Spec.md`를 따른다. - - Pipeline Worker가 `GoogleSTTAdapter`를 사용할 때의 timeout/retry 값은 `Pipeline_Worker_Spec.md`를 따른다. - - 즉, 이 컴포넌트는 공통 정책 적용 메커니즘을 제공하되, 호출자별 수치는 상위 컴포넌트 스펙이 SOT이다. - -### 1.3 경계 (Boundaries) -- **In-Scope:** - - 외부 LLM/STT 제공자별 SDK 호출 추상화 - - 공통 오류 분류 (`retryable` / `non-retryable`) 및 예외 래핑 - - 호출자별 timeout / retry / circuit breaker 정책 적용 - - `trace_id` 기반 구조화 로깅 및 provider 호출 correlation - - 로컬/단위 테스트용 mock adapter 제공 -- **Out-of-Scope:** - - 프롬프트 템플릿 작성, `ContextBlock` 조립, `used_refs` 파싱 - - 이는 Search Service의 `prompt_builder` 책임이다. - - 음성 파일 추출, 오디오 포맷 변환, 청킹, enriched text 생성 - - 이는 Pipeline Worker 책임이다. - - 임베딩 추론 - - Managed Embedding Endpoint가 별도 책임을 가진다. - - Vision caption / OCR / scene tag 호출 - - V1에서는 Pipeline Worker 내부 `VisionAdapter` 책임으로 유지한다. - - 도메인 카테고리 기반 STT adaptation hint 또는 per-request domain hint DTO - - V1에서는 category를 메타데이터로만 유지하며 adapter 입력 계약에 포함하지 않는다. - - DB 영속화 및 Object Storage 쓰기 - - 상위 서비스가 책임진다. - -### 1.4 상태 라이프사이클 기준 -이 컴포넌트는 도메인 엔티티 상태를 직접 소유하지 않는다. 다만 각 provider binding의 adapter runtime instance는 프로세스 로컬 circuit breaker 상태를 가질 수 있다 (§3.2 참조). - ---- - -## 2. Contracts (Interface & Data) - -### 2.1 Adapter Interface - -#### [LLMAdapter] - -Search Service는 `ContextBlock` 목록과 자연어 지침을 조합해 최종 prompt 문자열을 만든 뒤, 이 adapter에 전달한다. -즉, `ContextBlock` 타입과 `video_title`/`video_id` 라벨 삽입은 Search Service 소유 계약이며, External AI Adapters는 이미 직렬화된 prompt를 입력으로 받는다. - -```python -@dataclass -class TokenUsageDTO: - input_tokens: int | None = None - output_tokens: int | None = None - total_tokens: int | None = None - - -@dataclass -class LLMGenerationResult: - text: str - provider_request_id: str | None = None - token_usage: TokenUsageDTO | None = None - finish_reason: str | None = None - - -class LLMAdapter(Protocol): - async def generate( - self, - *, - prompt: str, - trace_id: str, - ) -> LLMGenerationResult: - """ - 외부 LLM API를 호출하여 provider-neutral 결과 객체를 반환한다. - `text`에는 답변 본문과 structured {"used_refs": [...]} 블록이 포함될 수 있다. - adapter는 이 값을 파싱하거나 수정하지 않는다. - """ -``` - -- **입력 제약:** - - `prompt`는 Search Service가 완성한 provider-neutral prompt 문자열이다. - - adapter는 prompt 내용을 재작성하지 않는다. - - adapter는 query rewrite, citation 복구, post-processing을 수행하지 않는다. -- **출력 제약:** - - `LLMGenerationResult.text`는 provider raw text를 최대한 보존한 문자열이어야 하며, 빈 문자열 또는 공백-only 문자열이면 안 된다. - - `{"used_refs": [...]}` 블록이 포함되어 있으면 `text` 안에 그대로 유지되어 Search Service가 후속 파싱할 수 있어야 한다. - - `provider_request_id`, `token_usage`, `finish_reason`은 운영/관측용 선택 메타데이터이며, provider가 값을 주지 않으면 `None`을 허용한다. - - adapter는 형식 안정성을 위해 provider의 structured output / JSON mode / response schema 기능을 내부 구현으로 사용할 수 있으나, 호출자에게 provider-specific 모드를 노출하지 않는다. - -#### [STTAdapter] - -```python -@dataclass -class TranscriptSegmentDTO: - text: str - start_ms: int - end_ms: int - - -@dataclass -class STTTranscriptionResult: - segments: list[TranscriptSegmentDTO] - stt_model_version: str - - -class STTAdapter(Protocol): - async def transcribe( - self, - *, - audio_path: str, - trace_id: str, - ) -> STTTranscriptionResult: - """ - 로컬 오디오 파일을 외부 STT API로 전송하여 - 타임스탬프를 포함한 세그먼트 목록과 STT 모델 버전을 반환한다. - """ -``` - -- **입력 제약:** - - `audio_path`는 Pipeline Worker가 생성한 로컬 파일 경로이다. - - V1에서 입력 오디오 포맷은 `mono, 16kHz, 16-bit PCM, FLAC`으로 고정한다. - - adapter는 오디오 포맷 변환을 수행하지 않는다. 입력 검증 실패는 `INVALID_REQUEST`로 처리한다. - - V1에서 `category` 또는 별도 domain hint는 adapter request contract에 포함하지 않는다. -- **출력 제약:** - - `STTTranscriptionResult.segments`는 `start_ms ASC`로 정렬되어야 한다. - - 각 항목은 `0 <= start_ms <= end_ms`를 만족해야 한다. - - 빈 세그먼트 목록은 허용된다. 후속 청킹/실패 판단은 Pipeline Worker가 담당한다. - - `stt_model_version`은 provider가 실제로 사용한 모델 식별자의 공식 모델명 이어야 한다. - - Pipeline Worker는 이 값을 `TranscriptSegment.stt_model_version`에 그대로 영속화한다. - -### 2.2 Data Access (Reads & Writes) - -| Type | Store | Entity/Table | Key/Filter | Mutation/Action | Notes | -| --- | --- | --- | --- | --- | --- | -| Read | Local Filesystem | 로컬 오디오 파일 | `audio_path` | READ | `GoogleSTTAdapter` 입력 파일. 파일 존재 여부와 접근 가능성만 확인한다. | -| Call | External LLM API | Gemini provider | model name, prompt | GENERATE | 네트워크 호출. DB 영속화 없음. | -| Call | External STT API | Google Cloud Speech-to-Text | project context, audio bytes | RECOGNIZE | 네트워크 호출. DB 영속화 없음. | - -> 이 컴포넌트는 SOT 테이블을 소유하지 않으며, Metadata DB / Vector Store / Object Storage에 직접 쓰지 않는다. - -### 2.3 SLA & Constraints - -#### LLM 호출 바인딩 (Search Service) - -| Caller | Adapter | Timeout | Retryable 조건 | Max Retries | Circuit Breaker | 최종 실패 | -| --- | --- | --- | --- | --- | --- | --- | -| Search Service | `GeminiLLMAdapter` | `LLM_TIMEOUT_SEC` (default: `3`) | timeout, 429, 503 | `LLM_MAX_RETRIES` (default: `1`) | `LLM_CB_FAILURE_THRESHOLD` / `LLM_CB_RECOVERY_SEC` (default: `3` / `30`) | retryable 최종 실패는 Search `503`, non-retryable 최종 실패는 Search `500` | - -- `GeminiLLMAdapter`는 자체 대기열을 두지 않는다. -- retry와 circuit breaker는 adapter 내부에서 적용하되, 값의 SOT는 Search Service spec이다. -- Search Service는 배포 단위 고정 generation profile을 wiring 시점에 주입하며, V1에서는 per-request profile switching을 하지 않는다. -- final failure 시 adapter는 retryable 여부가 보존된 예외를 호출자에게 전달한다. -- 성공 시 adapter는 `LLMGenerationResult`를 반환하며, Search Service는 `text`를 기능 처리에 사용하고 선택 메타데이터는 로깅/메트릭에만 사용한다. - -#### STT 호출 바인딩 (Pipeline Worker) - -| Caller | Adapter | Timeout | Retryable 조건 | Max Retries | Circuit Breaker | 최종 실패 | -| --- | --- | --- | --- | --- | --- | --- | -| Pipeline Worker | `GoogleSTTAdapter` | `STT_TIMEOUT_SEC` (default: `120`) | timeout, 429, 503, 일시적 네트워크 오류 | `MAX_RETRIES` (default: `3`) | V1에서는 필수 아님 | Pipeline Worker가 `FAILED`/재시도 정책으로 해석 | - -- `GoogleSTTAdapter`는 caller가 지정한 retry budget 안에서만 재시도한다. -- V1에서는 STT용 별도 circuit breaker를 필수 계약으로 두지 않는다. 필요한 경우 호출자 wiring에서 옵션으로 추가할 수 있다. -- provider raw transcript를 그대로 노출하지 않고, `STTTranscriptionResult`로 정규화된 결과만 반환한다. -- `stt_model_version`은 실제 사용된 모델의 공식 식별자여야 하며, 값을 모를 경우 빈 문자열이나 `unknown` 같은 임시값으로 대체하지 않는다. 확인할 수 없으면 `INTERNAL_ERROR`로 처리한다. -- Pipeline Worker는 배포 단위 고정 transcription profile을 wiring 시점에 주입하며, V1에서는 category 기반 STT adaptation을 사용하지 않는다. - -#### 공통 제약 - -- adapter는 입력 텍스트 정규화를 수행하지 않는다. - - query normalization은 Search Service 책임이다. - - `enriched_text` 정규화는 Pipeline Worker 및 Managed Embedding Endpoint 호출자 책임이다. -- V1 adapter request 시그니처에는 raw provider 파라미터, absolute deadline, cancellation token을 포함하지 않는다. -- adapter는 응답 원문을 일반 운영 로그에 기록하지 않는다. -- adapter는 내부 큐 기반의 hidden buffering을 두지 않는다. 과부하/불능 상태는 빠르게 surface한다. - -### 2.4 Error Contract & Messaging Semantics - -이 컴포넌트는 외부 공개 HTTP API를 직접 노출하지 않는다. 따라서 상위 서비스의 `{"code","message","trace_id"}` HTTP envelope를 직접 생성하지 않는다. 대신 아래의 구조화된 내부 예외 계약을 제공하며, 호출자가 이를 HTTP/MQ 오류 의미로 매핑한다. - -```python -@dataclass -class ExternalAIAdapterError(Exception): - code: str - message: str - trace_id: str - provider: str - retryable: bool -``` - -| Error Code | 발생 조건 | Retryable | Caller-side 예시 매핑 | -| --- | --- | --- | --- | -| `INVALID_REQUEST` | caller가 adapter contract를 위반한 입력, 파일 없음, 로컬 입력 검증 실패 | N | Worker `FAILED`, Search는 내부 오류로 취급 금지 | -| `AUTH_ERROR` | provider 인증 실패, credential/project/model 접근 권한 오류 | N | Search `500 INTERNAL_ERROR`, Worker `FAILED` | -| `TIMEOUT` | provider 응답 timeout | Y | Search `503`, Worker 재시도 | -| `RATE_LIMITED` | provider 429 | Y | Search 재시도 후 `503`, Worker 재시도 | -| `UNAVAILABLE` | provider 503/일시적 장애 | Y | Search `503`, Worker 재시도 | -| `CIRCUIT_OPEN` | circuit breaker open 상태 | Y | Search `503`, Worker 재시도 가능 | -| `INTERNAL_ERROR` | adapter 코드 결함, invariant 위반, 비정상 provider 응답, 내부 policy/config 매핑 오류 | N | Search `500 INTERNAL_ERROR`, Worker `FAILED` | - -- `GeminiLLMAdapter`는 `used_refs` 파싱 실패를 이 컴포넌트 오류로 간주하지 않는다. 파싱은 Search Service 책임이다. -- `GeminiLLMAdapter`는 `used_refs`의 semantic correctness를 검증하지 않으며, citation integrity 관측은 Search Service 책임이다. -- `GoogleSTTAdapter`는 provider 응답을 `TranscriptSegmentDTO`로 해석할 수 없는 경우 `INTERNAL_ERROR`를 raise한다. -- provider가 4xx를 반환하더라도 원인이 adapter 내부 policy/config 매핑 결함이면 `INVALID_REQUEST`가 아니라 `INTERNAL_ERROR`로 분류한다. -- 에러 상세(`provider_status`, `raw_reason`, `failed_index` 등)는 API 계약으로 노출하지 않고 구조화 로그에만 남긴다. - ---- - -## 3. Core Design & Logic - -### 3.1 주요 흐름 (Sequence) - -#### Search Service -> `GeminiLLMAdapter` - -1. Search Service `prompt_builder`가 최종 prompt 문자열을 조립한다. -2. `GeminiLLMAdapter.generate(prompt, trace_id)`를 호출한다. -3. adapter는 timeout / retry / circuit breaker 정책을 적용하여 Gemini API를 호출한다. -4. 성공 시 `LLMGenerationResult`를 반환한다. `text`에는 raw provider text가 보존되며, 선택 메타데이터(`provider_request_id`, `token_usage`, `finish_reason`)는 제공 가능할 때만 채워진다. -5. Search Service가 `llm_result.text`에서 `used_refs`를 파싱하고 `citations`, `used_chunk_ids`를 생성한다. 선택 메타데이터는 caller-side logging/metrics에만 사용한다. - -#### Pipeline Worker -> `GoogleSTTAdapter` - -1. Pipeline Worker가 로컬 오디오 파일을 준비한다. -2. `GoogleSTTAdapter.transcribe(audio_path, trace_id)`를 호출한다. -3. adapter는 파일 존재 여부와 기본 접근 가능성을 확인한다. -4. timeout / retry 정책을 적용하여 Google Cloud STT API를 호출한다. -5. provider 응답을 `STTTranscriptionResult`로 정규화하고, 세그먼트를 `start_ms ASC`로 정렬하며 `stt_model_version`을 채운다. -6. Pipeline Worker가 `segments`를 `TranscriptSegment` 테이블에 bulk insert하고, `stt_model_version`을 함께 영속화한 뒤 후속 청킹을 수행한다. - -### 3.2 상태 전이 (State Machine) - -> 도메인 엔티티 상태가 아니라 adapter 인스턴스의 circuit breaker 상태를 설명한다. - -| From | To | Actor | Trigger | Guard | Side Effects | -| --- | --- | --- | --- | --- | --- | -| `CLOSED` | `OPEN` | Adapter runtime | 연속 실패 임계치 초과 | caller binding에 circuit breaker가 configured | 신규 호출 즉시 `CIRCUIT_OPEN` 예외 | -| `OPEN` | `HALF_OPEN` | Adapter runtime | recovery time 경과 | caller binding에 circuit breaker가 configured | probe 호출 1건 허용 | -| `HALF_OPEN` | `CLOSED` | Adapter runtime | probe 성공 | N/A | 연속 실패 카운터 초기화 | -| `HALF_OPEN` | `OPEN` | Adapter runtime | probe 실패 | N/A | recovery timer 재시작 | - -> `GoogleSTTAdapter`는 V1에서 circuit breaker가 필수가 아니므로, 이 기능이 설정된 경우에만 위 상태 전이를 적용한다. -> V1 circuit breaker 상태는 프로세스 로컬 상태이며, replica 간 공유 breaker는 범위 밖이다. - -### 3.3 멱등성 및 복구 (Resilience) - -- 이 컴포넌트는 DB에 쓰지 않으므로, 자체 멱등 키나 resume state를 소유하지 않는다. -- retry는 adapter 내부의 provider 호출 재시도만 의미한다. 상위 서비스의 HTTP 재시도/MQ 재처리와는 별개다. -- `GeminiLLMAdapter`는 overload 상황을 숨기기 위한 내부 대기열을 두지 않는다. -- `GoogleSTTAdapter`는 오디오 파일을 수정하거나 재인코딩하지 않는다. -- `MockLLMAdapter`와 `MockSTTAdapter`는 같은 입력에 대해 항상 같은 테스트 응답을 반환해야 하며, 네트워크를 사용하지 않는다. -- `video_id` 단위의 영속 재시도 총량 제한은 adapter가 아니라 caller/SOT 레이어의 책임이며, V1 adapter contract에는 포함하지 않는다. - -### 3.4 Data Consistency & Orphan Prevention - -- 이 컴포넌트는 외부 provider 호출 결과를 직접 영속화하지 않는다. -- 로컬 오디오 파일의 생성/삭제 책임은 Pipeline Worker에 있다. -- LLM 결과 `text`의 저장/파싱 책임은 Search Service에 있다. -- 따라서 orphan data 방지는 상위 서비스 경계에서 수행하며, 이 컴포넌트는 불완전한 외부 응답을 즉시 예외로 승격하여 잘못된 데이터 적재를 방지한다. - ---- - -## 4. Observability & Ops - -- **Logging:** - - 모든 구조화 로그에 `trace_id`, `adapter_type`, `provider`, `latency_ms`, `retry_count`, `final_error_code`를 포함한다. - - LLM adapter는 `model_name`을 로그에 포함하며, 값이 있으면 `provider_request_id`, `finish_reason`, `token_usage` 요약도 함께 기록한다. - - STT adapter는 `audio_path` 원문 대신 파일 존재 여부, 파일 크기, `audio_duration_sec`, 호출 결과 세그먼트 수를 기록한다. - - prompt 원문, transcript 원문, provider raw payload는 일반 운영 로그에 기록하지 않는다. -- **Metrics:** - - `external_ai_adapter_call_latency_ms{adapter_type,provider}` - - `external_ai_adapter_call_count{adapter_type,provider,status}` - - `external_ai_adapter_retry_count{adapter_type,provider}` - - `external_ai_adapter_circuit_breaker_state{adapter_type,provider}` - - `external_ai_adapter_llm_input_tokens_total{provider,model_name}` - - `external_ai_adapter_llm_output_tokens_total{provider,model_name}` - - `external_ai_adapter_llm_total_tokens_total{provider,model_name}` - - `external_ai_adapter_stt_audio_duration_sec_total{provider}` - - `external_ai_adapter_stt_billed_audio_sec_total{provider}` (provider가 billed duration을 제공하는 경우에만) -- **Alerts:** 임계치는 인프라/운영 환경에서 결정한다. 최소 감시 대상은 timeout 급증, retryable failure rate 급증, circuit breaker open 지속 시간이다. -- **Trace Propagation:** - - adapter는 호출자에게서 받은 `trace_id`를 변경하지 않는다. - - provider SDK가 사용자 정의 correlation field를 허용하면 동일 `trace_id`를 함께 전달한다. - - provider가 이를 지원하지 않더라도 local logs와 caller logs에서 같은 `trace_id`를 유지해야 한다. -- **Caller-side Observability Boundary:** - - `used_refs` 파싱 성공 여부와 citation integrity 저하 관측은 이 컴포넌트 책임이 아니며, Search Service가 caller-side observability로 수집한다. -- **Credentials & Rotation:** - - API key와 provider credential은 프로세스 시작 시 로드되며, rotation 반영에는 재배포 또는 재시작이 필요하다. - - hot reload는 V1 범위에 포함하지 않는다. - ---- - -## 5. Acceptance Criteria (DoD) - -### 5.1 시나리오 검증 - -#### `GeminiLLMAdapter.generate()` - -**정상** -* [ ] Search Service가 조립한 prompt를 입력받아 Gemini API 호출 후 `LLMGenerationResult`를 반환한다. -* [ ] `LLMGenerationResult.text`는 non-empty 문자열이며, raw text 안의 `{"used_refs": [1, 2]}` JSON 블록을 adapter가 손상시키지 않고 그대로 전달한다. -* [ ] `provider_request_id`, `token_usage`, `finish_reason`은 provider가 값을 주지 않으면 `None`이어도 성공으로 처리한다. -* [ ] 일반 운영 로그에 prompt 원문이 기록되지 않는다. - -**예외** -* [ ] timeout 발생 시 Search binding 기준으로 최대 `LLM_MAX_RETRIES`회 재시도 후 실패 시 retryable `ExternalAIAdapterError(code="TIMEOUT")`를 raise한다. -* [ ] 429 발생 시 retryable `ExternalAIAdapterError(code="RATE_LIMITED")`를 raise한다. -* [ ] 503 발생 시 retryable `ExternalAIAdapterError(code="UNAVAILABLE")`를 raise한다. -* [ ] circuit breaker open 상태에서는 provider 호출 없이 즉시 `CIRCUIT_OPEN` 예외를 raise한다. -* [ ] provider 인증 실패, credential/project/model 접근 권한 오류는 `AUTH_ERROR`를 raise한다. -* [ ] provider 응답 구조를 해석할 수 없거나 `text`가 빈 문자열/공백-only 문자열이면 `INTERNAL_ERROR`를 raise한다. -* [ ] provider가 4xx를 반환해도 원인이 내부 policy/config 매핑 결함이면 `INTERNAL_ERROR`를 raise한다. - -#### `GoogleSTTAdapter.transcribe()` - -**정상** -* [ ] 로컬 FLAC 오디오 파일을 입력받아 `STTTranscriptionResult`를 반환한다. -* [ ] 반환 결과는 `STTTranscriptionResult`이며, `stt_model_version`이 비어 있지 않다. -* [ ] 반환 세그먼트는 `start_ms ASC`로 정렬되어 있다. -* [ ] 일반 운영 로그에 transcript 원문이나 오디오 바이트가 기록되지 않는다. - -**예외** -* [ ] `audio_path`가 없거나 읽을 수 없으면 `INVALID_REQUEST` 예외를 raise한다. -* [ ] timeout 발생 시 Pipeline Worker binding 기준으로 재시도 후 retryable `TIMEOUT` 예외를 raise한다. -* [ ] 429 또는 503 발생 시 retryable `RATE_LIMITED` / `UNAVAILABLE` 예외를 raise한다. -* [ ] provider 응답의 타임스탬프를 `TranscriptSegmentDTO`로 변환할 수 없으면 `INTERNAL_ERROR`를 raise한다. -* [ ] `stt_model_version`을 확정할 수 없으면 `INTERNAL_ERROR`를 raise한다. - -#### Mock adapters - -**정상** -* [ ] `MockLLMAdapter`는 지정된 고정 `LLMGenerationResult`를 deterministic하게 반환한다. -* [ ] `MockSTTAdapter`는 지정된 세그먼트 목록과 `stt_model_version`을 deterministic하게 반환한다. -* [ ] mock 구현체는 네트워크나 provider SDK를 호출하지 않는다. - -### 5.2 검증을 위한 테스팅 전략 (Testing Strategy) - -에이전트는 아래 가이드라인을 만족하는 자동화 테스트를 작성해야 한다. -* 테스트 프레임워크는 `pytest`, `pytest-asyncio`를 사용한다. -* **커버리지 목표:** 단위 테스트 중심으로 adapter 예외 분류와 retry/circuit breaker 분기를 충분히 커버한다. -* **외부 의존성 격리 전략:** - * Gemini SDK 호출은 `AsyncMock` 또는 provider client test double로 대체한다. - * Google Cloud STT SDK 호출은 `AsyncMock` 또는 provider client test double로 대체한다. - * mock adapter 테스트는 실제 네트워크 호출 없이 수행한다. -* **필수 분기:** - * LLM 선택 메타데이터 누락(`provider_request_id=None`, `token_usage=None`, `finish_reason=None`) 성공 처리 - * LLM `text` 빈 문자열/공백-only 처리 - * provider 인증 실패(`AUTH_ERROR`)와 retryable 오류 구분 -* Search Service와 Pipeline Worker는 이 spec을 소비하는 쪽이므로, cross-component 통합 테스트에서는 각 caller가 adapter 예외를 자신의 오류 계약으로 올바르게 변환하는지 별도 검증한다. - -### 5.3 산출물 (Artifacts) - -폴더 구조는 `docs/Tech_Spec/folder_structure.md`를 참조한다. - -* [ ] `LLMAdapter`, `STTAdapter` 추상 인터페이스 -* [ ] `LLMGenerationResult`, `TokenUsageDTO` -* [ ] `ExternalAIAdapterError` 및 공통 오류 코드 정의 -* [ ] `GeminiLLMAdapter` -* [ ] `GoogleSTTAdapter` -* [ ] `MockLLMAdapter`, `MockSTTAdapter` -* [ ] adapter runtime policy 적용 레이어 (timeout / retry / circuit breaker) -* [ ] 단위 테스트 diff --git a/docs/Tech_Spec/folder_structure.md b/docs/Tech_Spec/folder_structure.md deleted file mode 100644 index b218bd1..0000000 --- a/docs/Tech_Spec/folder_structure.md +++ /dev/null @@ -1,91 +0,0 @@ -# 프로젝트 폴더 구조 - -> 각 컴포넌트의 권장 폴더 구조를 정의한다. 파일명은 구현 시 상황에 따라 조정 가능하다. - ---- - -## Core API Server - -``` -src/ -├── api/v1/routers/ — HTTP 라우터 (영상 업로드, 완료, 조회, 수정, 삭제) -├── schemas/ — Pydantic 요청/응답 DTO, cursor DTO -├── services/ — 비즈니스 로직 (상태 전이, 멱등성, Reconciler) -├── models/ — SQLAlchemy ORM 모델 -├── infra/ -│ ├── db/ — Repository, 트랜잭션 경계 -│ ├── storage/ — StorageClient 인터페이스 및 구현체 (GCS, InMemory) -│ └── broker/ — BrokerClient 인터페이스 및 구현체 (RabbitMQ, InMemory) -└── core/ — Settings, 공통 미들웨어 - -tests/ -├── unit/ — InMemory 구현체로 격리한 단위 테스트 -└── integration/ — Testcontainers 기반 DB 통합 테스트 -``` - ---- - -## Pipeline Worker - -``` -src/ -├── config/ — Settings, 환경 변수 관리 -├── usecases/ — 비디오 처리 유스케이스 (상태 전이, Resume 로직) -├── services/ — Chunking, 파이프라인 오케스트레이션 -├── adapters/ -│ ├── queue/ — 메시지 브로커 컨슈머, DLQ 라우터 -│ ├── db/ — Video/Chunk/Asset Repository -│ ├── storage/ — StorageClient 인터페이스 및 구현체 (GCS, InMemory) -│ ├── ai/ — Gemini, Embedding 외부 AI 어댑터 -│ └── media/ — FFmpeg 미디어 처리 어댑터 (오디오 추출, 키프레임 추출) -├── schemas/ — 이벤트 메시지 스키마 (Pydantic) -└── utils/ — 미디어 Context Manager 등 유틸 - -tests/ -├── unit/ — AsyncMock 기반 단위 테스트 -└── integration/ — Testcontainers 기반 DB 통합 테스트 -``` - ---- - -## Search Service - -``` -src/ -├── api/v1/routers/ — HTTP 라우터 (/api/v1/search) -├── api/v1/schemas/ — 요청/응답 스키마 -├── services/ — 검색 오케스트레이터, RRF 병합, 프롬프트 빌더 -├── infra/ -│ ├── db/ — Chunk/Video Repository (pgvector 포함) -│ └── ai_adapters/ — Embedding, Gemini 어댑터 -├── middlewares/ — JWT 인증, 에러 핸들러 -└── observability/ — 구조화 로깅, 메트릭 - -tests/ -├── unit/ — AsyncMock 기반 단위 테스트 -├── integration/ — Testcontainers 기반 DB 통합 테스트 -└── perf/ — Locust 성능 테스트 -``` - ---- - -## Managed Embedding Endpoint - -``` -src/ -├── api/routers/ — HTTP 라우터 (POST /embed, GET /health) -├── schemas/ — Pydantic 요청/응답 DTO (EmbedRequest, EmbedResponse, HealthResponse) -├── services/ -│ └── inference_service.py — 텍스트 임베딩 추론, 입력 순서 보장 -├── infra/ -│ └── model_loader.py — 구성된 모델 파일/디렉토리 로드, 버전 노출, 메모리 적재/해제 -├── middlewares/ — Trace ID 추출 및 전파 미들웨어 -├── observability/ — 구조화 로깅, 메트릭 (embed_request_latency_ms 등) -└── core/ - ├── settings.py — pydantic-settings 기반 환경변수 관리 - └── model_state.py — 모델 로딩 상태 플래그 (로딩 중 / 완료) - -tests/ -├── unit/ — 더미 임베딩 모델 스텁 기반 단위 테스트 -└── integration/ — E2E 통합 테스트 (더미 모델 스텁 사용) -``` diff --git a/docs/Tech_Spec/plan_template.md b/docs/Tech_Spec/plan_template.md deleted file mode 100644 index f2bf10a..0000000 --- a/docs/Tech_Spec/plan_template.md +++ /dev/null @@ -1,205 +0,0 @@ -# [ComponentName] PLAN - -**Meta** -- **Component ID:** (예: core-api-server / pipeline-worker / search-service) -- **Target SPEC:** `./spec.md` (이 PLAN이 구현할 스펙 문서의 경로) -- **SOT:** `docs/system-design.md`, `(Target SPEC 경로)`, `(필요 시 관련 ADR / 관련 Tech Spec)` - ---- - -> 이 PLAN은 특정 구현자나 특정 에이전트 도구에 종속되지 않는 실행 계획 문서다. -> 목표는 구현자가 `SPEC + PLAN`만 읽고도 작업 순서, 검증 기준, 통합 경계를 오해 없이 이해할 수 있게 만드는 것이다. - -## 1. Goals & Strategy - -### 1.1 달성 목표 (Goals) - -- **(핵심 기능 A):** (E2E 흐름 완성 목표 — 정상/예외 경로 포함하여 명시) -- **(핵심 기능 B):** (추가 주요 흐름이 있으면 bullet 추가) -- **관측성:** 모든 요청·발행 로그에 `trace_id`를 포함하고, 핵심 메트릭을 노출한다. -- **테스트:** (통합 테스트 환경 — 예: Postgres/Testcontainers)을 기반으로 SPEC §5.1에 정의된 시나리오를 모두 충족하고, 단위·통합 테스트 합산 커버리지 (목표 %) 이상을 달성한다. - -### 1.2 제외 대상 (Non-Goals) -- (이번 PLAN에서 의도적으로 배제하는 작업) - - *예: 실패 시 DLQ(Dead Letter Queue) 재처리 로직은 다음 페이즈에서 진행함.* - -### 1.3 리스크 및 대응 방안 (Risk & Mitigation) -- **위험 요소:** (예: 외부 STT API 응답 지연으로 인한 워커 스레드 고갈) -- **대응 방안:** (예: HTTP Timeout을 30초로 강제하고, 서킷 브레이커 패턴 적용) - -### 1.4 구현 전제 및 열려 있는 결정사항 (Preconditions & Open Decisions) - -- **구현 전제:** (예: Target SPEC의 상태 전이 / 메시지 계약 / DDL이 확정되어 있음) -- **선행 필요 사항:** (예: 공용 인증 모듈 선구현 필요, 공용 라이브러리 버전 확정 필요) -- **열려 있는 결정사항:** 구현을 막는 미확정 항목이 있다면 `Open Decision`, `Blocker`, `Pending Decision` 같은 일반적인 표기로 명시한다. - -### 1.5 핵심 의존성 패키지 - -| 패키지 | 용도 | 최소 버전 | -| --- | --- | --- | -| `(패키지명)` | (용도 설명) | (x.x+) | -| ... | ... | ... | - ---- - -## 2. Implementation Phasing Strategy - -- **Phase 1:** (스캐폴딩 및 인터페이스) 앱 팩토리/엔트리포인트, 설정 로딩, DTO/스키마, 라우터/컨슈머 스켈레톤, 공통 미들웨어. -- **Phase 2:** (영속성 및 핵심 유스케이스) Repository/트랜잭션 경계, 상태 전이, 외부 의존성 어댑터, 멱등성/복구 로직. -- **Phase 3:** (통합 및 운영) 관측성(로그·메트릭), 통합 테스트, 마이그레이션/배포 검증, 성능 스모크. -- **병합 게이트:** 각 단계 또는 자율적으로 나눈 PR 단위로 CI(단위·통합 테스트) 통과 + 1인 이상 리뷰. - -### 2.1 작업 분해 원칙 (Task Decomposition Rules) - -- 각 Task는 하나의 명확한 출력물과 하나의 검증 가능한 완료 조건을 가져야 한다. -- 병렬 작업이 가능하도록 Task 간 파일 수정 범위와 계약 경계를 최대한 분리한다. -- 선행 작업이 있는 경우 `Depends On`으로 명시하고, 독립 수행 가능하면 `병렬 가능`으로 표시한다. -- 각 Phase는 중간 병합 가능한 상태여야 하며, 부분 구현만 남은 채 다음 Phase로 넘어가지 않는다. -- 구현 순서는 레이어 나열보다 “기능 슬라이스가 검증 가능한 순서”를 우선한다. - -### 2.2 선행 경로 및 병렬 가능 범위 (Critical Path & Parallelism) - -- **Critical Path:** 반드시 먼저 완료되어야 하는 작업 흐름을 bullet로 적는다. -- **Parallelizable Workstreams:** 계약이 고정된 뒤 병렬로 진행 가능한 작업 묶음을 적는다. -- **Merge Owner / Integration Point:** 병렬 작업 결과를 어디서 통합하고 어떤 검증을 거칠지 적는다. - ---- - -## 3. Work Breakdown Structure (WBS) - -> 구현자가 그대로 실행할 수 있는 구체 작업 지시서. -> 모든 작업은 반드시 `Output / Files / Test Files / Commands / Verify / Linked AC / Depends On`를 포함해야 한다. -> 병렬화 가능한 작업은 `병렬 가능: Y` 또는 `병렬 가능: N`으로 표시한다. - -### Phase 1: Skeleton & Contracts - -- [ ] **Task 0: 프로젝트 엔트리포인트 및 설정 스캐폴딩** - - **Output:** 앱 엔트리포인트, 설정 로딩, 기본 디렉토리 구조, 공통 의존성 주입 뼈대 - - **Files:** `src/main.py`, `src/core/config.py`, `.env.example` - - **Test Files:** `(필요 시) tests/unit/test_config.py` - - **Commands:** `pytest tests/unit/test_config.py` / `(프로젝트 실행 확인 명령)` - - **Verify:** 필수 환경 변수 누락 시 실패하고, 정상 설정으로 앱이 기동한다. - - **Linked AC:** SPEC §(관련 계약 섹션), DoD §5.1의 공통 전제 - - **Depends On:** 없음 - - **병렬 가능:** N - -- [ ] **Task 1: DTO/Validation 및 인터페이스 스켈레톤** - - **Output:** 요청/응답 스키마, 기본 라우터/컨슈머 시그니처, 유효성 검증 규칙 - - **Files:** `src/schemas/...`, `src/api/...` 또는 `src/adapters/...` - - **Test Files:** `tests/unit/test_*.py` (유효성 검사 및 기본 예외 흐름) - - **Commands:** `pytest tests/unit/test_*.py` - - **Verify:** 잘못된 입력이 SPEC의 Error Contract에 맞는 에러로 매핑된다. - - **Linked AC:** SPEC §2.1, §2.4, DoD §5.1 해당 시나리오 - - **Depends On:** Task 0 - - **병렬 가능:** Y - -### Phase 2: Core Logic & Persistence - -- [ ] **Task 2: 저장소/트랜잭션 경계 구현** - - **Output:** SOT 저장소 접근 로직, 트랜잭션 경계, 조회/수정 필터, 필요한 인덱스 사용 경로 - - **Files:** `src/infra/db/...`, `src/models/...`, `alembic/...` - - **Test Files:** `tests/integration/test_*repository.py` - - **Commands:** `alembic upgrade head`, `pytest tests/integration/test_*repository.py` - - **Verify:** 정상 저장/조회/롤백 및 테넌시 필터가 통합 테스트로 재현된다. - - **Linked AC:** SPEC §2.2, §2.5, DoD §5.1 해당 시나리오 - - **Depends On:** Task 0 - - **병렬 가능:** Y - -- [ ] **Task 3: 핵심 유스케이스 및 상태 전이 구현** - - **Output:** 핵심 비즈니스 로직, 상태 전이 가드, 멱등성/재시도/복구 규칙 - - **Files:** `src/services/...` 또는 `src/usecases/...` - - **Test Files:** `tests/unit/test_*service.py` - - **Commands:** `pytest tests/unit/test_*service.py` - - **Verify:** 동일 입력 재시도 시 허용된 부수 효과만 발생하고, 상태 전이가 SPEC과 일치한다. - - **Linked AC:** SPEC §3.2, §3.3, DoD §5.1 해당 시나리오 - - **Depends On:** Task 1, Task 2 - - **병렬 가능:** N - -- [ ] **Task 4: 외부 의존성 어댑터 및 인프라 연동** - - **Output:** Storage/Broker/외부 API 클라이언트 인터페이스 및 운영/테스트 구현체 - - **Files:** `src/infra/storage/...`, `src/infra/broker/...`, `src/infra/external/...` - - **Test Files:** `tests/unit/test_*client.py` - - **Commands:** `pytest tests/unit/test_*client.py` - - **Verify:** 메시지/요청 페이로드, 타임아웃, 재시도 정책이 계약과 일치한다. - - **Linked AC:** SPEC §2.1, §2.3, §2.4, DoD §5.1 해당 시나리오 - - **Depends On:** Task 1 - - **병렬 가능:** Y - -### Phase 3: Integration & Ops - -- [ ] **Task 5: 라우터/컨슈머와 서비스 통합** - - **Output:** 인터페이스 계층과 서비스/저장소/어댑터가 실제 흐름으로 연결된 실행 가능한 기능 - - **Files:** `src/api/...`, `src/services/...`, `src/infra/...` - - **Test Files:** `tests/api/test_*.py` 또는 `tests/integration/test_*flow.py` - - **Commands:** `pytest tests/api/test_*.py` 또는 `pytest tests/integration/test_*flow.py` - - **Verify:** 주요 정상/예외 흐름이 엔드투엔드 또는 통합 테스트로 통과한다. - - **Linked AC:** DoD §5.1의 대상 시나리오 묶음 - - **Depends On:** Task 2, Task 3, Task 4 - - **병렬 가능:** N - -- [ ] **Task 6: 관측성 및 전역 예외 처리 적용** - - **Output:** 구조화 로깅(`trace_id` 포함), 핵심 메트릭, 공통 에러 응답/예외 매핑 - - **Files:** `src/common/...`, `src/middlewares/...`, `src/observability/...` - - **Test Files:** `tests/unit/test_error_handler.py`, `(필요 시) tests/unit/test_metrics.py` - - **Commands:** `pytest tests/unit/test_error_handler.py` - - **Verify:** 성공/실패 경로 모두에서 추적 필드와 에러 응답 계약이 일관된다. - - **Linked AC:** SPEC §4, SPEC §2.4, DoD §5.2 - - **Depends On:** Task 1, Task 3 - - **병렬 가능:** Y - -- [ ] **Task 7: 최종 검증 및 릴리스 준비** - - **Output:** 통합 체크리스트 완료, 배포/롤백 절차 검증, 커버리지 및 CI 기준 충족 - - **Files:** `.github/workflows/...`, `docs/...` 또는 배포 설정 파일 - - **Test Files:** 전체 테스트 스위트 - - **Commands:** `(lint 명령)`, `(type check 명령)`, `pytest`, `pytest --cov`, `alembic upgrade head` - - **Verify:** 완료 조건과 병합 게이트를 모두 만족한다. - - **Linked AC:** DoD §5.1, §5.2, §5.3 - - **Depends On:** Task 5, Task 6 - - **병렬 가능:** N - ---- - -## 4. Integration Checklist & Done Criteria - -> 컴포넌트 통합 및 최종 완료 판정을 위한 체크리스트 - -### 4.1 통합 체크리스트 (Integration Checklist) -- [ ] **SPEC 추적성:** 모든 구현 대상 엔드포인트/메시지/상태 전이가 SPEC의 명시된 계약과 연결되어 있는가? -- [ ] **계약 정합성:** API/MQ/스토리지/스키마 필드가 관련 컴포넌트 문서의 기대 버전과 일치하는가? -- [ ] **테넌시 및 권한:** 데이터 조회/조작 시 `user_id` 또는 동등한 권한 격리 규칙이 모든 레이어에 적용되었는가? -- [ ] **네트워크 복원력:** 외부 연동 포인트에 적절한 Timeout, Retry, Circuit Breaker 또는 대체 정책이 적용되었는가? -- [ ] **데이터 일관성:** SOT와 파생 데이터 간 부분 실패 시 보정/정리 로직이 동작하는가? -- [ ] **마이그레이션/초기화 재현성:** 새로운 환경에서 스키마/설정/필수 리소스를 재현 가능한가? -- [ ] **공유 계약 검토:** 관련 Tech Spec 문서를 확인했고, 남은 충돌이 없는가? - -### 4.2 완료 조건 (Definition of Done) -- [ ] SPEC §5.1에 정의된 시나리오 테스트가 모두 녹색이다. -- [ ] 단위·통합 테스트 합산 커버리지가 (목표 %) 이상이다 (`pytest-cov` 기준). -- [ ] 단위·통합 테스트가 CI에서 통과한다. -- [ ] 핵심 메트릭이 정상 노출된다. - ---- - -## 5. Rollout & Rollback Plan - -> 배포 및 장애 발생 시 데이터/스키마 원복(되돌리기) 계획 - -### 5.1 배포 계획 (Rollout) -- **환경 변수 추가:** (필수 환경 변수 목록과 기본 검증 방식) -- **인프라/스키마 변경:** 배포 전 필요한 마이그레이션/리소스 생성 절차 -- **호환성 확인:** 기존 메시지/스키마/클라이언트와의 호환성 영향 여부 - -### 5.2 롤백 계획 (Rollback) -- **애플리케이션 롤백:** 이전 버전 이미지/아티팩트로 즉시 복귀하는 절차 -- **데이터베이스 스키마 원복:** 역방향 마이그레이션 또는 안전한 복원 전략 -- **메시지/비동기 호환성:** 큐/토픽에 남아 있는 신규 포맷 메시지 처리 방안 -- **부분 적용 복구:** 배포 도중 일부 단계만 반영된 경우의 복구 순서 - ---- - -## Assumptions (확정된 사항) - -- (확정된 기술 선택, 도구, 외부 의존성 전제 조건을 기술한다) -- *예: DB 마이그레이션 도구는 Alembic을 사용한다.* -- *예: 통합 테스트는 Testcontainers 기반으로 수행한다.* -- *예: Worker의 멱등성은 system-design SOT를 준수하여, 중복 메시지를 안전하게 처리한다고 가정한다.* diff --git a/docs/Tech_Spec/spec_template.md b/docs/Tech_Spec/spec_template.md deleted file mode 100644 index 7d06f86..0000000 --- a/docs/Tech_Spec/spec_template.md +++ /dev/null @@ -1,195 +0,0 @@ -# [ComponentName] SPEC - -**Meta** -- **Component ID:** (예: core-api-server / pipeline-worker / search-service) -- **SOT References:** [PRD], [System Design], [Plan], [Sequence Diagram 등] - ---- - -## 1. Context & Scope - -### 1.1 목적 (Purpose) -- **한 줄 요약:** 컴포넌트가 시스템에서 수행하는 핵심 역할. -- **비즈니스 목표:** (예: 업로드 완료 후 10분 이내 READY 달성) - -### 1.2 요구 기술 스택 및 환경 변수 (Tech Stack & Configs) -- 언어/프레임워크, ORM/DB, 주요 외부 라이브러리 -- 필수 환경 변수: `...` - -### 1.3 경계 (Boundaries) -- **In-Scope:** 컴포넌트가 직접 책임지는 기능 나열. -- **Out-of-Scope:** 다른 컴포넌트에 위임하거나 이번 스펙에서 제외되는 기능 명시. - -### 1.4 상태 라이프사이클 기준 (필요 시) -- 정상 전이: 예) `PENDING -> UPLOADED -> PROCESSING -> READY` -- 예외 전이: 예) `FAILED` (`failed_stage` 기록) - ---- - -## 2. Contracts (Interface & Data) -> 외부 통신 규격과 저장소 접근을 **Delta 중심**으로 명세 - -### 2.1 API / Message Endpoint - -#### [HTTP API] - -- **Auth / Tenancy:** 예) JWT 필수, `user_id`로 테넌시 필터. - -| HTTP Method | Endpoint (URI) | Request | Success Response | Notes | -| --- | --- | --- | --- | --- | -| POST | `/api/v1/...` | 주요 필드/제약 | 201/202 응답 스키마 | 분기 조건 등 | - -- **스키마 제약 조건 (Pydantic 기준):** 주요 필드별 타입·범위·필수 여부를 나열한다. - -#### [Object Storage] *(해당 시)* - -- **StorageClient 인터페이스:** `generate_signed_url()`, `get_blob_metadata()`, `delete_object()` 등 메서드를 가진 추상 클래스를 정의한다. 구현체는 의존성 주입(DI)으로 교체 가능하다. - - `(ConcreteStorageClient)` — 운영 환경 구현체 - - `InMemoryStorageClient` — 로컬/단위 테스트 전용 구현체 (더미 URL 반환, 내부 dict로 적재·삭제 상태 관리) - -#### [Message Broker / 비동기 큐] *(해당 시)* - -- **BrokerClient 인터페이스:** `publish(message)` 메서드를 가진 추상 클래스를 정의한다. 구현체는 의존성 주입(DI)으로 교체 가능하다. - - `(ConcreteBrokerClient)` — 운영 환경 구현체 - - `InMemoryBrokerClient` — 로컬/단위 테스트 전용 구현체 (발행 메시지를 메모리 리스트에 누적) -- **Exchange / Topic, Routing Key, Queue:** (해당 브로커 구성 명시) -- **Message Contract:** `docs/system-design.md` MessageEnvelope와 동일한 필드를 사용한다. - -```json -{ - "message_type": "PREPROCESS_REQUEST", - "payload_version": "v1", - "trace_id": "UUID4", - "attempt": 1, - "video_id": "UUID4", - "issued_at": "ISO8601" -} -``` - -### 2.2 Data Access (Reads & Writes) -| Type | Store | Entity/Table | Key/Filter | Mutation/Action | Notes (트랜잭션/인덱스/보안) | -| --- | --- | --- | --- | --- | --- | -| Read | ... | ... | ... | SELECT | 테넌시/인덱스 | -| Write | ... | ... | ... | INSERT/UPDATE | 트랜잭션 경계 | - -### 2.3 SLA & Constraints -- Timeout, Signed URL TTL, 파일 크기/확장자 제한, 페이지 limit 등 -- 강제 방식까지 명시 (예: `content-length-range`, 완료 시 size 재검증) - -### 2.4 Error Contract & Messaging Semantics -| HTTP Status | Error Code | 발생 조건 | Retryable | -| --- | --- | --- | --- | -| 400 | INVALID_ARGUMENT | 유효하지 않은 입력값 | N | -| 401 | UNAUTHENTICATED | JWT 미제공 또는 서명/만료 검증 실패 | N | -| 403 | FORBIDDEN | 테넌시 위반 (타인 리소스 접근) | N | -| 404 | NOT_FOUND | 미존재 리소스 접근 | N | -| 409 | CONFLICT | 허용되지 않은 상태 전이 시도 | N | -| 429 | RATE_LIMITED | 초당 요청 한도 초과 | Y (Backoff) | -| 500 | INTERNAL_ERROR | DB/외부 API 오류 | Y | -- **에러 응답 바디:** `{"code": "ERROR_CODE", "message": "설명 문자열", "trace_id": "UUID4"}` -- 멱등 응답 구분이 필요한 경우 (예: 최초 202, 중복 200) 별도 명시. -- MQ Ack/Nack, 재시도/Backoff, Poison/DLQ 정책 필요 시 추가. - -### 2.5 스키마 (DDL) *(SOT 소유 컴포넌트 필수 / 읽기 전용 참조 컴포넌트는 "참조 스키마"로 명시)* - -> SOT를 직접 관리하는 경우: 마이그레이션 도구(예: Alembic)로 관리하는 테이블 DDL을 전체 컬럼 기준으로 기술. -> 읽기 전용으로 참조하는 경우: 원본 관리 컴포넌트를 명시하고 접근 필드만 기재. - -```sql -CREATE TABLE (table_name) ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL, - status TEXT NOT NULL DEFAULT '...' CHECK (status IN ('...')), - deleted BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); -CREATE INDEX idx_(table)_(key) ON (table_name)(...); -``` - ---- - -## 3. Core Design & Logic - -### 3.1 주요 흐름 (Sequence) -- 핵심 플로우를 단계별 서술 (필요하면 Local/External 등 분기 별도 기술). - -### 3.2 상태 전이 (State Machine) -> 본 컴포넌트가 직접 트리거하는 전이만 구현 대상이다. ★ 표시 행은 타 컴포넌트 주도 전이로, 참조 목적으로만 기재한다. - -| From | To | Actor | Trigger | Guard | Side Effects | -| --- | --- | --- | --- | --- | --- | - -### 3.3 멱등성 및 복구 (Resilience) -- 멱등 키/조건, 중복 메시지 처리 정책, 재시도/보정(Reconcile) 전략. - -### 3.4 Data Consistency & Orphan Prevention -- 트랜잭션 경계, 잠금/유니크 제약, 보정/클린업(예: lifecycle 정책). -- 삭제 책임 분리 여부 명시. - ---- - -## 4. Observability & Ops -- **Logging:** 필수 필드(`trace_id`, `user_id`, 도메인 id), 포맷. -- **Metrics:** 핵심 지표(예: `mq_publish_fail_count`, `reconciler_republish_count`, `cursor_decode_fail_count`, p95 latency 등). -- **Alerts:** 임계치 정의는 인프라팀에 위임. 주요 감시 대상(예: 큐 지연, 재발행 급증, 5xx 비율 등)만 명시. -- **Trace Propagation:** 수신 `trace_id` 전파 규칙. - ---- - -## 5. Acceptance Criteria (DoD) - -### 5.1 시나리오 검증 - -> 엔드포인트 또는 기능 단위로 섹션(`####`)을 나누고, 각 섹션 내에서 **정상** / **예외** 로 구분하여 `* [ ]` 체크박스로 기술한다. - -#### (엔드포인트 또는 기능명 — 예: POST /api/v1/resource) - -**정상** -* [ ] (정상 흐름 시나리오 — 분기가 있으면 분기별로 기술) - -**예외** -* [ ] JWT 미제공 → 401 -* [ ] (유효성 위반 케이스) → 400 -* [ ] 타인 리소스 접근 → 403 -* [ ] 미존재 리소스 → 404 - -#### (엔드포인트 2 — 필요한 만큼 반복) - -**정상** -* [ ] ... - -**예외** -* [ ] ... - -#### (Reconciler / Background Worker 등 해당 시) - -**정상** -* [ ] (재발행 트리거 조건 충족 건 → 재발행 + 상태 변경) -* [ ] (조건 미충족 건 → 재발행 없음) - -### 5.2 검증을 위한 테스팅 전략 (Testing Strategy) - -에이전트는 아래 가이드라인을 만족하는 자동화 테스트를 작성해야 한다. -* 테스트 프레임워크는 `pytest`, `pytest-asyncio`, `httpx`를 사용한다. -* **커버리지 목표:** 단위·통합 테스트 합산 (목표 %) 이상을 달성한다 (`pytest-cov` 기준). -* DB 통합 테스트는 PostgreSQL 기반의 격리 환경(Testcontainers 또는 Docker Compose)을 사용한다. -* **외부 의존성 격리 전략:** - * Object Storage → `InMemoryStorageClient` (Test Double): 더미 URL 반환, 내부 dict로 적재·삭제 상태 관리. - * Message Broker → `InMemoryBrokerClient` (Test Double): 실제 MQ 없이 동작, 발행 메시지를 메모리 리스트에 누적. - * JWT 인증 → 테스트 전용 시크릿으로 실제 토큰을 생성하여 사용한다 (외부 인증 서버 호출 없음). - * 기타 외부 HTTP API(해당 시) → `AsyncMock`으로 대체하여 외부 호출 없이 동작한다. -* (해당 시) 시간 의존 로직(Reconciler, TTL 등)은 시간 고정(freezegun 등)과 상태별 fixture를 활용하여 재발행 조건을 검증한다. -* (해당 시) Opaque Cursor 계약은 encode/decode round-trip 테스트와 잘못된 토큰에 대한 예외 케이스를 포함한다. - -### 5.3 산출물 (Artifacts) - -폴더 구조는 `docs/Tech_Spec/folder_structure.md`를 참조한다. - -* [ ] HTTP 라우터 / 메시지 컨슈머 — (핵심 엔드포인트/큐 목록) -* [ ] Pydantic DTO — 요청/응답 스키마 -* [ ] 비즈니스 서비스 — (주요 유스케이스: 상태 전이, 멱등성 등) -* [ ] ORM 모델 — (엔티티 목록) -* [ ] StorageClient / BrokerClient 인터페이스 및 구현체 (운영 + InMemory) — 해당 시 -* [ ] (기타 인프라 어댑터 — 외부 API 클라이언트 등) -* [ ] 단위 테스트 / 통합 테스트 diff --git a/docs/git-naming-convention.md b/docs/git-naming-convention.md new file mode 100644 index 0000000..18d3576 --- /dev/null +++ b/docs/git-naming-convention.md @@ -0,0 +1,190 @@ +# Git Naming Convention + +`Biblio` 프로젝트에서 사용하는 브랜치, 커밋, PR 네이밍 규칙을 정의한다. + +## 목적 + +- 작업 단위를 GitHub Issue 기준으로 일관되게 추적한다. +- 브랜치, 커밋, PR 제목만 보고 변경 목적을 빠르게 파악한다. +- Git 로그와 PR 목록을 사람이 읽기 쉽게 유지한다. + +## 기본 원칙 + +- 작업 식별자는 `GitHub Issue 번호`를 사용한다. +- 브랜치와 PR 제목에는 Issue 번호를 반드시 포함한다. +- 커밋 메시지는 `Conventional Commits` 형식을 사용한다. +- 커밋 메시지의 Issue 번호 포함은 선택 사항으로 한다. +- 기본 머지 방식은 `Squash Merge`를 권장한다. + +## Type 규칙 + +다음 타입만 사용한다. + +| Type | 의미 | 사용 예시 | +| --- | --- | --- | +| `feat` | 사용자 관점의 새로운 기능 추가 | API 추가, 검색 기능 추가 | +| `fix` | 기존 동작의 버그 수정 | 예외 처리 보완, 상태 전이 수정 | +| `refactor` | 동작 변경 없는 내부 구조 개선 | 함수 분리, 책임 재구성 | +| `docs` | 문서만 수정 | README, ADR, Spec, Runbook 수정 | +| `test` | 테스트 코드만 추가 또는 수정 | unit/integration test 보강 | +| `chore` | 설정, 의존성, CI, 스크립트 등 잡무성 변경 | lint 설정, dependency update | +| `hotfix` | 운영 이슈 대응을 위한 긴급 수정 | 장애 대응 패치 | + +## 브랜치 네이밍 + +형식: + +```text +/- +``` + +규칙: + +- `type`은 위 표의 값 중 하나를 사용한다. +- `issue-number`는 GitHub Issue 번호만 넣는다. +- `short-slug`는 영어 소문자와 하이픈(`-`)만 사용한다. +- `short-slug`는 구현 상세보다 작업 목적을 드러내도록 작성한다. + +예시: + +```text +feat/123-video-upload +fix/241-search-timeout +refactor/198-preprocess-orchestrator +docs/310-git-naming-convention +test/322-upload-complete-idempotency +chore/415-pre-commit-hooks +hotfix/501-duplicate-callback-guard +``` + +## 커밋 메시지 네이밍 + +형식: + +```text +(): +``` + +권장 형식: + +```text +(): (#) +``` + +규칙: + +- `type`은 위 표의 값 중 하나를 사용한다. +- `scope`는 변경이 일어난 서비스 또는 모듈을 나타낸다. +- `summary`는 명령형 현재 시제로 간결하게 작성한다. +- 첫 글자는 소문자로 시작한다. +- 마침표는 붙이지 않는다. +- 하나의 커밋에는 하나의 의도만 담는다. + +추천 scope 예시: + +- `core-api` +- `search-service` +- `pipeline-worker` +- `managed-embedding-endpoint` +- `repo` +- `docs` + +예시: + +```text +feat(core-api): add upload completion endpoint (#123) +fix(search-service): handle empty retrieval result (#241) +refactor(pipeline-worker): split preprocess orchestrator +docs(repo): add git naming convention +test(core-api): add upload completion idempotency cases +chore(repo): add commitlint config +hotfix(core-api): reject duplicate callback request (#501) +``` + +## PR 제목 네이밍 + +형식: + +```text +(): [#] +``` + +규칙: + +- 브랜치와 동일한 작업 목적을 유지한다. +- PR 제목만 읽어도 변경 목적이 드러나야 한다. +- PR 제목에는 반드시 GitHub Issue 번호를 포함한다. + +예시: + +```text +feat(core-api): add video upload initiation API [#123] +fix(search-service): prevent timeout on empty query [#241] +refactor(pipeline-worker): split preprocess workflow [#198] +docs(repo): define git naming convention [#310] +``` + +## PR 본문 규칙 + +PR 본문에는 아래 중 하나를 반드시 포함한다. + +- `Closes #123` +- `Fixes #123` +- `Refs #123` + +권장 예시: + +```markdown +## Summary +- add upload completion endpoint +- validate object existence before status transition + +## Issue +Closes #123 +``` + +## 예외 규칙 + +- 아주 작은 저장소 관리 작업은 `no-ticket` 브랜치를 허용할 수 있다. +- `no-ticket`은 문서 오탈자 수정, 로컬 개발 편의 스크립트 정리 등 추적 가치가 낮은 작업에만 사용한다. +- `no-ticket` 사용이 잦아지면 Issue를 먼저 생성하는 것을 기본 원칙으로 되돌린다. + +예시: + +```text +docs/no-ticket-readme-typo +chore/no-ticket-local-dev-alias +``` + +## 운영 권장사항 + +- 하나의 PR은 가능한 한 하나의 Issue만 다룬다. +- 큰 작업은 먼저 Issue로 쪼개고, 브랜치도 그 단위에 맞춰 나눈다. +- `chore`는 남용하지 않는다. 기능이면 `feat`, 버그면 `fix`, 구조 개선이면 `refactor`를 우선 사용한다. +- 운영 장애 대응이 아니면 `hotfix` 대신 일반 `fix`를 사용한다. + +## 빠른 예시 + +Issue: + +```text +#123 Add video upload initiation API +``` + +브랜치: + +```text +feat/123-video-upload-initiation +``` + +커밋: + +```text +feat(core-api): add upload initiation endpoint (#123) +``` + +PR: + +```text +feat(core-api): add video upload initiation API [#123] +``` diff --git a/docs/system-design.md b/docs/system-design.md index 10b0d0c..2e266d9 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -33,27 +33,40 @@ #### Core API Server +**User 기능** + 1. 사용자 요청 처리: 영상 업로드, 처리 상태 조회, 영상 목록 관리 등 클라이언트 요청을 처리한다. 2. presigned URL 발급: 권한 확인 후 업로드 가능한 URL과 영상 고유 id 를 발급 3. 메타데이터 및 초기 상태 저장: 영상에 대한 메타데이터(업로더, 영상이름,카테고리)와 초기 상태를 db에 저장 4. 업로드 완료 확인/처리 트리거: 클라이언트로 부터 업로드가 끝났다는 신호를 받아 파이프라인 작업을 큐에 넣고, 상태를 업데이트 5. 인증 및 인가(AuthN/AuthZ): 전달받은 Access Token(JWT)의 서명과 만료를 내부 미들웨어에서 직접 검증한다. 이후 claim에서 추출한 requester_user_id를 기준으로 영상 리소스(video_id)의 소유권을 확인하여, 본인이 업로드한 영상과 데이터에만 접근하도록 제어한다. -6. 피드백 수집(Feedback Ingestion): 검색 결과에 대한 좋아요/싫어요를 영상 단위가 아닌 검색 응답 단위(`req_id`)로 수집하며, 클라이언트는 Search Service 응답의 `chunks`에서 `topk_ids`, `used_ids`를 파생해 해당 시점의 `query_text`와 함께 저장한다. +6. 피드백 수집(Feedback Ingestion): 검색 결과에 대한 좋아요/싫어요를 영상 단위가 아닌 검색 응답 단위로 수집한다. Core API Server는 req_id에 대응하는 검색 응답 스냅샷을 Metadata DB에서 조회하여 사용자 권한, 시간 창, 요청 유효성을 검증한 뒤, 검증된 피드백 이벤트를 Feedback Ingestion Pipeline으로 전달한다. 7. 비동기 작업 요청: 영상 처리처럼 시간이 오래 걸리는 작업은 직접 처리하지 않고 메시지 브로커로 전달하여, 즉시 “요청 접수(Accepted)” 응답을 반환한다. 8. 영상 삭제 요청 처리: 사용자의 삭제 요청을 수신하여 Video.status를 DELETING으로 전이하고 DELETE_REQUEST를 메시지 브로커에 발행한다. 실제 연쇄 삭제는 Pipeline Worker에 위임한다. 9. 파이프라인 실패 재처리: 사용자의 재시도 요청을 수신하여 Video.status를 PENDING으로 초기화하고 PREPROCESS_REQUEST를 재발행하여 Worker가 실패 지점부터 재개할 수 있도록 한다. +**Admin 기능** + +Admin 기능은 JWT claim의 role을 기준으로 운영자 권한을 별도 검증하며, 소유권(user_id) 기반 제한 없이 모든 리소스에 접근한다. + +10. 전체 파이프라인 상태 조회: 소유권 제한 없이 임의 video_id의 처리 현황 및 실패 상세를 조회한다. +11. 강제 재처리: 임의 video_id에 대해 재처리가 가능하도록 처리 상태를 조정하고, 후속 처리 파이프라인을 다시 시작한다. +12. 강제 삭제: 임의 video_id에 대해 삭제 절차를 시작하고, 연쇄 정리 작업을 수행하도록 요청한다. +13. ML 파이프라인 상태 조회 및 재트리거: MLPipelineRun의 진행 상태 및 실패 현황을 조회하고, 장애 시 수동 재트리거 또는 롤백 액션을 요청한다. + #### Search Service -1. 검색 전담 처리: 자연어 질의를 수신하며, 요청자 소유 검색 범위의 영상이 1개 이상 존재하고 그 전체가 `READY`일 때만 하이브리드 검색 파이프라인(임베딩 변환 및 DB/Vector 조회)을 시작한다. 영상이 0개면 `409 NO_VIDEOS_UPLOADED`, 1개 이상이지만 하나라도 미준비 상태면 `409 SEARCH_NOT_READY`를 반환한다. +1. 검색 전담 처리: 자연어 질의를 수신하며, 요청자 소유 영상이 1개 이상 존재하고 그중 현재 검색 가능한 영상이 1개 이상 있을 때 하이브리드 검색 파이프라인(임베딩 변환 및 DB/Vector 조회)을 시작한다. 영상이 0개면 `409 NO_VIDEOS_UPLOADED`, 검색 가능한 영상이 1개도 없으면 `409 SEARCH_NOT_READY`를 반환한다. 2. 하이브리드 검색 오케스트레이션 (Search Orchestrator): - - 내부 추상화 인터페이스를 통해 **Managed Embedding Endpoint를 직접 호출**하여 질의 텍스트를 벡터로 변환한다. - - 키워드 후보는 Metadata DB(FTS)에서, 벡터 후보는 Vector Store(ANN)에서 각각 생성한다. - - 두 후보를 RRF(Reciprocal Rank Fusion) 등으로 병합하여 최종 Top-K를 만든다. - - 최종 응답 전에 Metadata DB(SOT)에서 최종 서빙 검증을 수행하여 (권한/존재 여부(삭제=hard delete)/READY 상태) 기준을 통과한 Chunk만 반환한다. -3. LLM 컨텍스트 주입: 추출된 청크들을 조립한 프롬프트를 `LLM_PROVIDER`가 선택한 Search Service 내부 LLM 인터페이스 구현체(기본값: `GeminiLLMAdapter`)에 전달하여 최종 답변을 생성하며, 피드백 귀속용 `req_id`, 타임스탬프 레퍼런스 `ref`, 실제 인용 여부 `used`가 포함된 `chunks` 배열을 함께 반환한다. + - 내부 추상화 인터페이스를 통해 **Managed Embedding Endpoint를 직접 호출**하여 질의 텍스트를 active 모델 기준으로 임베딩한다. + - previous 세대가 병행 서빙 중인 동안에는 동일 질의를 previous 모델 기준으로도 추가 임베딩한다. + - 키워드 후보는 Metadata DB(FTS)에서, 벡터 후보는 Vector Store의 active/previous 인덱스에서 각각 생성한다. + - 키워드 후보와 active/previous 벡터 후보를 RRF(Reciprocal Rank Fusion) 등으로 병합하여 최종 Top-K를 만든다. + - 최종 응답 전에 Metadata DB(SOT)에서 최종 서빙 검증을 수행하여 권한, 존재 여부, 검색 가능 상태를 통과한 Chunk만 반환한다. +3. LLM 컨텍스트 주입: 추출된 청크들을 조립한 프롬프트를 Search Service 내부 LLM 연동 구현체에 전달하여 최종 답변을 생성하며, 피드백 귀속용 `req_id`, 타임스탬프 레퍼런스 `ref`, 실제 인용 여부 `used`가 포함된 `chunks` 배열을 함께 반환한다. 복구 중인 영상이 검색 범위에서 제외된 경우에는 그 사실을 사용자에게 함께 고지한다. 4. 인증 및 테넌시 강제(AuthN/AuthZ): 전달받은 Access Token(JWT)을 직접 검증하여 requester_user_id를 추출한다. 이를 기준으로 검색 대상 범위를 제한하고, Metadata DB(FTS) 및 Vector Store(ANN) 조회 단계 모두에서 사용자 테넌트 필터를 반드시 강제한다. +5. 검색 응답 스냅샷 저장: 검색 응답 반환 시, 피드백 검증과 운영 추적에 필요한 응답 스냅샷(질의, 최종 청크, active 모델 정보, 실제 조회한 벡터 검색 경로 정보, 검색 범위 축소 여부)을 Metadata DB에 단기 보존(TTL)한다. --- @@ -66,25 +79,37 @@ 1. 작업 대기열 관리: API 서버로부터 전달받은 '영상 처리 요청'을 큐(Queue)에 적재하여, Worker가 처리 가능한 시점에 작업을 가져가도록 한다. 2. 워크로드 격리: 모델 학습(Training) 큐를 별도로 운영하여, 학습 부하가 업로드/검색 처리에 영향을 주지 않도록 분리한다. -3. 메시지 계약(Message Contract): 파이프라인 메시지는 공통 Envelope + 메시지별 Payload로 구성하며, 최소 필드/스키마는 Data Model의 “Message Contract(3.7~)” 정의를 따른다. +3. 메시지 계약(Message Contract): 파이프라인 메시지는 공통 Envelope + 메시지별 Payload로 구성하며, 최소 필드/스키마는 Data Model의 “Message Contract(3.10~)” 정의를 따른다. + +#### Feedback Ingestion Pipeline + +1. 피드백 이벤트 수집: Core API Server가 전달한 검증된 피드백 이벤트를 수신한다. +2. 로그 적재: 피드백 이벤트를 수정 없이 누적 저장하는 원본 로그 형태로 Object Storage에 적재한다. +3. 내결함성: 일시적 전송 장애 시 버퍼링 및 재전송을 통해 이벤트 손실을 최소화한다. #### Media & AI Pipeline Worker (통합 워커) 1. 단일 파이프라인 실행: 큐에서 PROCESS_REQUEST 수신 시, 하나의 프로세스 내에서 `다운로드 → 추출 → STT → 청킹 → 임베딩 → DB 적재`를 순차적으로 논스톱 처리하여 네트워크 I/O 지연을 극소화한다. 2. 멱등성 보장 다운로드 및 전처리: 외부 URL 인입 시 대상 파일이 스토리지에 존재하는지 확인(멱등성 체크) 후, 없으면 다운로드하여 상태를 UPLOADED로 갱신한다. 이후 로컬 환경에서 오디오와 키프레임을 추출한다. -3. 데이터 지역성 기반 AI 서비스 직접 연동: 추출된 오디오 데이터를 스토리지 다운로드 대기 없이 로컬 환경에서 추상화된 AI 클라이언트 인터페이스를 통해 **External AI Adapters(STT API)**로 직접 전송하여 텍스트 스크립트를 반환받는다. (추출된 원본 오디오 및 키프레임은 장애 복구 및 서비스 서빙을 위해 Object Storage에 비동기 백업 적재한다.) +3. 데이터 지역성 기반 AI 서비스 직접 연동: 추출된 오디오 데이터를 스토리지 다운로드 대기 없이 로컬 환경에서 Pipeline Worker 내부 STT 연동 구현을 통해 외부 음성 인식 서비스로 직접 전송하여 텍스트 스크립트를 반환받는다. (추출된 원본 오디오 및 키프레임은 장애 복구 및 서비스 서빙을 위해 Object Storage에 비동기 백업 적재한다.) 4. 시맨틱 청킹 및 벡터화: 변환된 스크립트를 문맥 단위로 분할(Chunking)하고 타임스탬프에 맞는 키프레임을 매핑한다. 청킹 및 Vision enrichment 단계는 추출 직후 확보한 로컬 키프레임 메타데이터를 즉시 사용하며, Object Storage 백업 및 Asset 레코드 기록은 병렬로 진행하되 최종 Chunk 적재 전에는 완료되어 `keyframe_asset_id`를 확정해야 한다. 이후 텍스트 청크를 **Managed Embedding Endpoint**로 직접 전송하여 임베딩 벡터를 반환받는다. -5. 검색 색인 구축 및 완료: 전체 대본/청크 메타데이터는 Metadata DB(SOT)에 트랜잭션 적재하고, 임베딩 벡터는 Vector Store(ANN)에 Upsert 한다. 반영 완료 시 Video.status를 READY로 갱신한다. +5. 검색 색인 구축 및 완료: 전체 대본/청크 메타데이터는 Metadata DB(SOT)에 트랜잭션 적재하고, 임베딩 벡터는 Vector Store(ANN)에 Upsert 한다. 반영 완료 시 Video.status를 READY로 갱신한다. 실제 검색 노출 여부는 검색 노출 상태를 따른다. 6. 부분 실패 및 장애 복구: 작업 실패 시 기존 산출물(스토리지 백업본)을 보존하며 DB에 failed_stage를 기록한다. 재처리 요청 시 완료된 무거운 작업(영상 다운로드 등)은 건너뛰고(Skip), failed_stage와 보존 산출물을 함께 참조하여 안전한 재개 지점을 판단할 수 있도록(Resume) 복구력을 보장한다. 7. 영상 삭제 연쇄 처리: DELETE_REQUEST 수신 시, 또는 파이프라인 처리 중 각 단계 진입 전 Video.status=DELETING을 감지한 경우 현재 단계에서 중단하고 연쇄 삭제를 수행한다. Metadata DB의 관련 레코드(VectorIndexEntry, Chunk, TranscriptSegment, Asset, Video)를 트랜잭션으로 삭제하고, Object Storage 파일(원본 영상, 오디오, 키프레임)은 메인 서비스와 분리하여 비동기로 정리한다. -#### Model Training Worker +#### ML Lifecycle Worker + +1. 데이터셋 전처리: 정기 배치 스케줄의 실행 책임은 ML Lifecycle Worker가 가진다. Worker는 정기 배치에 따라 신규 피드백을 학습 가능한 형태의 데이터셋으로 변환하여 Object Storage에 저장한다. 배치 실행은 중복 수행하지 않으며, 전처리 완료 후 학습 파이프라인을 자동 트리거한다. +2. 모델 학습: 최신 데이터셋을 입력으로 임베딩 모델 개선 학습을 자동 수행하고 후보 모델을 Model Artifact Files에 저장한다. +3. 모델 평가 및 결과 저장: 후보 모델과 기준 모델의 검색 성능을 별도 평가용 데이터셋으로 자동 비교 평가한다. 집계 결과는 Metadata DB에, 질의별 상세 결과는 아티팩트로 저장한다. 평가 결과는 품질 미달(FAIL)과 시스템 오류(ERROR)를 구분하여 기록한다. +4. 재색인 및 점진 재임베딩: 평가 PASS 시 후보 모델 전용 인덱스를 구축한다. 전체 코퍼스의 즉시 재색인을 배포 선행 조건으로 두지 않으며, 신규 유입 데이터부터 우선 반영한다. +5. 서빙 전환 및 세대 관리: 후보 모델 readiness와 우선 반영 대상의 인덱싱 완료가 확인되면 서빙을 전환한다. 서빙 전환 직전에는 마지막 정상 서빙 상태를 롤백 복구용 스냅샷으로 저장한다. 전환 후 온라인 검색은 active와 previous의 최대 2세대만 병행 지원한다. +6. 이전 세대 정리: previous보다 더 오래된 세대의 데이터는 중간 버전을 거치지 않고 최신 active 모델로 점진 재임베딩한다. +7. 롤백 복구: 운영자가 새 모델을 문제 모델로 판정하면, 마지막 정상 서빙 상태를 가리키는 rollback snapshot을 기준으로 rollback 복구를 시작한다. +8. 복구 후 후처리: rollback 이후 문제 모델 기간에 생성된 기존 데이터가 포함된 영상은 복구가 끝날 때까지 검색 범위에서 제외한다. 해당 데이터는 복원된 정상 서빙 상태 기준으로 백그라운드 재임베딩하며, 복구가 끝난 영상부터 다시 검색 범위에 합류시킨다. 이 재임베딩은 rollback 완료와 분리된 후속 복구 작업이다. +9. 실행 제어 및 실패 처리: 동시에 활성 상태인 MLPipelineRun은 하나만 유지한다. 실행 중 새 데이터셋이 준비되면 FIFO로 모두 쌓지 않고, 최신 데이터셋 기준의 다음 실행만 남긴다. 각 단계의 진행 상태와 실패 정보는 MLPipelineRun에 계속 기록하며, 신규 데이터셋으로 대체된 실행은 SUPERSEDED로 표시한다. +10. 내부 책임 분리 원칙: ML Lifecycle Worker는 단일 배포 단위로 유지하되, 내부 구현은 단계별 책임이 섞이지 않도록 1모듈 1책임 원칙으로 분리한다. 실행 제어, 학습/평가, 재색인, 점진 재임베딩, 서빙 전환, 롤백 복구 책임은 서로 독립적으로 변경·재실행 가능해야 하며, 구체적인 모듈 구조와 상호작용은 후속 Spec에서 정의한다. -1. 데이터셋 전처리: 사용자 피드백 및 관리자가 확정한 학습용 데이터셋을 로드하여 학습 가능한 포맷으로 변환한다. -2. 모델 성능 개선: 피드백 기반 데이터셋을 활용해 임베딩 모델을 개선하고, 필요 시 기존 벡터 데이터를 재색인한다. -3. 모델 파인 튜닝: 구축된 특정 도메인의 데이터셋을 활용해 범용 임베딩 모델을 특정 도메인(법률, 의료 등) 데이터에 맞춰 재학습(Fine-tuning)한다. -4. 자동 평가(Auto-Evaluation): 후보 모델과 현재 운영 모델의 성능을 비교 평가하고, 개선이 검증된 경우에만 배포 가능한 상태로 등록한다. -5. 메시지 소비: TRAINING_REQUEST를 소비하여 학습/평가/배포 파이프라인을 수행한다. (상세 스키마는 Data Model 참조) --- ### AI 추론 서브시스템 (AI Inference Subsystem) @@ -94,13 +119,8 @@ #### Managed Embedding Endpoint (자체 호스팅 모델) 1. 임베딩 추론 전담: Worker 및 Search Service로부터 API 요청을 직접 수신하여 텍스트를 벡터로 변환하는 연산에만 집중한다. -2. 모델 관리 및 서빙 기준: 배포 시점에 지정된 모델 파일을 프로세스 시작 시 로드하여 서빙한다. 모델 교체는 최신 버전 자동 감지가 아니라 파일 교체 후 새 프로세스 기동 방식으로 반영하며, 초기 단계에서는 운영자 개입을 전제로 한다. -3. 서빙 준비성(readiness): 모델 파일 로드가 성공해야만 요청을 받는다. 실제 추론 가능 여부 검증은 서비스 내부 startup smoke inference가 아니라 배포/운영 절차의 별도 smoke test로 확인한다. 현재 서빙 중인 모델 버전의 기준값은 실제 로드한 artifact path이다. - -#### External AI Adapters (외부 API) - -1. STT 연동: Google Cloud Speech-to-Text 등 외부 상용 음성 API와의 통신 및 에러 핸들링(Retry, 서킷 브레이커 등)을 담당하는 **추상화된 연결 모듈(Adapter)**이다. Pipeline Worker가 구체적인 외부 API 라이브러리나 통신 규격에 직접 결합되지 않도록 인터페이스를 분리하여 설계한다. - +2. 모델 관리 및 서빙 기준: 런타임 서빙 모델은 Metadata DB의 릴리스 레코드를 SOT로 결정한다. 최초 기동 시 릴리스 레코드가 없으면 배포 설정(환경변수)의 기본값으로 폴백하고, 기동 후 릴리스 레코드를 초기화한다. Endpoint는 active 모델, previous 모델, 재색인/검증용 후보 모델을 동시에 유지할 수 있으며, 어떤 모델을 사용할지는 요청 목적과 릴리스 레코드에 따라 결정한다. +3. 서빙 준비성(readiness): active 모델 아티팩트 로드가 성공해야만 요청을 받는다. previous 모델은 병행 검색 기간에만 노출되며, 후보 모델은 재색인/검증 트래픽에만 노출된다. 후보 모델이 실제로 로드되고 readiness가 통과한 뒤에만 ModelRelease를 갱신한다. --- @@ -111,47 +131,63 @@ #### Object Storage 1. 대용량 파일 저장: 원본 영상, 추출된 오디오, 키프레임 이미지 등 대용량 파일을 저장한다. +2. 운영 산출물 저장: 평가 상세 아티팩트 같은 운영 산출물을 저장한다. +3. 원본 이벤트 로그 저장: 검색 응답 단위 피드백 이벤트를 수정 없이 누적 저장하는 원본 로그 형태로 저장한다. +4. 평가용 데이터셋 저장: 모델 평가에 사용하는 입력/정답 기준 데이터셋을 별도 버전 산출물로 저장한다. 사용자 업로드 자산과 ML 운영 산출물은 논리적으로 분리하여 관리한다 +5. 롤백용 인덱스 스냅샷 저장 #### Metadata DB (Source of Truth; RDB) -1. 정합성 보장(SOT): 사용자, 영상 메타데이터, 상태(Status), Transcript/Chunk(텍스트/타임스탬프/참조), 피드백을 ACID 트랜잭션으로 저장한다. +1. 정합성 보장(SOT): 사용자, 영상 메타데이터, 상태(Status), Transcript/Chunk(텍스트/타임스탬프/참조)를 ACID 트랜잭션으로 저장한다. 2. 키워드 검색(FTS): Chunk의 enriched_text(없을 경우 text)에 대한 FTS 인덱스를 운영하여 키워드 후보를 생성한다. (초기 구성: RDB 내 FTS) -3. 최종 서빙 검증(SOT Validation): 검색 결과로 반환되기 전, 권한/존재 여부(삭제=hard delete)/상태(READY) 기준으로 노출 가능한 Chunk만 최종 확정하는 기준 저장소로 동작한다. +3. 최종 서빙 검증(SOT Validation): 검색 결과로 반환되기 전, 권한, 존재 여부, 검색 가능 상태 기준으로 노출 가능한 Chunk만 최종 확정하는 기준 저장소로 동작한다. +4. 운영 메타데이터 저장: 피드백 기반 모델 평가 결과와 운영 상태 추적에 필요한 메타데이터를 저장한다. +5. 모델 릴리스 상태 관리(SOT): 현재 서빙 중인 active/previous 조합, 후보 모델 버전, 롤백 대상을 릴리스 레코드로 관리한다. 모델 전환과 롤백의 기준은 이 레코드 갱신을 따른다. +6. 검색 응답 스냅샷 단기 보존: 피드백 검증용 검색 응답 스냅샷을 TTL 기반으로 단기 보존하며, Core API Server의 피드백 유효성 검증 시 조회 기준으로 사용된다. #### Vector Store (ANN Index; Derived Projection) 1. 벡터 저장(파생 인덱스): Chunk 단위 임베딩 벡터를 저장하며, 검색 시 테넌시 필터를 적용할 수 있도록 최소 메타데이터(user_id, video_id 등)를 함께 보유한다. -2. 최종 일관성: Vector Store는 Metadata DB로부터 파생된 Projection이며, 부분 실패/지연이 발생할 수 있다. 사용자 노출 정합성은 Metadata DB의 상태(READY) 및 최종 서빙 검증(SOT Validation)으로 보장한다. +2. 최종 일관성: Vector Store는 Metadata DB로부터 파생된 Projection이며, 부분 실패/지연이 발생할 수 있다. 사용자 노출 정합성은 Metadata DB의 검색 가능 상태와 최종 서빙 검증(SOT Validation)으로 보장한다. #### Model Artifact Files 1. 모델 버전 관리: 임베딩 모델 파일과 버전 메타데이터는 파일 단위의 배포 아티팩트로 관리한다. -2. 버전 식별 기준: 서빙 중 모델 버전의 SOT는 실제 로드한 artifact path이며, 경로명은 고정 naming convention에 따라 version string을 포함해야 한다. -3. 서빙 반영 방식: Managed Embedding Endpoint는 프로세스 시작 시 지정된 로컬 경로의 모델 파일을 로드한다. 모델 승격은 운영자가 선택한 아티팩트를 배포하고 새 프로세스를 기동하는 절차로 반영한다. +2. 버전 식별 기준: 서빙 중 모델 버전의 SOT는 Metadata DB의 릴리스 레코드이다. artifact path는 최초 기동 시 릴리스 레코드가 없을 때의 부트스트랩 기본값으로 사용된다. +3. 서빙 반영 방식: 후보 모델 readiness와 우선 반영 대상의 인덱싱 완료가 확인되면 ML Lifecycle Worker가 Managed Embedding Endpoint와 ModelRelease를 갱신하여 후보 모델을 서빙에 반영한다. 롤백도 동일한 원칙을 따르며, 롤백 대상 모델이 Managed Embedding Endpoint에 실제로 로드되고 readiness를 통과한 뒤에만 ModelRelease를 이전 상태로 복원한다. +4. 후보 산출물 보관: 학습으로 생성된 후보 모델 파일과 관련 버전 정보를 보관한다. + +#### Index Snapshot Files + +1. 롤백 스냅샷 보관: 롤백 복구에 필요한 vector index 데이터는 `index_name` 단위 파일 아티팩트로 Object Storage에 저장한다. +2. 스냅샷 포인터 기준: `ModelRelease`의 rollback snapshot active 인덱스 식별자는 복원 대상 인덱스 스냅샷을 가리키는 메타데이터다. +3. 복원 순서: rollback 시 snapshot이 가리키는 active 모델 아티팩트와 active 인덱스 스냅샷이 준비된 뒤 `ModelRelease`를 active 기준으로 복원한다. + --- ### 관리 및 운영 (Admin & Ops) -**목적:** 운영자가 파이프라인/모델 품질을 관리하고 장애 대응 및 배포 자동화를 수행 +**목적:** 운영자가 파이프라인/모델 품질을 관리하고 장애 대응 및 모델 운영 절차를 수행 #### Admin Dashboard -* 모니터링: 파이프라인 단계별 성공/실패 여부, 소요 시간, 대기열 적체 등 운영 지표를 시각화하여 실시간에 가깝게 상태를 제공한다. -* 데이터셋 구축 도구: 수집된 사용자 피드백 데이터를 검수하고, 학습용 데이터셋으로 확정(Commit)하는 UI를 제공한다. -* 학습 파이프라인 제어: 학습 진행 상황 및 평가 리포트를 시각화하여 제공한다. +* 운영 모니터링: 파이프라인 상태, 실패 현황, 모델 운영 상태를 조회할 수 있는 관리 인터페이스를 제공한다. +* 운영 액션: 재처리, 데이터 삭제 등 운영 액션을 수행한다. +* 모델 운영 지원: MLPipelineRun 진행 상태(피드백 루프) 및 실패 현황을 조회한다. 현재 실행 중인 모델 개선 파이프라인과 최신 데이터셋 기준의 다음 대기 실행이 있는지도 확인할 수 있다. 실패는 대시보드에 표시하고, 조치가 필요한 상태도 함께 확인할 수 있다. 품질 미달(FAIL)과 시스템 오류(ERROR)는 구분하여 표시한다. 장애 시 수동 재트리거 및 롤백 액션을 제공한다. #### Observability (Logging / Metrics) -* 운영 데이터 수집: 파이프라인 로그, 처리 시간, 실패 원인, 큐 적체량 등 관측 데이터를 수집/저장하여 Admin Dashboard에서 조회 가능하도록 한다. -* 장애 분석 지원: 특정 단계 실패 시 원인 파악과 재처리 판단에 필요한 근거 데이터를 제공한다. +* 운영 데이터 수집: 파이프라인 상태, 검색 응답 시간/실패율, 큐 적체량, 피드백 수집 현황 등 운영 판단에 필요한 로그와 지표를 수집/저장하여 Admin Dashboard와 운영 절차에서 활용할 수 있도록 한다. +* 장애 분석 지원: 특정 단계 실패, 검색 오류, 재처리 필요 여부를 판단할 수 있도록 trace_id 기반 로그와 핵심 지표를 제공한다. -#### Pipeline Controller +#### 주요 연결 관계 -* 오케스트레이션 및 정합성 관리: 파이프라인 상태를 기준으로 단계 실행 순서, 재시도를 관리한다. 영상 삭제 시 연쇄 삭제(Cascade Delete) 워크플로우는 Pipeline Worker가 담당한다. -* 모델 교체 및 재색인 트리거: 임베딩/STT 모델 버전 변경 시, 기존 데이터를 새로운 모델로 다시 벡터화(재색인)하는 작업을 트리거한다. -* 배포/롤백 제어: 평가를 통과한 모델을 배포하고, 문제 발생 시 이전 버전으로 즉시 롤백할 수 있도록 제어 로직을 제공한다. +* Admin Dashboard → Core API Server (Admin 기능): 동기 HTTP 호출로 파이프라인 상태 조회, 실패 상세 조회, 재처리/강제 삭제를 요청한다. +* ML Lifecycle Worker (자동 연쇄): 전처리 완료 후 학습→평가→재색인→서빙 전환을 자동으로 연쇄 수행한다. Admin Dashboard에서 장애 시 수동 재트리거 가능. +* ML Lifecycle Worker → Model Artifact Files → Managed Embedding Endpoint: 후보 모델 readiness와 우선 반영 대상의 인덱싱 완료가 확인되면 ML Lifecycle Worker가 후보 모델을 서빙에 자동 반영한다. +* 각 백엔드 컴포넌트 → Observability: Core API Server, Search Service, Media & AI Pipeline Worker, ML Lifecycle Worker, Managed Embedding Endpoint가 로그와 메트릭을 push 방식으로 전송한다. --- @@ -163,7 +199,27 @@ 1. **정상 전이:** `PENDING` (요청 인입) → `UPLOADED` (영상 원본 확보) → `PROCESSING` (추출 및 AI 분석 중) → `READY` (검색 가능 상태) 2. **예외 전이:** 진행 중 어느 단계에서든 오류 발생 시 `FAILED`로 전이되며, DB에 `failed_stage`를 기록하여 재시도 시 실패 분류 및 재개 판단 근거로 활용한다. 3. **삭제 전이:** 임의 상태에서 사용자가 삭제 요청 시 `DELETING`으로 전이된다. 이 시점부터 해당 영상은 검색 범위에서 즉시 제외된다. Pipeline Worker가 연쇄 삭제를 완료한 후 레코드를 hard-delete한다. +4. **검색 노출 상태:** `READY`는 파이프라인 기본 처리 완료를 뜻한다. 실제 검색 노출 여부는 별도 검색 노출 상태를 따르며, rollback 복구 중인 영상은 일시적으로 검색 범위에서 제외될 수 있다. + +**모델 개선 사이클 (피드백 루프):** + +사용자 피드백을 기반으로 임베딩 모델을 개선하고 프로덕션에 반영하는 운영 사이클이다. 구체적인 데이터 I/O는 2.7~2.8을 따른다. +데이터 수집부터 모델 배포까지 자동으로 진행: 수집 → 전처리(자동 배치) → 학습 → 평가 → 판정 분기 → 재색인 → 서빙 전환 / 실행 제어: 한 번에 하나만 활성 실행 유지, 더 최신 데이터셋 실행이 있으면 기존 대기 실행 대체 / 실패 시 기존 서빙 유지 + Admin Dashboard에 실패 상태 표시 + +- 수집: 사용자 피드백이 Object Storage의 원본 이벤트 로그로 자동 누적된다. (2.6 참조) +- 전처리: ML Lifecycle Worker가 배치 스케줄에 따라 신규 피드백을 학습용 데이터셋으로 변환한다. 전처리 완료 후 학습 파이프라인을 자동 트리거한다. +- 학습: ML Lifecycle Worker가 자동으로 임베딩 모델 학습을 수행한다. +- 평가: 학습에 이어 후보 모델과 기준 모델의 검색 성능을 학습셋과 분리된 오프라인 평가셋으로 비교 평가한다. 평가셋은 immutable artifact로 버전 관리되며, 평가 결과가 기준을 충족하면 배포 후보로 판정한다. +- 판정 분기: 평가 결과가 기준에 맞으면 후보 모델 배포를 진행하고, 기준에 미치지 못하면 기존 서빙을 유지하며 Admin Dashboard에 실패 상태를 표시한다. +- 우선 전환: 후보 모델 전용 인덱스를 먼저 만들고, 신규 유입 데이터를 우선 반영한다. 이 기간에도 사용자 검색은 기존 서빙을 유지한다. +- 서빙 전환: 후보 모델 readiness와 우선 반영 대상의 인덱싱 완료가 확인되면 ML Lifecycle Worker가 마지막 정상 서빙 상태를 롤백 복구용 스냅샷으로 저장한 뒤 ModelRelease를 갱신하여 서빙을 전환한다. 병행 기간 동안 Search Service는 active와 previous 모델을 함께 사용한다. +- 점진 재임베딩: 전환 이후 남아 있는 더 오래된 세대 데이터는 최신 active 모델로 순차 재임베딩한다. 온라인 검색은 최대 2세대까지만 병행 지원한다. +- 운영자 롤백: 배포 후 새 모델이 문제 모델로 판정되면, 마지막 정상 서빙 상태를 가리키는 rollback snapshot을 기준으로 rollback 복구를 시작한다. +- 복구: rollback 대상 모델 readiness와 snapshot 인덱스 복원이 완료되면 마지막 정상 서빙 상태를 기준으로 `ModelRelease`를 복원하고, 전환 중 후보 조합 관련 메타데이터는 비운다. +- 검색 범위 제한: 문제 모델 기간에 생성된 기존 데이터가 포함된 영상은 복구가 끝날 때까지 검색 범위에서 제외한다. Search Service는 남아 있는 검색 가능 영상으로 검색을 계속 제공하고, 일부 영상이 복구 중임을 사용자에게 고지한다. +- 복구 후 후처리: 문제 모델 기간에 생성된 기존 데이터는 복원된 정상 서빙 상태 기준으로 재임베딩하며, 복구가 끝난 영상부터 다시 검색 범위에 합류시킨다. 이 작업은 rollback 완료와 분리된 후속 복구 작업으로 진행한다. +- 예외: 각 단계 실패 시 MLPipelineRun에 실패 단계와 유형을 기록하고 파이프라인을 종료한다. 기존 서빙은 유지된다. --- @@ -201,9 +257,9 @@ 1. Worker가 PREPROCESS_REQUEST를 수신하고 Metadata DB에서 Video 정보를 로드하여 상태 기반 멱등성 및 failed_stage를 체크한다. (완료된 무거운 작업 방지) 2. **[영상 확보]** External URL 인입이면서 스토리지에 파일이 없다면 영상을 다운로드하여 저장하고, Metadata DB의 상태를 UPLOADED로 갱신한다. 3. **[전처리]** 상태를 PROCESSING으로 변경 후 로컬 환경에서 영상을 로드하여 오디오와 키프레임을 추출한다. (네트워크 대기 없이 즉시 4번으로 넘어가며, 추출된 파일은 비동기로 Object Storage에 저장하고 DB에 경로를 남긴다.) -4. **[STT 변환]** 로컬의 오디오 데이터를 **External AI Adapters(외부 STT API)**로 직접 전송하여 텍스트 및 타임스탬프 스크립트를 반환받는다. +4. **[STT 변환]** 로컬의 오디오 데이터를 Worker 내부 STT 연동 구현을 통해 외부 음성 인식 서비스로 직접 전송하여 텍스트 및 타임스탬프 스크립트를 반환받는다. 5. **[청킹 및 임베딩]** Worker가 전체 스크립트를 문맥 단위로 청킹하고 키프레임을 매핑한다. 텍스트 청크를 **Managed Embedding Endpoint(자체 배포 모델)**로 직접 전송하여 임베딩 벡터를 반환받는다. -6. **[적재 및 완료]** 스크립트/청크(텍스트, 타임스탬프, 참조)는 Metadata DB(SOT)에 적재하고, 임베딩 벡터는 Vector Store(ANN)에 적재(Upsert)한다. 두 저장소 반영 완료 시 status=READY로 갱신한다. +6. **[적재 및 완료]** 스크립트/청크(텍스트, 타임스탬프, 참조)는 Metadata DB(SOT)에 적재하고, 임베딩 벡터는 Vector Store(ANN)에 적재(Upsert)한다. ModelRelease에 candidate 재색인 상태가 열려 있으면 online ingest는 active 인덱스와 candidate 인덱스에 각각 맞는 `model_version`으로 dual-write 한다. 두 저장소 반영 완료 시 status=READY로 갱신한다. **출력** - 이벤트: (내부 상태 전이로 인해 별도 완료 큐 발행 없음) @@ -225,15 +281,15 @@ **처리** 1. Reverse Proxy가 요청을 수신하여 Authorization 헤더를 포함한 원본 요청을 그대로 Search Service로 전달 2. Search Service가 내부 미들웨어를 통해 JWT를 직접 검증하고, claim에서 requester_user_id를 추출하여 테넌시 필터에 사용 -3. 질의 텍스트를 **Managed Embedding Endpoint**로 직접 보내 임베딩 벡터로 변환한다. +3. 질의 텍스트를 **Managed Embedding Endpoint**로 직접 보내 active 모델 기준 임베딩 벡터로 변환한다. previous 세대가 병행 서빙 중이면 동일 질의를 previous 모델 기준으로도 추가 임베딩한다. 4. Search Service가 먼저 요청자 소유 영상이 0개인지 확인하고, 0개면 `409 NO_VIDEOS_UPLOADED`를 반환한다. -5. Search Service가 요청자 소유 영상 중 `READY`가 아닌 항목이 있는지 확인하고, 있으면 `409 SEARCH_NOT_READY`를 반환한다. +5. Search Service가 요청자 소유 영상 중 현재 검색 가능한 항목이 1개 이상 있는지 확인하고, 없으면 `409 SEARCH_NOT_READY`를 반환한다. 6. Search Service가 Metadata DB(FTS)에서 키워드 후보 Top-K를 조회 (테넌시 적용) -7. Search Service가 Vector Store(ANN) 에서 벡터 후보 Top-K를 조회 (테넌시 적용) -8. Search Service가 키워드/벡터 후보를 병합(RRF)하여 최종 Top-K 후보를 결정 -9. Search Service가 Metadata DB(SOT) 를 “서빙 게이트”로 조회하여 (권한/존재 여부(삭제=hard delete)/READY 상태) 검증을 수행하고, 최종 컨텍스트(청크 텍스트/타임스탬프)를 로드한다. 이 단계 이후에도 최종 컨텍스트가 0개면 LLM을 호출하지 않고 `"검색 결과가 없습니다"`를 반환한다. +7. Search Service가 Vector Store(ANN)에서 active 인덱스의 벡터 후보 Top-K를 조회한다. previous 세대가 병행 서빙 중이면 previous 인덱스도 별도로 조회한다. (테넌시 적용) +8. Search Service가 키워드 후보와 active/previous 벡터 후보를 병합(RRF)하여 최종 Top-K 후보를 결정한다. +9. Search Service가 Metadata DB(SOT) 를 “서빙 게이트”로 조회하여 권한, 존재 여부, 검색 가능 상태 검증을 수행하고, 최종 컨텍스트(청크 텍스트/타임스탬프)를 로드한다. 이 단계 이후에도 최종 컨텍스트가 0개면 LLM을 호출하지 않고 `"검색 결과가 없습니다"`를 반환한다. 10. Search Service가 Top-K 컨텍스트 + 질의를 서비스 내부 LLM 인터페이스 구현체에 전달하여 최종 답변과 structured `used_refs`를 생성한다. -11. Search Service가 `req_id` + 생성된 답변 + `chunks[{ref, chunk_id, video_id, title, start_ms, end_ms, text, used}]`를 Client에 최종 반환 +11. Search Service가 검색 응답을 Client에 반환한다. 동시에 피드백 검증과 운영 추적에 사용할 수 있도록, 요청 시점의 응답 내용과 active 모델 정보, 실제 조회한 벡터 검색 경로 정보, 검색 범위 축소 여부를 포함한 검색 응답 스냅샷을 불변 기록으로 저장한다. **출력** - `req_id` + 생성된 답변 + `chunks` → Client 반환 @@ -242,7 +298,10 @@ - `chunks[].used`: 해당 청크가 실제 답변 근거로 사용되었는지 여부 **저장 위치** -- 검색 응답은 실시간으로 생성 및 반환되며 별도로 영구 저장하지 않음. (단, 피드백 발생 시 2.6 절차에 따라 수집됨) +| 데이터 | 저장소 | +|--------|--------| +| 검색 응답 본문 | 별도 장기 보관 없음 (실시간 생성 및 반환) | +| 검색 응답 스냅샷 (req_id 기준, 단기 보존). 피드백 수집 시 스냅샷의 핵심 필드가 원본 이벤트에 함께 고정된다. | Metadata DB | ## 2.4 처리 실패 (FAILED) @@ -276,6 +335,7 @@ - Metadata DB: VectorIndexEntry, Chunk, TranscriptSegment, Asset 삭제 (단일 트랜잭션) - Metadata DB: Video 레코드 hard-delete - Object Storage: 원본 영상, 오디오, 키프레임 파일 삭제 (비동기, 메인 서비스와 분리하여 처리) + - 단, 이미 수집된 피드백 이벤트와 이미 만들어진 학습/평가용 데이터셋은 운영 기록으로 그대로 보존한다. 다만 이후 새 데이터셋을 만들 때는 이미 삭제된 영상이나 청크를 다시 사용하지 않는다. 6. DELETE_REQUEST 처리 시 대상 Video 레코드가 이미 존재하지 않으면 중복 삭제로 간주하고 성공으로 처리한다. 이 경우에도 메시지는 Ack되며 오류로 취급하지 않는다. **출력** @@ -294,32 +354,71 @@ - 사용자가 검색 결과에 대해 좋아요/싫어요를 누를 때 **처리** -1. Client가 Search Service 응답의 `chunks`에서 전체 `chunk_id` 목록을 `topk_ids`로, `used=true`인 항목의 `chunk_id` 목록을 `used_ids`로 파생한 뒤 `req_id`와 함께 Reverse Proxy를 거쳐 Core API Server에 피드백을 전송한다. -2. Core API Server가 해당 시점의 `query_text`, `topk_ids`, `used_ids`를 함께 수집하여 검색 응답 단위로 저장한다. +1. Client가 검색 응답 단위의 피드백을 Core API Server에 전송한다. +2. Core API Server가 사용자 권한을 검증하고, `req_id`에 대응하는 검색 응답 스냅샷을 조회하여 동일 사용자 요청인지, 허용된 시간 창 내의 피드백인지, 이미 무효화된 요청이 아닌지 확인한다. +3. Core API Server가 검증된 피드백 이벤트에 검색 시점의 질문, 응답 결과, 활성 모델/인덱스 정보 등 피드백 검증과 추적에 필요한 정보를 함께 담아 Feedback Ingestion Pipeline으로 전달한다. +4. Feedback Ingestion Pipeline이 검증된 피드백 이벤트를 원본 로그 형태로 Object Storage에 적재한다. 이 원본 로그는 이후 데이터셋 생성 시 재현 가능한 최소 맥락을 포함해야 한다. **저장 위치** | 데이터 | 저장소 | |--------|--------| -| user_id, req_id, query_text, rating, topk_ids, used_ids, created_at | Metadata DB | +| 검색 응답 단위 피드백 원본 이벤트 로그 | Object Storage | -## 2.7 모델 재학습 및 배포 +## 2.7 피드백 데이터셋 생성 -**발생 시점** -- Pipeline Controller가 재학습 작업을 트리거할 때 +**입력** +- 주체: ML Lifecycle Worker (배치 전처리), 운영자 +- 데이터: 피드백 원본 이벤트 로그 (Object Storage) **처리** -1. Pipeline Controller가 Message Broker에 재학습 작업 큐 발행 (TRAINING_REQUEST) -2. Model Training Worker가 Metadata DB에서 like 피드백 로그 로드 -3. used_ids를 positive, topk 중 used가 아닌 chunk를 negative로 분류하여 (query, positive_chunk, negative_chunk) 형태의 JSONL로 전처리 후 Object Storage에 저장 -4. Model Training Worker가 Managed ML Platform에 파인튜닝 작업(Job)을 요청하여 외부 GPU 클러스터에서 파인튜닝 수행 -5. 학습 완료 후 반환된 평가 지표가 기준을 통과하면, Model Training Worker가 모델 파일 + 버전 메타데이터를 배포 가능한 아티팩트로 패키징한다. -6. 운영자가 선택한 모델 아티팩트를 Managed Embedding Endpoint 배포 경로에 반영하고, 새 프로세스를 기동하여 서빙한다. +1. ML Lifecycle Worker가 정기 배치에 따라 신규 피드백 원본 로그를 읽고, 검색 시점의 불변 서빙 맥락이 포함된 이벤트만 학습 가능한 데이터셋으로 전처리한다. +2. 생성된 데이터셋은 학습 시점의 입력을 다시 추적할 수 있도록 버전 단위로 저장한다. 학습용 데이터셋과 평가용 데이터셋은 분리된 산출물로 관리한다. +3. 전처리 완료 후 학습 파이프라인을 자동 트리거한다. 이미 MLPipelineRun이 실행 중이면 즉시 시작하지 않고, 최신 데이터셋 기준의 다음 실행을 대기 상태로 둔다. + +**출력** +- 학습용 데이터셋 생성 +- 학습 파이프라인 자동 트리거 **저장 위치** | 데이터 | 저장소 | |--------|--------| -| 전처리된 학습 데이터 (JSONL) | Object Storage | -| 모델 파일 + 버전 메타데이터 | Model Artifact Files | +| 전처리된 학습 데이터셋 | Object Storage | + +## 2.8 모델 재학습 및 재색인 + +**입력** +- 주체: 운영자, ML Lifecycle Worker +- 데이터: 선택된 학습용 데이터셋 버전 (Object Storage), 평가용 데이터셋 버전 (Object Storage: 관리자가 사전 업로드), 후보 모델 아티팩트 + +**처리** +1. 전처리 완료 후 ML Lifecycle Worker가 최신 학습용 데이터셋으로 후보 임베딩 모델을 학습하고, 결과 모델을 저장한다. 이후 실행 상태 추적을 위해 MLPipelineRun을 생성한다. +2. ML Lifecycle Worker가 후보 모델과 기준 모델의 검색 성능을 별도 평가용 데이터셋으로 비교 평가한다. 평가 결과 요약은 Metadata DB에 저장하고, 상세 결과는 아티팩트로 저장한다. +3. 평가를 통과하면 ML Lifecycle Worker가 후보 모델 전용 인덱스를 구축한다. 전체 코퍼스의 즉시 재색인은 필수 조건이 아니며, 신규 유입 데이터와 고활성 데이터부터 우선 반영한다. 이 과정에서도 사용자 검색은 기존 서빙을 유지한다. +4. 후보 모델 readiness와 우선 반영 대상의 인덱싱 완료가 확인되면 ML Lifecycle Worker가 마지막 정상 서빙 상태를 롤백 복구용 스냅샷으로 저장한 뒤 서빙을 전환한다. 전환 후에는 직전 active 조합이 previous 세대로 보존된다. +5. 서빙 전환 이후 남아 있는 더 오래된 세대 데이터는 최신 active 모델 기준으로 점진 재임베딩한다. 온라인 검색은 active와 previous의 최대 2세대까지만 병행 지원한다. +6. 운영자가 배포 후 새 모델을 문제 모델로 판정하면, 마지막 정상 서빙 상태를 가리키는 rollback snapshot을 기준으로 rollback 복구를 시작한다. +7. rollback 대상 모델 readiness와 snapshot 인덱스 복원이 완료되면 ML Lifecycle Worker는 마지막 정상 서빙 상태를 기준으로 `ModelRelease`를 복원하고, 전환 중 후보 조합 관련 메타데이터는 비운다. +8. 문제 모델 기간에 생성된 기존 데이터가 포함된 영상은 복구가 끝날 때까지 검색 범위에서 제외한다. Search Service는 남아 있는 검색 가능 영상으로 검색을 계속 제공하고, 일부 영상이 복구 중임을 사용자에게 고지한다. 해당 데이터는 복원된 정상 서빙 상태 기준으로 재임베딩하며, 복구가 끝난 영상부터 다시 검색 범위에 합류시킨다. +9. 평가 실패 또는 처리 중 오류가 발생하면 파이프라인 실행 정보를 기록하고 종료한다. 기존 서빙은 유지되며, 운영자는 Admin Dashboard에서 실패 상태를 확인한다. + + +**출력** +- 후보 모델 파일 → Model Artifact Files +- 평가 결과 → Metadata DB +- 평가 질의별 상세 아티팩트 → Object Storage +- 재색인된 임베딩 벡터 → Vector Store +- 활성 모델/인덱스 버전 (릴리스 레코드) → Metadata DB + +**저장 위치** +| 데이터 | 저장소 | +|--------|--------| +| 후보 모델 파일 + 버전 메타데이터 | Model Artifact Files | +| 평가 결과 집계 | Metadata DB | +| 평가 질의별 상세 JSONL 아티팩트 | Object Storage | +| 재색인된 임베딩 벡터 | Vector Store | +| 활성 모델/인덱스 버전 (릴리스 레코드) | Metadata DB | + + # 3. Data Model (high level) @@ -339,6 +438,7 @@ - source_url: 외부 URL 입력 시 원본 URL (Local File의 경우 null) - storage_path: Object Storage 내 영상 파일 경로 - status: 처리 상태 (PENDING / UPLOADED / PROCESSING / READY / FAILED / DELETING) +- search_serving_state: 검색 노출 상태 (`SERVABLE` / `ROLLBACK_EXCLUDED`). 기본값은 `SERVABLE`이며, rollback 복구 중인 영상은 일시적으로 `ROLLBACK_EXCLUDED`가 된다. - failed_stage: 실패 시 어느 범주의 단계에서 실패했는지 나타내는 분류값. 재시도 시 재개 판단 근거로 활용한다. (예: DOWNLOAD / EXTRACT / STT / CHUNKING / EMBEDDING / VECTOR_UPSERT) - created_at: 업로드 요청 시각 - updated_at: 상태 변경 시각 @@ -379,18 +479,59 @@ STT 결과물로 생성되는 시간 구간 단위의 원본 텍스트 - embedding_model_version: 임베딩 생성에 사용된 모델 버전 - created_at: 생성 시각 -## 3.5 Feedback -사용자의 명시적 피드백 및 모델 학습에 필요한 컨텍스트 로그 -- id: 피드백 고유 ID +## 3.5 Feedback Event +검색 응답 단위로 수집되는 원본 피드백 이벤트 로그. Object Storage에 저장되는 논리 데이터 모델이다. +- event_id: 피드백 이벤트 고유 ID - user_id: 피드백을 남긴 사용자 ID (User 참조) -- req_id: 검색 응답 고유 ID. 피드백의 귀속 단위이며 Search Service가 응답마다 생성한다. 별도 검색 응답 레코드의 FK가 아니라 클라이언트-Search-Core 간 상관관계용 opaque ID로 사용한다. +- req_id: 검색 응답 고유 ID. 피드백의 귀속 단위이며 Search Service가 응답마다 생성한다. - query_text: 피드백 시점의 질의 텍스트 - rating: 평가 (LIKE / DISLIKE) - topk_ids: SOT 게이트를 통과해 실제 응답 생성에 사용된 최종 청크 ID 목록 (relevance 순) - used_ids: LLM이 structured `used_refs`를 통해 실제 참조했다고 보고한 최종 청크 ID 목록 +- active_model_version: 피드백이 발생한 시점에 Search Service가 사용한 활성 임베딩 모델 버전 +- active_index_name: 피드백이 발생한 시점에 Search Service가 사용한 활성 벡터 인덱스 식별자 +- response_snapshot_ref: 필요 시 검색 응답 스냅샷 원본을 다시 조회할 수 있는 참조값 - created_at: 피드백 시각 -## 3.6 VectorIndexEntry (Derived; Vector Store) +## 3.6 SearchResponseSnapshot +검색 응답 시 피드백 검증과 운영 추적을 위해 단기 보존되는 불변 스냅샷. Search Service가 생성하며 Metadata DB에 TTL 기반으로 저장된다. +- req_id: 검색 응답 고유 ID (PK, Search Service가 생성) +- user_id: 검색 요청자 ID (피드백 시 소유권 검증용) +- query_text: 검색 질의 텍스트 +- topk_chunk_ids: SOT 게이트를 통과한 최종 청크 ID 목록 (relevance 순) +- used_chunk_ids: LLM이 실제 참조한 청크 ID 목록 +- active_model_version: 검색 시점의 활성 임베딩 모델 버전 +- active_index_name: 검색 시점의 활성 벡터 인덱스 식별자 +- served_vector_paths: 검색 시 실제 조회한 벡터 검색 경로 목록. 각 항목은 `model_version`, `index_name`을 가진다. +- scope_restricted: 검색 시점에 일부 영상이 복구 중이라 검색 범위가 축소되었는지 여부 +- scope_notice: 검색 범위 축소 사실을 사용자에게 고지한 문구 또는 구조화된 코드 +- created_at: 스냅샷 생성 시각 +- expires_at: 만료 시각 (TTL 기반 단기 보존) + +## 3.7 ModelEvaluation +후보 임베딩 모델과 기준 모델의 검색 성능 비교 평가 1건에 대한 집계 결과 +- id: 평가 실행 고유 ID +- candidate_model_version: 평가 대상 후보 모델 버전 +- baseline_model_version: 비교 기준 모델 버전 +- evaluation_dataset_ref: 학습셋과 분리된 immutable 평가 데이터셋 참조값 +- sample_count: 평가에 사용한 질의 수 +- status: 평가 실행 상태 +- quality_metrics: 검색 품질 지표 집합 (구체 항목은 Spec에서 정의) +- pass_criteria: 특정 평가에서 PASS/FAIL을 판단할 때 사용한 기준 +- overall_decision: quality_metrics와 pass_criteria를 바탕으로 내린 최종 판정 (PASS / FAIL) +- fail_reason: 평가 실패 시 원인 요약 +- created_at: 레코드 생성 시각 + +## 3.8 ModelEvaluationDetail Artifact +평가 실행 1건에 대응하는 질의별 상세 비교 결과 아티팩트. Metadata DB 테이블이 아니라 별도 파일 아티팩트로 저장한다. +- evaluation_id: 상위 평가 실행 ID (ModelEvaluation 참조) +- storage_path: 질의별 상세 결과 JSONL 아티팩트 경로 +- format: 저장 포맷 (JSONL) +- description: 질의 텍스트, 기대 결과, 반환 결과, 질의별 검색 품질 지표를 포함하며 필요 시 운영 절차에서 조회한다. +- created_at: 아티팩트 생성 시각 + +## 3.9 VectorIndexEntry (Derived; Vector Store) +- index_name: 모델 버전별 물리 분리 인덱스 식별자. 서빙 대상 인덱스는 릴리스 레코드의 active/previous 조합 기준으로 결정된다. - chunk_id: Chunk 고유 ID (SOT의 Chunk.id와 동일 키) - user_id: 테넌시 필터용 - video_id: 스코프 필터용 @@ -398,11 +539,41 @@ STT 결과물로 생성되는 시간 구간 단위의 원본 텍스트 - embedding_model_version: 모델 버전 - created_at: 적재 시각 -## 3.7 Async Message Contract -비동기 파이프라인에서 사용되는 공통 메시지 규격. 페이로드에는 상태 조회를 위한 최소한의 식별자(video_id 등)만 포함하며, 상세 데이터는 Worker가 Metadata DB를 직접 조회하여 획득한다. +## 3.10 MLPipelineRun +ML 피드백 루프 파이프라인 실행 1회에 대한 추적 레코드 +- id: 실행 고유 ID +- status: 현재 진행 상태 +- failed_stage: 실패 단계 +- failure_type: 실패 유형 +- candidate_model_version: 이번 실행의 후보 모델 버전 +- candidate_index_name: 이번 실행의 후보 인덱스 식별자 +- dataset_version: 학습에 사용한 데이터셋 버전 +- evaluation_id: 연결된 평가 결과 식별자 +- cutover_time: 서빙 전환 전에 반영이 완료되어야 하는 기준 시각 +- superseded_by_run_id: 더 최신 실행으로 대체되었을 때의 실행 ID +- failure_reason: 실패 원인 요약 +- created_at / updated_at + +## 3.11 ModelRelease +모델 서빙 상태의 SOT. 현재 서빙 조합, 전환 중인 후보 조합, 마지막 정상 서빙 조합의 롤백 복구용 스냅샷 포인터를 관리하는 릴리스 레코드. snapshot 필드는 실제 모델/인덱스 본체가 아니라 복원 대상 모델 버전과 인덱스 식별자를 가리키는 메타데이터다. 실제 모델 파일은 Model Artifact Files에, 인덱스 스냅샷은 Index Snapshot Files에 보관한다 +- release_status: 현재 모델 전환 및 롤백 복구 진행 상태. 롤백 복원 완료 후에는 안정 상태로 되돌린다. +- active_model_version: 현재 활성 모델 버전 +- active_index_name: 현재 활성 인덱스 식별자 +- previous_model_version: 현재 병행 서빙 중인 직전 모델 버전 (없으면 null) +- previous_index_name: 현재 병행 서빙 중인 직전 인덱스 식별자 (없으면 null) +- candidate_model_version: 전환 중인 후보 모델 버전 (없으면 null). 롤백 복원 완료 후에는 null로 초기화한다. +- candidate_index_name: 전환 중인 후보 인덱스 식별자 (없으면 null). 롤백 복원 완료 후에는 null로 초기화한다. +- rollback_snapshot_active_model_version: 마지막 정상 서빙 조합의 active 모델 버전을 가리키는 복원 포인터 +- rollback_snapshot_active_index_name: 마지막 정상 서빙 조합의 active 인덱스 식별자를 가리키는 복원 포인터 +- rollback_snapshot_captured_at: 마지막 정상 서빙 조합 스냅샷 저장 시각 +- candidate_ready_at: 후보 조합의 readiness 확인 시각 (없으면 null). 롤백 복원 완료 후에는 null로 초기화한다. +- switched_at: 마지막 서빙 전환 또는 롤백 복원 완료 시각 + +## 3.12 Async Message Contract +비동기 파이프라인에서 사용되는 공통 메시지 규격. Video-processing 메시지에만 공통 Envelope를 사용하며, 페이로드에는 상태 조회를 위한 최소한의 식별자(video_id 등)만 포함한다. TRAINING_REQUEST와 ROLLBACK_REQUEST는 video-processing shared envelope가 아니라 별도의 control-message schema를 사용한다. **공통 Envelope (MessageEnvelope)** -- message_type: 메시지 종류 (PREPROCESS_REQUEST / TRAINING_REQUEST 등) +- message_type: 메시지 종류 (PREPROCESS_REQUEST / DELETE_REQUEST 등) - payload_version: 스키마 버전 (예: v1) - trace_id: 분산 추적 및 로그 상관관계 ID (모든 내부 호출 및 큐 메시지는 동일 trace_id 상속) - attempt: 재시도 횟수 (멱등/재처리 판단에 사용. 최초 1, 재발행 시 +1) @@ -412,7 +583,9 @@ STT 결과물로 생성되는 시간 구간 단위의 원본 텍스트 **메시지 타입별 제약사항 (Payload)** - `PREPROCESS_REQUEST`: Payload 추가 필드 없음. 워커 통합으로 인해 단일 큐로 파이프라인 전체(다운로드~추출~임베딩)를 트리거함. - `DELETE_REQUEST`: Payload 추가 필드 없음. Worker가 video_id로 DB를 조회하여 storage_path 등 삭제 대상 정보를 확인하고 연쇄 삭제를 수행함. -- `TRAINING_REQUEST`: Payload 추가 필드 없음. 학습 대상 및 범위는 DB의 피드백 로그를 기준으로 워커가 자체 조회함. ---- +**Control Message Schemas** +- `TRAINING_REQUEST`: `message_type`, `payload_version`, `trace_id`, `attempt`, `issued_at`만 사용한다. `video_id`는 포함하지 않는다. +- `ROLLBACK_REQUEST`: `message_type`, `payload_version`, `trace_id`, `attempt`, `issued_at`만 사용한다. `video_id`는 포함하지 않으며, rollback control message는 video-processing shared envelope와 분리한다. +---