Skip to content

Latest commit

 

History

History
301 lines (229 loc) · 10.4 KB

File metadata and controls

301 lines (229 loc) · 10.4 KB

AI Server — Claude 컨텍스트

StackUp AI 서버. Python 3.13 + FastAPI + LangChain. RabbitMQ consumer로 동작하며 LLM 호출, RAG, 음성 분석을 담당.

상위 컨텍스트: /CLAUDE.md · 횡단 관심사: /docs/


1. 기술 스택

영역 기술
Language Python 3.13 (.python-version 고정)
패키지 매니저 uv (uv.lock 락파일)
Framework FastAPI 0.135+
ASGI uvicorn (standard)
스키마 Pydantic 2.x + pydantic-settings
HTTP httpx
MQ aio-pika (async AMQP)
객체 스토리지 boto3 (S3 호환)
LLM LangChain 1.x (core + community)
로깅 structlog
빌드 hatchling
테스트 pytest + pytest-asyncio
포맷/린트 black, flake8, pylint

2. 디렉토리 구조

ai/
├── pyproject.toml
├── uv.lock
├── .python-version
├── Dockerfile
├── .env.example
├── src/
│   └── ai_server/
│       ├── __init__.py
│       ├── main.py              # FastAPI 앱 팩토리
│       ├── api/                 # FastAPI 라우터 (health, internal endpoints)
│       │   └── health.py
│       ├── config/
│       │   └── settings.py      # pydantic-settings (env)
│       ├── chain/               # (계획) LangChain 체인 정의
│       ├── rag/                 # (계획) 청킹·임베딩·검색
│       ├── analyzer/            # (계획) 이력서·레포 분석
│       ├── voice/               # (계획) STT/TTS, 음성 분석
│       ├── messaging/           # (계획) RabbitMQ consumer/publisher
│       ├── storage/             # (계획) S3 client
│       └── model/               # (계획) Pydantic 모델 (메시지 envelope, 도메인)
└── tests/

현재는 api/, config/만 존재. 기능 추가 시 위 골격대로 디렉토리 생성.


3. 책임 매트릭스

모듈 책임
main.py FastAPI 부트스트랩, lifespan에서 RabbitMQ consumer 시작
config/settings.py 환경변수 → 타입 안전 설정 객체
api/ 헬스체크 + (필요 시) 내부 디버그 API
messaging/ aio-pika consumer (큐별), publisher
analyzer/ 이력서/레포 분석 use case (PDF 추출, GitHub fetch, 마크다운 생성)
chain/ LangChain prompt template + chain composition
rag/ 청킹, 임베딩 생성, pgvector 검색 호출 (Core API 경유)
voice/ STT/TTS 어댑터, WPM/filler/silence 분석
storage/ S3 GET/PUT 래퍼
model/ RabbitMQ envelope, request/response Pydantic 모델

4. 비책임 (명시적)

  • ❌ PostgreSQL 직접 접근 — Core 서버 API 경유 또는 RabbitMQ 메시지에 데이터 동봉
  • ❌ JWT 발급·검증 — 인증은 Core
  • ❌ REST CRUD API 노출 — 외부 트리거는 RabbitMQ만
  • ❌ 사용자 인증 (내부 통신만) — api/는 헬스체크 / 내부 도구

5. 메시징 (RabbitMQ)

본 서버는 RabbitMQ consumer로 작동.

Queue Bind
q.ai.resume ai.request.resume.*
q.ai.repo ai.request.repo.*
q.ai.session ai.request.session.*

콜백 발행: ai.callback.{type} 익스체인지. 상세 envelope/스키마/재시도: /docs/messaging.md.

consumer 패턴 (aio-pika)

async def consume_resume_analyze(message: AbstractIncomingMessage) -> None:
    async with message.process(requeue=False):  # auto ack on exit
        envelope = parse_envelope(message)
        with trace_context(envelope.trace_id):
            await resume_analyzer.handle(envelope.payload)

