← 이력서로 돌아가기

캐릭터 제작 에이전트

Router → 병렬 Tool Agents → Idea Agent 3단계 파이프라인을 프레임워크 없이 직접 설계·구현한 멀티 에이전트 시스템.

사용 기술

FastAPIPython 3.12+asyncioPydanticsse-starletteJinja2httpxOpenRouter API

문제#

AI 롤플레이 서비스에서 캐릭터의 품질은 곧 프롬프트의 품질이다. 그런데 유저가 직접 좋은 캐릭터 프롬프트를 쓰기는 어렵고, 그 어려움이 서비스 유입 이후 첫 이탈 지점이었다.

시스템 아키텍처#

단일 에이전트로는 세 영역을 동시에 깊게 다룰 수 없다는 점이 구조의 출발점이다. 역할을 쪼개 병렬로 돌리고, 마지막에 하나의 목소리로 합친다.

유저 입력
    │
    ▼
[1차]  Router  (v5.0.0)  — 비스트리밍
       · 모드 분류        아이디어 / JSX / 진단&압축
       · Concept 생성     genre, mood, core_appeal, constraints
       · tool_calls 선택 + per_tool_task 지시문 생성
    │
    ▼
[2차]  병렬 Tool Agents  — asyncio.gather
       ├── character_writer      이름 · 성격 · 말투 · 외모 · 배경
       ├── narrative_designer    세계관 · 배경사건 · 현재상황 · 로어북 시드
       └── relationship_designer 유저-캐릭터 관계 · 긴장감 · 도입부 힌트
       (진행상황을 SSE tool_start / tool_done 으로 프론트에 실시간 전송)
    │
    ▼
[3차]  Idea Agent  (v4.0.0)  — 스트리밍
       · ToolContext(tool_results) 주입
       · 최종 캐릭터 프롬프트 스트리밍 응답

세 개 모드는 서로 다른 산출물을 낸다. 아이디어는 캐릭터 프롬프트를 기획·작성하고, JSX는 캐릭터 상태창용 코드를 생성하며, 진단&압축은 기존 프롬프트의 품질을 진단하고 토큰을 절약하도록 압축한다. Router 가 입력을 보고 어느 모드인지 스스로 판단한다.

Router 가 선택해 병렬 실행한 Tool Agent 3종 — 서사 설계자 · 인물 작가 · 관계 설계자. 실행 중에는 SSE 로 진행 상태가, 끝난 뒤에는 어떤 Tool 이 쓰였는지가 남는다
Router 가 선택해 병렬 실행한 Tool Agent 3종 — 서사 설계자 · 인물 작가 · 관계 설계자. 실행 중에는 SSE 로 진행 상태가, 끝난 뒤에는 어떤 Tool 이 쓰였는지가 남는다

기술 의사결정#

LangChain / LangGraph 를 쓰지 않은 이유#

이전 RAG 파이프라인에서 프레임워크 내부의 검색 로직이 블랙박스로 동작해, 로컬과 프로덕션의 결과가 달라졌을 때 어디서 갈라졌는지 추적하지 못한 선례가 있었다.

에이전트 오케스트레이션은 결국 "어떤 입력으로 어떤 순서로 무엇을 호출하는가"의 문제이고, 이 규모에서는 직접 구현해도 난이도가 크게 높지 않다고 판단했다. 추상화 레이어를 걷어낸 대가로 동작을 전부 추적·수정할 수 있게 됐고, 실제로 Router 프롬프트를 5세대까지 고쳐 나가는 동안 이 결정의 값어치가 나왔다.

병렬 Tool 실행 (asyncio.gather)#

캐릭터·세계관·관계를 순차 처리하면 응답 대기가 세 배가 된다. 세 Tool 의 출력 스키마가 서로 독립적이라 의존 관계가 없으므로 asyncio.gather 로 묶어 병렬 실행했다.

병렬 실행은 빨라지는 대신 "지금 무슨 일이 일어나는지"가 유저에게 안 보인다는 문제가 따라온다. SSE queue 로 tool_start / tool_done 이벤트를 프론트엔드에 실시간 전송해 어떤 에이전트가 작업 중인지 화면에 드러냈다.

Router 의 선택적 Tool 활성화#

처음에는 모든 요청에 세 Tool 을 전부 실행했다. 그런데 "이름만 바꿔줘" 같은 일반 대화에도 세 번의 LLM 호출이 나가면서 비용이 낭비됐다. Router 가 입력을 분석해 필요한 Tool 만 골라 활성화하도록 바꿔 불필요한 호출을 걷어냈다.

Pydantic 으로 에이전트 간 계약 고정#

에이전트 사이를 dict 로 넘기면 파싱이 실패했을 때 어느 단계에서 깨졌는지 찾기 어렵다. Concept, ToolContext Pydantic 모델로 Router 출력 → Tool 입력 → Idea Agent 입력의 스키마를 고정해, 실패 지점이 예외 메시지에 그대로 드러나게 했다.

프롬프트 버전 관리#

사용자 경험 설계#

생성 결과의 일부를 드래그해 그 부분만 타겟으로 수정을 요청하는 참조 문맥 UX
생성 결과의 일부를 드래그해 그 부분만 타겟으로 수정을 요청하는 참조 문맥 UX

결과#

Router → 병렬 Tool → Idea Agent 를 거쳐 완성된 캐릭터 프롬프트 결과 화면
Router → 병렬 Tool → Idea Agent 를 거쳐 완성된 캐릭터 프롬프트 결과 화면

최종 수정 2026.08.12 · 요약은 이력서에 있습니다.