Skip to content

refactor(ai/api): admin reparse/trigger 를 HTTP → gRPC 로 통일 #416

@cocoyoon

Description

@cocoyoon

배경

prod 의 admin 페이지에서 raw_post reparse / source trigger 버튼이 작동 안 함:
```
Reprocess failed: AI_SERVER_URL is not configured
```

원인: web (Vercel) 의 server route 가 ai-server 의 내부 HTTP (port 10000) 를 직접 호출하는 구조. ai-server 는 internal docker network 에만 노출돼있어 Vercel 에서 도달 불가. dev (web/ai-server 같은 localhost) 에서만 작동했던 dead-on-prod 패턴.

추가 발견 — 현재 ai-server 가 두 wire protocol 동시 노출:

  • gRPC :50051 — `AnalyzeImage`, `ProcessPostEditorial`, `ExtractPostContext` 등 (api-server 가 호출)
  • HTTP :10000 — `/raw-posts/sources/{id}/trigger`, `/raw-posts/items/{id}/reparse` (web 이 호출 시도)

이 두 plane 분리가 design doc 에 명시된 적 없음. 우연한 inconsistency.

목표

ai-server 의 모든 외부 진입점 을 gRPC 로 통일. 새 흐름:
```
admin UI → web /api/admin/.../trigger → api-server /api/v1/admin/.../trigger → ai-server gRPC TriggerSource
```

  • web 은 api-server 만 호출 (`API_BASE_URL`)
  • api-server 가 ai-server 의 유일한 client (gRPC `DecodedAIGrpcClient`)
  • ai-server 의 FastAPI HTTP API 는 deprecated — 후속 PR 에서 제거

효과

  1. ai-server 가 어떻게 노출되는지 외부 dependency 없음 (docker network 격리만으로 충분)
  2. wire format 일관성 — proto = single source of truth
  3. dev/prod 차이 사라짐 (현재는 dev 만 작동)
  4. admin 의 reparse/trigger 가 prod 에서도 작동

작업 분해

  1. proto (`packages/ai-server/src/grpc/proto/inbound/inbound.proto`)

    • `rpc TriggerSource(TriggerSourceRequest) returns (TriggerSourceResponse)`
    • `rpc ReparseRawPost(ReparseRawPostRequest) returns (ReparseRawPostResponse)`
    • 양쪽 codegen 재생성
  2. ai-server

    • 새 gRPC servicer 메서드 — 기존 HTTP handler (`raw_posts_controller.py`) 의 로직 service 레이어로 옮기고 양쪽이 공유
    • HTTP route 는 유지 (deprecated 마크), 후속 PR 에서 제거
  3. api-server

    • `DecodedAIGrpcClient` (`packages/api-server/src/services/ai/`) 에 두 메서드 추가
    • `domains/admin` 또는 `domains/raw_posts` 에 thin proxy handler 두 개:
      • POST `/api/v1/admin/raw-post-sources/{id}/trigger`
      • POST `/api/v1/admin/raw-posts/{id}/reparse`
    • 인증: 기존 admin middleware 재사용
  4. web

    • `packages/web/app/api/admin/raw-post-sources/[id]/trigger/route.ts` → `API_BASE_URL` 호출
    • `packages/web/app/api/admin/raw-posts/items/[id]/reparse/route.ts` → 동일
    • `AI_SERVER_URL` 의존 제거 (`server-env.ts` 에서 export 빼도 OK)

Verification

  • dev: ai-server + api-server 띄우고 admin 페이지에서 reparse/trigger 버튼 → ai-server 로그에 도달
  • dev: `AI_SERVER_URL` env 제거 후에도 작동
  • prod 머지 후: admin 에서 reparse/trigger 클릭 → 502/error 사라짐
  • 기존 자동 cycle (1h discovery, 1m expansion) 영향 없음 (별 path 사용)

Out of scope (별도 PR)

  • ai-server 의 HTTP API (`raw_posts_controller.py`) 완전 제거
  • ai-server 의 `port 10000` 자체 비활성화
  • ADR / architecture doc 작성 ("ai-server 는 gRPC only")

변경 라벨

backend (Rust + Python) + frontend (TypeScript) 양쪽. `bump:patch` (런타임 wire 변경 — minor 도 고려 가능).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions