One-Shot LLM에서 Multi-Turn Agent로: 텍스트-다이어그램 도구를 재구축한 방법
요약
단일 LLM 호출 방식의 한계를 극복하기 위해 텍스트-다이어그램 생성 도구를 멀티턴 에이전트 구조로 재설계한 과정을 다룹니다. 구문 오류와 의미적 드리프트 문제를 해결하기 위해 외부 루프(요청 이해)와 내부 루프(코드 생성)를 분리하고 역할을 세분화했습니다.
핵심 포인트
- 단일 호출의 한계인 구문 오류와 의미적 드리프트 해결 필요
- 요청 이해를 위한 외부 루프와 코드 검증을 위한 내부 루프 분리
- 에이전트 역할을 세분화하여 디버깅 용이성 및 테스트 가능성 확보
- Front Desk 역할을 통한 오케스트레이션 구조 설계
문제점: 단 한 번의 LLM 호출로는 부족하다
저는 자연어를 편집 가능한 다이어그램으로 변환하는 AI 도구를 만들었습니다. 사용자가 흐름을 설명하면, LLM이 Mermaid 코드를 생성하고 그 결과가 실시간으로 렌더링되는 방식입니다.
v1은 단일 LLM 호출 방식이었습니다: 사용자 입력 → LLM → 다이어그램 구조였죠. 실제 서비스 운영을 며칠 해보니, 두 가지 실패 모드가 계속 나타났습니다.
- 복구 불가능한 구문 오류 (Syntax errors). 생성된 Mermaid 코드가 겉보기에는 올바르지만, Mermaid가
Parse error를 발생시키고 사용자는 빨간색 스택 트레이스(stack trace)를 보게 됩니다. 그러면 사용자는 떠납니다. - 조용한 의미적 드리프트 (Silent semantic drift). 사용자가 "로그인 흐름"을 요청하면, LLM은 친절하게도 "인증 코드 전송" 액터를 임의로 추가합니다. 물어보지도 않고, 무언가를 추가했다는 사실을 알려주지도 않습니다.
두 문제 모두 "프롬프트 수정 (tweaking the prompt)"만으로는 안정적으로 해결할 수 없습니다. 첫 번째 문제는 **두 번째 기회 (a second chance)**가 필요하고, 두 번째 문제는 **감사 추적 (an audit trail)**이 필요합니다.
그래서 v2는 **에이전트 (agent)**가 되었습니다: 멀티턴 대화 (multi-turn dialogue), 자기 수정 (self-correction), 그리고 결정적으로 — 사용자가 말한 것과 말하지 않은 것에 대한 메모리(memory)를 갖춘 형태입니다. 이 포스트는 그 에이전트가 실제로 어떻게 구성되어 있는지에 관한 것입니다.
핵심 통찰: 이것은 하나의 루프가 아니라, 두 개의 루프입니다
"멀티턴 다이어그램 생성기"처럼 보이는 것은 실제로는 서로 다른 목표를 가진 두 개의 루프입니다:
외부 루프 (Outer loop) · 요청 이해하기
사용자 발화 → 사실 추출 → 공백 탐지 → 질문 → 정교화
...
외부 루프는 **"내가 요청을 충분히 잘 이해하고 있는가?"**라고 묻습니다. 내부 루프는 **"생성된 코드가 올바른가?"**라고 묻습니다.
이 두 가지를 하나의 루프에서 처리하려고 하면 **"에이전트가 이 모호함에 대해 사용자에게 질문해야 하는가?"**라는 문제에 직면하게 됩니다. 이 질문에는 정답이 없습니다. 왜냐하면 외부 루프의 모호함(사용자가 명시하지 않음)과 내부 루프의 모호함(생성이 대본을 벗어남)은 근본적으로 다르기 때문입니다.
에이전트는 하나의 팀입니다 — 업무를 분담하세요
저는 에이전트를 사건을 처리하는 법률 사무소로 모델링했습니다. 각 역할은 정확히 한 가지 일만 수행합니다:
| 역할 | 수행하는 일 | 수행하지 않는 일 |
|---|---|---|
| Clerk (서기) | 사용자가 명시적으로 언급한 사실을 기록함 | 절대 추측하거나 추론하지 않음 |
| ... |
처음에는 이 모든 것을 하나의 거대한 메가 프롬프트 (mega-prompt)에 몰아넣었습니다. 디버깅은 지옥 같았습니다. Clerk가 사실을 환각 (hallucination)한 것인지, 아니면 Lawyer가 캐릭터를 즉흥적으로 만들어낸 것인지 구분할 수 없었기 때문입니다. 역할을 분리한다는 것은 각 역할이 순수 함수 (pure function)가 된다는 것을 의미합니다: 동일한 입력 → 동일한 출력, 그리고 독립적으로 테스트 가능합니다.
호출 그래프 (Call graph): Front Desk가 모두를 조율합니다
여섯 가지 역할은 병렬로 작동하지 않습니다. 이들은 하나의 파이프라인이며, Front Desk가 유일한 오케스트레이터 (orchestrator)입니다:
User message (사용자 메시지)
↓
Front Desk (대화 기록에 추가)
...
주의 깊게 살펴볼 몇 가지 사항이 있습니다:
- Front Desk가 유일한 상태 유지 (stateful) 컴포넌트입니다. 다른 모든 역할은 순수 함수입니다. 멀티턴 (multi-turn) 메모리는 전적으로 Front Desk가 유지하는 사건 파일 (case file)에 존재합니다.
- Planner가 유일한 분기점입니다. 에이전트의 "질문할 것인가 vs 그릴 것인가"에 대한 결정은 오직 이 하나의 함수에서만 일어납니다.
- 내부 수정 루프 (inner repair loop)는 Lawyer와 Reviewer 사이에서 실행됩니다. 사용자에게 번거로움을 주지 않고 Mermaid 구문 (syntax)의 자가 치유 (self-healing)가 여기서 이루어집니다.
- 의미론적 문제 (semantic issues)가 출력을 차단하지 않습니다. 대신 다이어그램과 함께 전달됩니다 ("여기 다이어그램이 있습니다. 그런데 제가 이 부분을 추가했는데, 그대로 둘까요?"). 사용자는 눈앞에 그림이 있을 때 판단하기가 더 쉽습니다.
기본 원칙: Clerk는 보수적이어야 하며, Lawyer는 즉흥적일 수 있습니다
이 글에서 한 가지만 기억해야 한다면, 바로 이것입니다.
사용자가 다음과 같이 말합니다: "로그인 흐름을 그려줘. 사용자가 비밀번호를 입력하면 시스템이 검증하고 결과를 반환해."
Clerk는 다음과 같이 출력합니다:
{
actors: ["user", "system"],
events: [
...
여기서 sync: "unknown"은 질문을 유발하는 신호입니다. Auditor가 이를 표시하면, Planner가 질문할지 여부를 결정합니다.
만약 Clerk가 "친절하게" 이를 채워 넣는다면 ("로그인은 보통 동기 방식이니까요") 그 신호는 파괴됩니다. 에이전트는 더 이상 "사용자가 실제로 동기 방식이라고 말한 것"과 "내가 지어낸 것"을 구분할 수 없게 됩니다. 그 이후의 모든 하위 결정 과정이 오염됩니다.
Lawyer(변호사)는 다른 제약 조건 하에서 작동합니다: Lawyer는 합리적인 가정으로서 "검증 서비스 (verification service)"를 채워 넣을 수 있지만, 해당 가정은 반드시 케이스 파일의 assumptions 슬롯에 기록되어야 합니다. 이후 Reviewer(검토자)는 다이어그램의 모든 요소를 조정합니다. 즉, 각 요소는 "사용자가 이렇게 말했다" 또는 "Lawyer가 이렇게 가정했다" 중 하나로 추적될 수 있어야 합니다. 이 중 어느 쪽에도 해당하지 않는 것은 **무단 추가 (unauthorized addition)**라고 불리며, 이는 후속 질문("제가 X를 추가했습니다. 유지할까요?")이 됩니다.
이 단 하나의 규칙이
5-unit 예산 중 3개의 에이전트 주도 질문 사용, 2개 남음. 이것이 깔끔한 의미론 (semantics)을 가진 글로벌 예산 (global budget)의 유연성입니다.
새로운 다이어그램 유형을 추가하는 데 필요한 것
이 시스템은 8가지 다이어그램 유형을 지원합니다: Sequence · Flowchart · ERD · State · Class · Mindmap · Gantt · Architecture.
새로운 유형을 추가하는 것은 다음의 네 가지 유형별 구성 요소를 구현하는 것을 의미합니다:
- Clerk (서기) — 이 유형에 대해 자연어에서 사실을 추출하는 방법
- Auditor (감사관) — 어떤 필드가 필수(required)이고 어떤 필드가 선택(optional)인지
- Reviewer (검토자) — 무엇을 승인되지 않은 추가(unauthorized addition)로 간주할 것인지
- Question templates (질문 템플릿) — 프롬프트(prompt)를 어떻게 구성할 것인지 (시퀀스 다이어그램은 "액터(actors)"에 대해 묻고, 클래스 다이어그램은 "엔티티(entities)"에 대해 묻습니다)
Planner (플래너) / Front Desk (프런트 데스크) / state store (상태 저장소)는 변경되지 않습니다 — 이들은 어떤 유형의 다이어그램이 그려지고 있는지에 관여하지 않습니다.
훔쳐올 만한 가치가 있는 세 가지 교훈
-
두 개의 루프를 분리하세요. "요청 이해하기"와 "코드 생성하기"는 상태(state)를 공유할 뿐 서로 다른 문제입니다. 이 둘을 하나로 합치면 "이 모호함에 대해 질문해야 할까요?"라는 대답할 수 없는 함정에 빠지게 됩니다.
-
Clerk은 보수적으로, Lawyer는 즉흥적으로, 엄격히 분리하세요. 만약 Clerk이 빈틈을 임의로 채운다면, 에이전트의 판단권을 LLM의 직관에 넘겨준 셈이 됩니다. 모든 Lawyer의 가정은 Reviewer가 감사할 수 있도록 반드시 기록되어야 합니다.
-
모든 정책은 하나의 Planner 함수에 존재합니다. 질문 빈도, 예산 정책, 또는 "언제 확인할 것인가"에 대한 휴리스틱 (heuristic)을 변경하고 싶나요? 파일 하나면 됩니다. 하위 시스템에 미치는 파급 효과는 제로입니다.
다음 단계
현재 저는 다음 작업들을 진행 중입니다:
- 자동 분류 신뢰도 보정 (Auto-classification confidence calibration) (사용자가 다이어그램 유형을 명시하지 않았을 때, LLM이 얼마나 자주 틀리게 추측하는가?)
- 각 다이어그램 유형에 대한 "승인되지 않은 추가" 규칙 세트 확장
- 원클릭 세션 리플레이 (One-click session replay):
prompt + spec + generated diagram을 공유 가능한 링크로 직렬화(serialize)하여, 버그 리포트 시 에이전트의 전체 상태를 재현할 수 있도록 함
다이어그램, SQL, API 모킹 (API mocks) 또는 다른 목적을 위해 이와 유사한 멀티턴 (multi-turn) LLM 에이전트를 구축하고 있다면, 이 세 가지 원칙이 수 주간의 고통을 줄여줄 것입니다. 에이전트는 단순히 "LLM을 더 많이 호출하는 것"이 아닙니다. 에이전트는 스스로를 수정(self-correct)하고, 부정적인 피드백을 기억하며, 모든 결정에 대한 감사 추적 (audit trail)을 생성하도록 설계된 하나의 팀입니다.
직접 시도해 보세요: text2everything.vip
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기