Skip to content

Commit 60ae3ca

Browse files
committed
docs(ai): ai_server 패키지 가이드의 stale 참조 정정
- 존재하지 않는 chain/parsers/·rag/pgvector_client.py·storage/keys.py·analyzer/feedback_generator.py 제거 - 실제 파일명으로 교체: rag/splitter.py→chunker.py, analyzer/repo_analyzer.py→repository_analyzer.py, model/messages.py→model/messages/ 패키지, messaging/consumers/ 구조 반영 - 미기재 모듈 core/·observability/ 추가, voice '(Phase 2)'·runner '(도입 예정)' 라벨을 실구현 상태로 갱신 - §3 lifespan 예시를 MessagingRuntime 실제 패턴으로 교체
1 parent cc683d9 commit 60ae3ca

1 file changed

Lines changed: 66 additions & 49 deletions

File tree

ai/src/ai_server/CLAUDE.md

Lines changed: 66 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ messaging │
1515
└──→ voice ──→ │
1616
│ │
1717
└──→ rag ────┴──→ storage (S3)
18-
└──→ httpx (Core API)
18+
└──→ core (httpx, Core internal API)
1919
2020
config: 모두가 의존
2121
model: 모두가 의존 (Pydantic 스키마)
22+
observability: chain 에 붙는 LangChain 콜백 — core 경유로 호출 로그 POST
2223
```
2324

2425
원칙:
@@ -45,22 +46,21 @@ settings = Settings() # singleton
4546
```
4647

4748
### `model/`
48-
- RabbitMQ envelope 모델
49-
- 도메인 객체 (`AnalyzedResume`, `QuestionPool`, `FollowUpResult`)
50-
- LLM 응답 schema (Pydantic) — `OutputParser`에 사용
49+
- RabbitMQ envelope 모델 (`envelope.py`)
50+
- 메시지 페이로드 — `model/messages/` 하위에 도메인별 파일
51+
(`analyze.py`, `questions.py`, `followup.py`, `feedback.py`, `voice.py`, `tts.py`, `realtime.py`)
52+
- LLM 응답 schema (Pydantic) — 체인의 구조화 출력 검증에 사용
53+
- 모든 페이로드 모델은 `_config.camel_config()` 로 wire 필드명을 camelCase 로 직렬화
5154

5255
```python
53-
# model/messages.py
54-
class ResumeAnalyzeRequest(BaseModel):
55-
resume_id: int
56-
s3_key: str
57-
58-
class ResumeAnalyzed(BaseModel):
59-
resume_id: int
60-
summary: str
61-
tech_stack: list[str]
62-
document_s3_key: str
63-
embedding_chunk_count: int
56+
# model/messages/questions.py (발췌)
57+
class QuestionPoolCallbackPayload(BaseModel):
58+
model_config = camel_config()
59+
60+
session_id: int
61+
kind: CallbackKind = "POOL"
62+
questions: list[GeneratedQuestion] = []
63+
status: GenerationStatus = "OK"
6464
```
6565

6666
### `api/`
@@ -70,11 +70,15 @@ class ResumeAnalyzed(BaseModel):
7070

7171
### `messaging/`
7272
- aio-pika consumer / publisher
73-
- 큐별 consumer 함수 분리
73+
- 큐별 consumer 는 `messaging/consumers/{name}_consumer.py` 로 분리
74+
(resume/repository/web/cover_letter/questions/followup/feedback/voice/tts)
75+
- 조립·기동은 `runner.py``MessagingRuntime` (§3), 연결은 `connection.py`,
76+
콜백 발행은 `publisher.py`, 멱등은 `idempotency.py`(`LruIdempotencyStore`),
77+
RealTime 직접 발행은 `progress.py`(분석 진행)·`session_notify.py`(델타/오디오)
7478
- 모든 consumer는 envelope parsing → trace_context → 비즈니스 핸들러 호출 패턴
7579

