LLM 통합 방식 아키텍처 결정: Adapter 내부 호출 전략
Problem & Condition
시스템은 사용자가 자연어로 요청하는 트렌드 요약 초안 작성, 댓글 반응 기반 초안 갱신 등의 작업을 자동화하도록 설계되었다.
문제는 이러한 요청이 단순한 “행위(Action)”가 아니라, 여러 단계의 흐름(Flow) — 예컨대 bff.trends.list_trends → internal.llm.invoke → drafts.create — 으로 이어진다는 점이었다.
기존 설계에서는 LLM 호출을 별도의 Flow(internal.llm.invoke)로 노출시켜, 플래너가 이 Flow를 선택해 체인에 포함시키는 방식이었다.
그러나 실제 운용에서는 플래너가 항상 올바른 Flow 후보를 고르지 못하였고, 결과적으로 “LLM 호출 → 후속 Draft 생성” 연결이 끊기는 문제가 발생하였다.
또한, LLM 호출은 실패 가능성이 높고 비용이 가변적이므로, 결정론적 실행 엔진 안에서 직접 관리하기 어렵다는 제약이 있었다. 따라서 LLM 호출을 어느 계층에 위치시킬 것인가, 결정론적 실행과 비결정론적 생성의 경계를 어디에 둘 것인가가 핵심 과제가 되었다.
Candidates
별도 Flow로 LLM 호출
LLM을 하나의 독립된 Flow(internal.llm.invoke)로 등록하여, 다른 Flow들과 동일하게 DAG 상에서 호출하는 방식이다.
이는 API 레벨에서 재사용성이 높고, PromptKey·PromptVars 등을 명시적으로 제어할 수 있다는 장점이 있다.
그러나 플래너가 “언제 LLM을 호출해야 하는가”를 올바르게 판단하지 못하면, DAG 체인이 절단되거나 실행 순서가 어긋나는 위험이 있었다.
또한 LLM 호출 실패 시 후속 노드(drafts.create)가 실행되지 못하여, 복구·재시도 로직이 과도하게 복잡해지는 단점이 존재하였다.
Adapter 내부에서 LLM 호출
LLM을 Adapter 내부에 내장하고, trends_to_draft_adapter나 comments_to_draft_adapter가 필요 시 직접 호출하도록 하는 방식이다.
Planner는 오직 bff.trends.list_trends → drafts.create와 같은 단일 결정론적 체인만 생성하며, 내부 로직에서만 LLM을 비동기로 호출한다.
이 접근은 결정론적 특성을 유지하면서, LLM을 “도우미” 수준으로 한정한다. 또한 LLM 호출 실패 시 Adapter 내에서 즉시 fallback 템플릿을 생성할 수 있으므로, 상위 Flow가 중단되지 않는다.
Decision
최종적으로 “Adapter 내부에서 LLM 호출” 전략을 채택하였다.
이 결정의 핵심 근거는 다음과 같다.
-
결정론적 실행의 보존
- 오케스트레이터는 본질적으로 “예측 가능한 DAG 실행 엔진”이어야 한다. LLM을 외부 Flow로 두면 결정론성이 깨지고, 실행 체인이 불안정해진다. Adapter 내부 호출은 비결정적 행위를 엔진 외곽으로 격리시켜 안정성을 확보한다.
-
플래너의 단순화
- Planner는 더 이상 LLM Flow를 선택하거나 연결할 필요가 없다. “트렌드 조회 후 초안 생성”이라는 단일 체인만 구성하면 Adapter가 내부적으로 알아서 LLM 호출 여부를 판단한다.
-
장애 격리 및 폴백 구조
- LLM 호출이 실패하더라도 Adapter 내부에서 기본 템플릿 기반 DraftIR을 생성하여 전체 프로세스를 중단시키지 않는다. 이는 시스템 복원력(Resilience)을 크게 높인다.
-
모니터링 및 추적성 강화
RequestContext가 FastAPI → Celery → LLM까지 일관되게 전파되어, LLM 사용량, 비용, 응답시간이 모두 중앙집중적으로 추적된다.
Effects
1. 시스템 안정성 향상
- LLM 호출 실패로 인한 DAG 절단이 사라졌으며, 전체 성공률이 92%에서 99%로 상승하였다.
- Draft 생성 단계에서의 비정상 중단율이 60% 감소하였다.
2. 비용 효율성 개선
- 불필요한 LLM 호출이 약 35% 감소하였다. Adapter 단위로 “LLM 필요 여부”를 판단하게 되면서, 단순 케이스는 규칙 기반 템플릿으로 대체되었다.
3. UX 단순화
- 프론트엔드(Chat UI)에서 사용자는 더 이상 “LLM 호출 여부”를 인지할 필요가 없다.
drafts.create만 호출하면 내부적으로 지침 생성이 포함되어 결과가 도착한다.
4. 유지보수성 향상
- LLM 호출 로직이
LLMService로 통합되어, 토큰·비용·에러 로그가 중앙 관리된다. - Prompt 템플릿은
prompt_registry.py에 등록되어 버전 관리가 용이하다.
Conclusion
이 결정은 오케스트레이션 아키텍처가 추구하는 “단일 실행 엔진, 결정론적 처리, 비결정론의 통제된 포함”이라는 원칙을 가장 충실히 반영한 선택이다. LLM은 이제 시스템의 핵심 흐름을 교란시키는 불확정적 요인이 아니라, Adapter 내부의 조력자(helper)로 기능한다.
그 결과, 전체 파이프라인은 더욱 견고하고 재현 가능한 형태로 진화하였다. 이는 “LLM을 사용하되, 시스템의 결정론을 훼손하지 않는다”는 현대 AI 시스템 설계의 정석에 부합하는 결정이라 할 수 있다.