멱등 처리

  • envelope의 messageId를 PostgreSQL processed_messages 테이블 (Core API 경유) 또는 AI 프로세스 인메모리 LRU 캐시에 기록
  • 이미 존재하면 skip + ACK
  • (Redis 미사용 — DB 1쿼리 부담을 감수하거나, 인메모리만 쓰고 재시작 시 RabbitMQ delivery_tag로 보조)

6. LLM 사용 패턴

6.1 모델 선택

시점 모델 용도
세션 시작 Pro (Gemini 3.1 Pro 기본) 질문 풀 (품질)
세션 중 Flash (Gemini 3.1 Flash) 꼬리질문 (저지연 < 3s)
분석 (이력서/레포) Pro 마크다운 구조화

설정은 settings.py + 환경변수로 모델명 주입 (코드에 하드코딩 금지).

6.2 LangChain 사용

from langchain_core.prompts import ChatPromptTemplate
from langchain_google_genai import ChatGoogleGenerativeAI

prompt = ChatPromptTemplate.from_messages([
    ("system", "당신은 면접관입니다..."),
    ("human", "이력서: {resume}\n질문 후보: ..."),
])
llm = ChatGoogleGenerativeAI(model=settings.llm_pro_model)
chain = prompt | llm | StructuredOutputParser(schema=...)
  • 모든 프롬프트는 chain/prompts/{name}.py 단일 위치
  • 프롬프트 변경은 PR로 (커밋 메시지에 의도 명시)
  • 응답은 반드시 schema validation (Pydantic) — LLM 결과를 그대로 신뢰 X

6.3 호출 로깅

호출 시작/완료/실패를 Core 서버에 RabbitMQ로 보내 ai_request_logs에 기록 — 또는 자체 endpoint POST. 필드: request_type, model_name, input_tokens, output_tokens, latency_ms, status.

상세: /docs/observability.md §3.


7. RAG 파이프라인

7.1 인제스트

  1. 마크다운 입력
  2. 청킹 (LangChain RecursiveCharacterTextSplitter, chunk_size=1000, overlap=200)
  3. 임베딩 생성 (Gemini text-embedding-004 또는 OpenAI text-embedding-3-small)
  4. Core API 호출 → pgvector INSERT

7.2 검색

  1. 쿼리 텍스트 → 임베딩
  2. Core API: POST /api/internal/embeddings/search (top_k 검색)
  3. 검색 결과 + 추가 메타데이터 → 프롬프트에 주입

Core가 pgvector 단일 진입점을 제공하므로 AI는 직접 pg 호출 X.


8. 음성 처리 (Phase 2)

  • STT: OpenAI Whisper API 채택 (한국어 + 개발 영어 혼용 환경 정확도 우수)
    • 비용: $0.006 / 분 (1시간 면접 ≈ ₩500 / USD ≈ $0.36)
    • 셀프호스팅 옵션: whisper.cpp 또는 faster-whisper (GPU 권장, 비용 ↓ but 운영 부담 ↑)
    • 브라우저 내장 SpeechRecognition API는 정확도 부족으로 채택 안 함
  • TTS 제공자 미정 → 도입 시 본 섹션 갱신
  • 추상화 계층 두기: voice/stt/base.py (interface), voice/stt/whisper_api.py, voice/tts/{provider}.py
  • 분석:
    • WPM = words / minutes
    • 간투어: 한국어 정규식 r"\b(음+|어+|그+)\b" 카운트
    • 침묵: VAD (Voice Activity Detection) 라이브러리 결과 합산

9. 환경 변수

config/settings.pySettings 클래스가 진실 공급원 (single source of truth). 필수 변수는 default 없음 → 부팅 실패로 누락 감지.

class Settings(BaseSettings):
    rabbitmq_url: str
    s3_endpoint_url: str
    s3_access_key: str
    s3_secret_key: str
    s3_bucket_name: str
    openai_api_key: str = ""
    google_api_key: str = ""
    llm_pro_model: str = "gemini-3.1-pro"
    llm_flash_model: str = "gemini-3.1-flash"
    embedding_model: str = "text-embedding-004"
    embedding_dim: int = 768
    core_server_base_url: str = "http://core:8080"

전체 환경변수 카탈로그: /docs/environment.md §4.


10. 로깅 (structlog)

import structlog
log = structlog.get_logger()

log.info("resume.analyze.start", resume_id=42, trace_id=trace_id)
log.error("resume.analyze.failed", resume_id=42, error_code="PDF_PARSE_FAILED", exc_info=True)
  • JSON 출력 (운영) / human pretty (로컬)
  • 컨텍스트 변수로 trace_id, user_id 자동 주입
  • 민감정보 (이력서 본문, 답변 본문) 절대 X
  • 자세한 정책: /docs/observability.md

11. 테스트

uv run pytest                       # 전체
uv run pytest tests/test_rag.py     # 특정 파일
uv run pytest -k "embedding"        # 키워드
  • 비동기 테스트는 pytest-asyncio (@pytest.mark.asyncio)
  • LLM 호출은 mock (LangChain FakeListLLM)
  • RabbitMQ는 Testcontainer 또는 aio-pika mock
  • 자세한 전략: /docs/testing-strategy.md

12. 코드 스타일

  • black (line-length 88)
  • pyproject.toml 기준
  • 함수/변수: snake_case
  • 상수: UPPER_SNAKE_CASE
  • 클래스: PascalCase
  • 타입 힌트 필수 (from __future__ import annotations 없이 PEP 604 union int | None)
  • async first — sync IO 사용 시 명시적 이유

상세 공통 규약: /docs/coding-conventions.md.


13. 빌드·실행

uv sync                                              # 의존성 설치
uv run uvicorn ai_server.main:app --reload          # 개발 실행
uv run python -m ai_server.messaging.runner         # consumer 단독 실행 (도입 후)

# Docker
docker build -t stackup-ai .
docker run --env-file .env -p 8000:8000 stackup-ai

docker-compose.yml에 ai 서비스 추가는 추후 (현재는 Core/PG/MQ/MinIO만 있음).


14. 새 기능 추가 절차

  1. 메시지 스키마 정의 → model/messages.py
  2. RabbitMQ routing key 결정 → /docs/messaging.md 갱신
  3. consumer 작성 → messaging/{name}_consumer.py
  4. 비즈니스 로직 → analyzer/ / chain/ / voice/ 적절한 모듈
  5. 프롬프트 (LLM 호출 시) → chain/prompts/{name}.py
  6. 단위 테스트 (LLM mock) + 통합 테스트 (Testcontainer)
  7. 본 문서 §3, §5 갱신

15. 안티패턴

  • ❌ 프롬프트를 코드 안 곳곳에 산재시키기 → chain/prompts/로 모은다
  • ❌ LLM 응답을 파싱 없이 그대로 사용 → 항상 Pydantic schema validation
  • ❌ 동기 라이브러리(requests, pika) 사용 → async 라이브러리(httpx, aio-pika)
  • ❌ 트랜잭션이 필요한 작업 (PG 직접 접근) → Core API 호출
  • ❌ messageId 멱등 체크 누락 → 중복 처리 위험
  • ❌ 프롬프트에 사용자 답변을 그대로 system message로 → injection 가능. 반드시 user message로.

16. 현재 상태 (2026-04 기준)

  • FastAPI 부트스트랩 + 헬스체크만 구현
  • RabbitMQ consumer 미구현 → US-09(이력서 분석) 작업 시 도입
  • LangChain 기본 import만 있음, 체인·프롬프트 정의 0
  • pgvector 연동 미구현
  • 음성 모듈은 Phase 2에서 본격 작성

각 도입 시 본 문서 갱신.