7680
```python
77-
# messaging/resume_consumer.py
81+
# messaging/consumers/resume_consumer.py (패턴)
7882
async def consume(message: AbstractIncomingMessage) -> None:
7983
async with message.process(requeue=False):
8084
envelope = parse_envelope(message)
@@ -86,52 +90,65 @@ async def consume(message: AbstractIncomingMessage) -> None:
8690
```
8791

8892
### `analyzer/`
89-
- use case 단위 (`resume_analyzer.py`, `repo_analyzer.py`, `feedback_generator.py`)
90-
- 외부 입력 → 내부 모듈 조합 → 결과 publish
93+
- 분석 use case 단위 (`resume_analyzer.py`, `repository_analyzer.py`, `web_resume_analyzer.py`)
94+
- 소스 추출 추상화는 `analyzer/sources/` (PDF/GitHub/웹/텍스트), 임베딩 인제스트는 `_embedding_step.py`
95+
- 외부 입력 → 내부 모듈 조합 → 결과 publish. 피드백 생성은 analyzer 가 아니라
96+
`messaging/consumers/feedback_consumer.py` + `chain/feedback_generation_chain.py` 에 있다
9197
- LLM 호출 자체는 `chain/`으로 위임
9298

9399
### `chain/`
94-
- LangChain 체인 정의
100+
- LangChain 체인 정의 (`document_analysis_chain.py`, `question_generation_chain.py`,
101+
`followup_generation_chain.py`, `feedback_generation_chain.py`, `pdf_vision.py`, `sentence_split.py`)
95102
- `chain/prompts/` 하위에 prompt 템플릿 (모든 프롬프트가 한 곳에)
96-
- `chain/parsers/` 출력 파서
103+
- 출력 파싱은 별도 모듈 없이 각 체인 안에서 Pydantic 구조화 출력으로 검증
97104

98105
### `rag/`
99-
- 청킹 (`splitter.py`)
100-
- 임베딩 생성 (`embedder.py`)
101-
- 검색 어댑터 (Core API client `pgvector_client.py`)
106+
- 청킹 (`chunker.py``MarkdownChunker`)
107+
- 임베딩 생성 (`embedder.py` — provider 추상화 + Gemini/Mock 구현)
108+
- 검색은 rag 모듈이 아니라 `core/client.py: search_embeddings` (Core `POST /api/internal/embeddings/search`)
102109

103-
### `voice/` (Phase 2)
104-
- `voice/stt/` — interface + provider impls
105-
- `voice/tts/`
106-
- `voice/analysis/` — WPM, filler, silence
110+
### `core/`
111+
- Core 내부 API httpx 클라이언트 (`client.py`) — `X-Internal-API-Key` 인증
112+
- GitHub token 위임 · 임베딩 upsert/검색 · AI 호출 로그 기록 (엔드포인트 목록:
113+
[`/docs/messaging.md §10`](../../../docs/messaging.md))
114+
115+
### `voice/`
116+
- `voice/stt/` — interface + provider impls (배치 Whisper/Deepgram + 라이브 Deepgram Live)
117+
- `voice/tts/` — provider 추상화 (Gateway/Gemini/OpenAI/Mock)
118+
- `voice/analysis/` — WPM, filler, silence (`metrics.py`)
107119

108120
### `storage/`
109-
- S3 client wrapper (`s3.py`)
110-
- key 생성 헬퍼 (`keys.py`) — [`/docs/storage.md §2`](../../../docs/storage.md) 컨벤션 준수
121+
- `ObjectStorage` 추상화 (`base.py`) + `s3.py` / `local_fs.py` 구현, `factory.py` 로 토글
122+
- 객체 key 는 각 사용처에서 [`/docs/storage.md §2`](../../../docs/storage.md) 컨벤션대로 조립 (전용 헬퍼 모듈 없음)
123+
124+
### `observability/`
125+
- `llm_logging_callback.py` — LangChain `AsyncCallbackHandler`. 토큰/latency 측정 후
126+
`core/client.py: record_ai_log` 로 Core `POST /api/internal/ai-logs` (fire-and-forget)
111127

