pydantic-ai와 FastAPI를 사용하여 멀티 에이전트 프롬프트 엔지니어링 런북(Runbook)을 구축한 방법
요약
프롬프트가 여러 곳에 흩어져 발생하는 '프롬프트 확산' 문제를 해결하기 위해 pydantic-ai와 FastAPI를 결합한 아키텍처를 제안합니다. Pydantic 모델을 통해 LLM의 출력 형식을 타입이 지정된 계약(contract)으로 강제하여 시스템의 신뢰성을 높이는 방법을 다룹니다.
핵심 포인트
- 프롬프트 확산으로 인한 출력 형식 불일치 및 시스템 장애 방지
- pydantic-ai를 활용한 LLM 응답의 타입 지정 및 검증
- FastAPI를 통한 에이전트의 단일 배포 가능 서비스화
- 구조화된 데이터 계약을 통한 다운스트림 시스템 안정성 확보
pydantic-ai와 FastAPI를 사용하여 멀티 에이전트 프롬프트 엔지니어링 런북(Runbook)을 구축한 방법
AI 도구(tooling)를 구축하는 대부분의 팀은 결국 동일한 문제에 직면합니다. Notion 문서, Slack 스레드, 그리고 누군가의 로컬 Python 파일에 다섯 가지의 서로 다른 프롬프트 패턴이 흩어져 있게 됩니다. 출력 형식(output format)에 대해 아무도 합의하지 않습니다. SWOT 분석 프롬프트는 어떤 때는 마크다운(markdown)을 반환하고, 어떤 때는 JSON을 반환합니다. 코드 리뷰어(code reviewer)는 그냥 텍스트를 쏟아냅니다. 운영 환경(production)에서 무언가 고장 나면, 실제로 어떤 버전의 프롬프트가 실행되고 있었는지 파악하는 데 40분을 허비하게 됩니다.
이 글에서는 pydantic-ai, FastAPI, 그리고 구조화된 Pydantic 출력을 사용하여 이 문제를 해결하는 아키텍처를 살펴봅니다. 그 결과물은 프롬프트 엔지니어링 런북(runbook)입니다. 이는 SWOT 분석, 소셜 포스트 생성, 코드 리뷰, 다중 형식 요약(multi-format summarisation), 그리고 의사결정 프레임워크(decision framework)를 처리하며, 모두 타입이 지정되고 검증된(validated) 응답을 반환하는 단일 배포 가능 서비스입니다.
문제점: 프롬프트 확산(Prompt Sprawl)이 신뢰성을 해친다
다음은 5명 이상의 엔지니어로 구성된 팀에서 실제로 일어나는 구체적인 시나리오입니다.
누군가가 Jupyter notebook에서 유용한 SWOT 분석기 프롬프트를 작성합니다. 아주 잘 작동합니다. 동료가 이를 FastAPI 라우트(route)로 복사하고, 단어를 몇 개 바꾸고, 모델 이름을 하드코딩(hardcode)합니다. 3개월 후, 세 번째 사람이 약간 다른 버전을 사용하는 Slack 봇을 만듭니다. 이제 운영 환경에는 출력 형식이 어떻게 보여야 하는지에 대한 공유된 계약(contract) 없이 세 개의 SWOT 분석기가 존재하게 됩니다.
한 버전은 strengths를 리스트(list)로 반환하고, 다른 버전은 쉼표로 구분된 문자열(comma-separated string)로 반환하기 때문에 다운스트림 시스템(downstream systems)이 깨지기 시작합니다. 코드 리뷰어 프롬프트는 그냥 가공되지 않은 텍스트(raw text)를 반환하므로, 프론트엔드(frontend)는 정규 표현식(regex)을 사용하여 이를 파싱(parse)해야 합니다. 모델을 업로그할 때, 6개의 프롬프트 함수 중 어떤 것이 조용히 성능 저하(regress)를 일으킬지 알 수 있는 방법이 없습니다.
Slack을 신뢰할 수 있는 단일 출처(source of truth)로 사용하는 팀들이 이 문제에 가장 많이 노출되어 있습니다. 컨텍스트는 메모리에서 사라지는 스레드 속에 존재하고, 결정 사항들은 묻혀버리며, 누군가 그 컨텍스트로부터 구조화된 인사이트(structured insights)를 추출해야 할 때, 수동으로 작업하거나 아무도 관리하지 않는 비공식 스크립트에 의존하게 됩니다. "우리 AI의 출력물은 이렇게 생겼다"라고 명시하는 단일 지점이 없기 때문에 이러한 혼란은 더욱 가중됩니다.
해결책은 더 나은 프롬프트 작성법이 아닙니다. 프롬프트와 시스템의 나머지 부분 사이에 타입이 지정된 계약 계층(typed contract layer)을 두는 것입니다.
접근 방식: 타입 계약 계층으로서의 pydantic-ai + FastAPI
핵심 아이디어는 간단합니다. 런북(runbook) 내의 모든 에이전트가 Pydantic 모델을 출력 타입(output type)으로 갖는 것입니다. pydantic-ai는 LLM 호출 경계에서 해당 계약을 강제합니다. FastAPI는 각 에이전트를 타입이 지정된 요청(request) 및 응답(response) 본문을 가진 엔드포인트(endpoint)로 노출합니다.
왜 다른 대안 대신 pydantic-ai를 사용했을까요?
LangChain은 가장 명확한 비교 대상입니다. LangChain은 출력 파서(output parsers)와 구조화된 출력(structured output) 지원 기능을 갖추고 있지만, 추상화 계층(abstraction layer)이 두껍습니다. 파싱 실패를 디버깅하려면 여러 내부 체인(chain) 객체를 추적해야 합니다. 팀 전체가 유지 관리해야 하는 런북의 경우, 이러한 불투명성은 결함이 됩니다.
instructor를 사용한 일반적인 요청(Plain requests) 방식은 이 방식과 더 유사하며, 솔직히 유효한 선택지입니다. 트레이드오프(tradeoff)는 pydantic-ai가 에이전트 수준의 재시도(retries) 및 도구(tool) 지원을 기본적으로 제공한다는 점이며, 이는 컨텍스트 검색(context retrieval)이나 다단계 추론(multi-step reasoning)을 추가하기 시작할 때 매우 중요합니다.
**OpenAI의 원시 구조화된 출력(Raw OpenAI structured outputs)**은 작동하지만, 특정 제공업체에 종속됩니다. pydantic-ai는 제공업체에 구애받지 않으므로(provider-agnostic), OpenAI에서 Anthropic 또는 로컬 모델로 교체하는 것은 코드 재작성이 아닌 설정 변경만으로 가능합니다.
이 시스템을 신뢰할 수 있게 만드는 핵심 설계 결정은 다음과 같습니다: 모든 에이전트가 문자열(string)이 아닌, Pydantic 모델인 result_type으로 정의된다는 점입니다. pydantic-ai는 출력이 검증(validation)에 실패할 경우 LLM 호출을 재시도(retry)합니다. 검증 피드백이 프롬프트에 다시 입력되어 자동 재시도가 이루어집니다. 이는 일반적인 프롬프트 엔지니어링(prompt engineering)만으로는 스스로 구현할 수 없는 기능입니다.
FastAPI 레이어는 입력 시 HTTP 수준의 검증을, 출력 시 직렬화(serialization)를 추가합니다. 모든 요청(request)과 응답(response)은 타입이 지정되어 있습니다. 프론트엔드, Slack 봇, 그리고 CI 파이프라인 모두가 동일한 계약(contract)을 바탕으로 통신합니다.
코드 패턴: 구조화된 출력을 가진 타입 지정 에이전트 (Typed Agents with Structured Outputs)
여기에 핵심 패턴이 있습니다. 런북의 모든 요소는 이 형태를 따릅니다.
from pydantic import BaseModel, Field
from pydantic_ai import Agent
from fastapi import FastAPI, HTTPException
...
각 부분의 역할과 중요성은 다음과 같습니다:
result_type=SWOTAnalysis는 결정적인 라인입니다. 이는 pydantic-ai에게 모델의 구조화된 출력(structured output) 모드를 사용하고, 응답을 사용자의 Pydantic 스키마(schema)에 따라 검증하도록 지시합니다. 만약 LLM이 잘못된 형식의 JSON을 반환하거나 필드가 누락된 경우, pydantic-ai는 자동으로 재시도합니다.
FastAPI 라우트의 response_model=SWOTAnalysis는 OpenAPI 문서가 실제 출력 타입으로부터 생성됨을 의미합니다. 프론트엔드 개발자들은 프롬프트를 읽지 않고도 어떤 필드가 반환되는지 정확히 확인할 수 있습니다.
result.data는 검증된 Pydantic 인스턴스를 직접 제공합니다. JSON 파싱이나 폴백(fallback)을 위한 .get() 호출이 필요 없습니다.
이 동일한 패턴이 런북의 모든 에이전트(코드 리뷰어, 소셜 포스트 생성기, 다중 형식 요약기, 의사결정 프레임워크)에 반복됩니다. 각 에이전트는 서로 다른 Pydantic 모델과 시스템 프롬프트(system prompt)를 가지지만, 구조적 형태는 동일합니다.
통합: 외부 소스 연결하기
런북이 외부 데이터 소스와 연결될 때 진정으로 유용해집니다. 대부분의 팀에게 가장 영향력 있는 통합은 Slack입니다.
데이터 흐름은 다음과 같습니다:
Slack 채널/스레드 (Slack channel/thread)
-> Slack API (conversations.history 또는 webhooks)
-> 런북(runbook)의 추출 엔드포인트 (extraction endpoint)
...
Slack 통합의 경우, slack_sdk를 사용하여 메시지 기록을 가져오고, 스레드를 하나의 컨텍스트 문자열 (context string)로 결합한 뒤, 해당 사용 사례에 적합한 에이전트 (agent)에게 전달합니다. 의사결정 스레드는 의사결정 프레임워크 에이전트 (decision framework agent)로 전달됩니다. 제품 토론 스레드는 SWOT 분석가 (SWOT analyser)로 전달됩니다. 채팅에서 공유된 코드 스니펫 (code snippets)은 코드 리뷰어 (code reviewer)로 전달됩니다.
from slack_sdk import WebClient
slack_client = WebClient(token=settings.slack_bot_token)
...
알아두어야 할 주의 사항(gotcha)이 하나 있습니다: Slack 메시지 텍스트에는 <@U12345> 형식의 사용자 ID 언급 (user ID mentions)이 포함되어 있습니다. 이를 그대로 두면 LLM (대규모 언어 모델)이 혼란을 겪을 수 있습니다. 에이전트에게 전달하기 전에 컨텍스트 문자열 (context string)을 전처리하여 사용자 ID를 표시 이름 (display names) 또는 일반적인 플레이스홀더 (placeholders)로 교체하십시오. 이는 users.info API 호출을 사용하거나 로컬 ID-to-name 캐시 (ID-to-name cache)를 유지함으로써 수행할 수 있습니다.
트레이드오프 (Tradeoffs) 및 한계점 (Limitations)
이 아키텍처에는 구축하기 전에 반드시 고려해야 할 실제 비용이 따릅니다.
지연 시간 (Latency). 모든 요청은 최소 한 번의 LLM API 호출을 발생시킵니다. 실시간 경로 (hot path)에 있는 코드 리뷰어의 경우, 최소 1~3초가 소요됩니다. 200ms 미만의 응답 시간이 필요한 작업에는 이 방식을 사용하지 마십시오.
재시도 비용 (Retry costs). 검증 실패 시 pydantic-ai가 수행하는 자동 재시도 (automatic retries) 기능으로 인해, 잘못 조정된 시스템 프롬프트 (system prompt)는 API 비용을 조용히 두 배로 늘릴 수 있습니다. 재시도율을 모니터링하고 max_retries를 명시적으로 설정하십시오.
소규모 팀에게는 과함 (Overkill for small teams). 엔지니어가 두 명이고 프롬프트가 세 개뿐이라면, 이름이 잘 지정된 함수와 타입 힌트 (type hints)를 갖춘 공유 Python 모듈이 아마도 정답일 것입니다. FastAPI 레이어는 여러 시스템이 동일한 에이전트를 소비할 때만 이점이 있는 배포 오버헤드 (deployment overhead)를 추가합니다.
공급자 종속 (Provider lock-in)은 연기될 뿐, 제거되지 않습니다. 공급자를 전환하는 것은 순수 OpenAI 호출을 사용하는 것보다 쉽지만, GPT-4o에 맞춰 조정된 시스템 프롬프트는 Claude 또는 Gemini에서 다르게 동작할 수 있습니다. 이식성 (portability)이 중요하다면 여전히 여러 공급자에 걸쳐 테스트를 수행해야 합니다.
이미 엄격한 문서화 습관을 갖춘 팀에게는 부가적인 가치가 낮을 수 있습니다. 이 런북(Runbook)은 현재 AI 프롬프트(Prompt)가 여기저기 흩어져 있고 출력이 일관되지 않을 때 가장 큰 가치를 발휘합니다.
코드를 확인하고 대화를 이어가세요
저는 이것을 GitHub에 오픈 소스 템플릿으로 패키징해 두었습니다: https://github.com/Reactance0083/pydantic-ai-prompt-engineering-runbook
이 스캐폴드(Scaffold)는 5개 에이전트(Agent) 모두에 대한 핵심 패턴과 FastAPI 설정을 제공합니다. 테스트, 에러 핸들링 (Error handling), 공급자 설정 (Provider configuration), 로깅 미들웨어 (Logging middleware) 및 배포 문서가 포함된 전체 프로덕션 버전을 원하신다면 여기서 확인하실 수 있습니다: https://reactance0083.gumroad.com/l/mdsbpc
만약 여러분이 유사한 것을 구축하면서, 특히 재시도 전략 (Retry strategies)이나 멀티 테넌트 프롬프트 격리 (Multi-tenant prompt isolation)와 관련하여 다른 트레이드오프 (Tradeoffs)에 직면했다면, 댓글을 통해 알려주시기 바랍니다. 이 아키텍처 (Architecture)에는 제가 여전히 해결해 나가고 있는 몇 가지 거친 부분들이 있으며, 실제 환경에서의 피드백은 로컬 테스트 (Local testing)에서 놓치기 쉬운 문제들을 드러내는 경향이 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기