112128
---
113129

114130
## 3. 진입점
115131

116132
### REST (FastAPI)
117133
- `api/health.py` — 헬스체크
118-
- `api/internal/*`Core가 호출할 수 있는 동기 endpoint (필요 시)
134+
- `api/voice_stream.py``/internal/voice/stream` WS (RealTime 이 프록시한 실시간 음성 답변, RT3)
119135

120136
### MQ Consumer
121-
- `messaging/runner.py` (도입 예정) — 모든 consumer를 시작하는 entry
122-
- `main.py` lifespan에서 자동 시작 (또는 별도 프로세스로 분리 검토)
137+
- `messaging/runner.py``MessagingRuntime` 이 의존성(체인·스토리지·Core 클라이언트·notifier)을
138+
조립하고 모든 consumer 를 시작/종료하는 단일 entry
139+
- `main.py` lifespan 에서 `runtime.start()` / `runtime.stop()` 호출
123140

124141
```python
125-
# main.py 의 lifespan
142+
# main.py 의 lifespan (실제 패턴)
126143
@asynccontextmanager
127144
async def lifespan(app: FastAPI):
128-
connection = await connect_robust(settings.rabbitmq_url)
129-
channel = await connection.channel()
130-
await start_resume_consumer(channel)
131-
await start_repo_consumer(channel)
132-
await start_session_consumer(channel)
133-
yield
134-
await connection.close()
145+
runtime = MessagingRuntime(settings)
146+
app.state.messaging = runtime
147+
try:
148+
await runtime.start()
149+
yield
150+
finally:
151+
await runtime.stop()
135152
```
136153

137154
---
@@ -165,25 +182,25 @@ class AnalysisError(Exception):
165182

166183
이력서 분석 (US-09)을 예로 들면:
167184

168-
1. `model/messages.py``ResumeAnalyzeRequest`, `ResumeAnalyzed`, `ResumeFailed` 정의
169-
2. `messaging/resume_consumer.py` 구현 (envelope parse → handler 호출)
185+
1. `model/messages/{name}.py``ResumeAnalyzeRequest`, `ResumeAnalyzed`, `ResumeFailed` 정의
186+
2. `messaging/consumers/resume_consumer.py` 구현 (envelope parse → handler 호출)
170187
3. `analyzer/resume_analyzer.py` 구현
171188
```python
172189
async def handle(req: ResumeAnalyzeRequest) -> None:
173-
pdf_bytes = await s3.get(req.s3_key)
190+
pdf_bytes = await storage.get(req.s3_key)
174191
text = extract_text(pdf_bytes)
175192
result = await resume_chain.ainvoke({"text": text})
176193
md_key = f"analyzed/resume/{req.resume_id}/summary.md"
177-
await s3.put(md_key, result.markdown)
178-
chunks = split(result.markdown)
194+
await storage.put(md_key, result.markdown)
195+
chunks = chunker.split(result.markdown)
179196
embeddings = await embedder.embed(chunks)
180-
await pgvector_client.upsert(req.resume_id, chunks, embeddings)
197+
await core_client.upsert_embeddings(document_id=req.analyzed_document_id, ...)
181198
await publisher.publish_callback(ResumeAnalyzed(...))
182199
```
183-
4. `chain/resume_analyzer_chain.py` (prompt + LLM + parser)
200+
4. `chain/{name}_chain.py` (prompt + LLM + Pydantic 구조화 출력)
184201
5. 단위 테스트 (mock LLM)
185202
6. 통합 테스트 (Testcontainer RabbitMQ + MinIO)
186-
7. main.py lifespan에 consumer 등록
203+
7. `messaging/runner.py``MessagingRuntime` consumer 등록
187204

188205
---
189206

0 commit comments

Comments
 (0)