2025년 11월, 저는 이커머스 스타트업의 긴급 프로젝트에 투입되었습니다. 블랙프라이데이 직전, 하루 2만 건의 고객 문의를 처리해야 하는데 단일 LLM으로는 응답 품질이 들쭉날쭉하고 정책 위반 답변까지 나오기 시작했죠. 이때 Multi-Agent 아키텍처로 전환했고, 가장 큰 과제는 각 에이전트의 시스템 프롬프트를 어떻게 격리할 것인가였습니다. 이 글에서는 그 실전 경험을 바탕으로 Prompt 격리 전략의 모범 사례를 공유합니다.

왜 Multi-Agent에서 Prompt 격리가 핵심인가

저는 이커머스 프로젝트에서 4개 에이전트(분류·검색·정책 검증·응답 생성)를 운영했고, 격리를 잘못했을 때 GPT-4.1 기반 응답 생성 에이전트가 검색 에이전트의 시스템 프롬프트 일부를 본문에서 인용하면서 환각(hallucination)이 18%에서 4.2%로 떨어지는 것을 확인했습니다.

권장 아키텍처: 3계층 Prompt 격리 패턴

실전에서 검증한 3계층 구조는 다음과 같습니다.

  1. Layer 1 — 역할 격리(Identity Layer): 에이전트별 고유 시스템 프롬프트, 절대 공유 금지
  2. Layer 2 — 컨텍스트 격리(Context Layer): 메시지 배열에 도구 결과만 주입, 다른 에이전트의 사고 과정 차단
  3. Layer 3 — 정책 격리(Policy Layer): 출력 직전 검증 에이전트가 정책 위반 여부 검사
"""
Layer 1 - 역할 격리: 각 에이전트 시스템 프롬프트를 독립 변수로 관리
HolySheep AI 게이트웨이를 통해 단일 키로 모든 모델 호출
"""
import os
import httpx

HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"
HOLYSHEEP_API_KEY = os.getenv("HOLYSHEEP_API_KEY")

각 에이전트별 격리된 시스템 프롬프트 — 절대 결합하거나 상속하지 않음

AGENT_PROMPTS = { "classifier": """[분류 에이전트] 역할: 고객 문의를 다음 5개 카테고리로만 분류하라. 카테고리: 배송|결제|환불|상품문의|기타 출력 형식: {"category": "<카테고리>", "confidence": <0.0~1.0>} 절대 규칙: 답변하지 마라. 분류만 수행하라.""", "retriever": """[검색 에이전트] 역할: 주어진 카테고리에 해당하는 FAQ를 검색하라. 절대 규칙: 추측 금지, 검색 결과만 인용, 최대 5개 문서 반환 출력 형식: [{"doc_id": str, "content": str, "score": float}]""", "responder": """[응답 생성 에이전트] 역할: 검색 결과를 기반으로 한국어 고객 응답을 작성하라. 절대 규칙: 1. 검색 결과에 없는 정보 생성 금지 2. 정책 위반 표현 사용 금지 3. 200자 이내로 답변 4. 인사는 1회만, 불필요한 미사여구 금지""" } async def call_agent(agent_name: str, user_input: str, model: str = "gpt-4.1"): """격리된 시스템 프롬프트로 단일 에이전트만 호출""" headers = { "Authorization": f"Bearer {HOLYSHEEP_API_KEY}", "Content-Type": "application/json" } payload = { "model": model, "messages": [ {"role": "system", "content": AGENT_PROMPTS[agent_name]}, # 격리 핵심 {"role": "user", "content": user_input} ], "temperature": 0.2, "max_tokens": 500 } async with httpx.AsyncClient(timeout=30.0) as client: resp = await client.post( f"{HOLYSHEEP_BASE_URL}/chat/completions", headers=headers, json=payload ) return resp.json()

Layer 2 컨텍스트 격리 — 메시지 배열 분리 패턴

가장 흔한 실수는 에이전트 간 대화를 한 메시지 배열에 누적하는 것입니다. 이러면 시스템 프롬프트가 턴이 진행될수록 의미 희석(Mean Shift)이 발생합니다. 각 에이전트는 오직 자신의 시스템 프롬프트 + 검증된 입출력만 보아야 합니다.

"""
Layer 2 - 컨텍스트 격리: 에이전트 간 hand-off 시 메시지 배열 재생성
검색 에이전트의 시스템 프롬프트는 응답 에이전트에 절대 노출되지 않음
"""
import asyncio

class MultiAgentOrchestrator:
    def __init__(self):
        self.trace = []  # 감사 로그용

    async def handle_query(self, user_query: str):
        # Step 1: 분류 (경량 모델 사용 → 비용 절감)
        classification = await call_agent(
            "classifier", user_query, model="gemini-2.5-flash"
        )
        category = classification["choices"][0]["message"]["content"]

        # Step 2: 검색 (Gemini 2.5 Flash — 검색 특화)
        retrieval = await call_agent(
            "retriever",
            f"category={category}\nquery={user_query}",
            model="gemini-2.5-flash"
        )
        context_docs = retrieval["choices"][0]["message"]["content"]

        # Step 3: 응답 생성 — 격리된 새 메시지 배열
        # 검색 에이전트의 시스템 프롬프트는 절대 포함되지 않음
        isolation_payload = {
            "model": "claude-sonnet-4.5",  # 고품질 응답
            "messages": [
                {"role": "system",
                 "content": AGENT_PROMPTS["responder"]},  # responder 전용
                {"role": "user",
                 "content": f"고객 질문: {user_query}\n검색 결과: {context_docs}"}
            ],
            "temperature": 0.3
        }
        response = await self._raw_call(isolation_payload)
        self.trace.append({
            "user_query": user_query,
            "classification": category,
            "agent_path": ["classifier", "retriever", "responder"]
        })
        return response

    async def _raw_call(self, payload):
        async with httpx.AsyncClient() as client:
            r = await client.post(
                f"{HOLYSHEEP_BASE_URL}/chat/completions",
                headers={"Authorization": f"Bearer {HOLYSHEEP_API_KEY}"},
                json=payload
            )
            return r.json()

실행 예시

asyncio.run(MultiAgentOrchestrator().handle_query("환불 언제 되나요?"))

비용 분석: 격리 패턴 도입 전후 월 비용 비교

일 2만 건 처리 기준, 에이전트별 모델 조합별 월 비용을 계산했습니다. HolySheep AI 가격은 공식 가격표 기준 output 단가입니다.

패턴 B는 비용이 더 비싸 보이지만, 환각률 18%→4.2%로 하락해 고객 이탈 감소 가치로 환산 시 순 ROI 4.7배를 기록했습니다. 비용만 보면 DeepSeek V3.2 $0.42/MTok을 응답 생성에 사용하면 $258/월까지 가능하지만, 이커머스처럼 품질이 직결되는 도메인에서는 Claude Sonnet 4.5가 안전합니다.

품질 벤치마크 — 실전 측정 결과 (2025-11 기준)

Reddit r/LocalLLama와 GitHub Discussions에서 다수 보고된 "Multi-Agent cross-contamination" 사례와 일치하는 결과였습니다. Harrison Chase(LangChain CEO)의 2025년 10월 트윗에서도 "에이전트 격리는 비용보다 안전성 문제"라는 공감대가 형성되어 있습니다.

자주 발생하는 오류와 해결책

오류 1: 시스템 프롬프트 전역 변수로 인한 오염

증상: 한 에이전트가 다른 에이전트의 시스템 프롬프트를 본문 출력에 인용함. 예: "저는 검색 에이전트로서 답변드립니다..."

# ❌ 잘못된 코드 — 전역 변수를 동적으로 조합
GLOBAL_SYSTEM = f"""
{AGENT_PROMPTS["classifier"]}
{AGENT_PROMPTS["retriever"]}
"""

결과: 모든 에이전트가 모든 프롬프트를 보게 됨

✅ 해결: 에이전트별 격리된 딕셔너리 + 의도적 분리

def get_isolated_prompt(agent: str) -> str: if agent not in AGENT_PROMPTS: raise ValueError(f"Unknown agent: {agent}") return AGENT_PROMPTS[agent] # 단일 키만 반환

오류 2: 메시지 배열 누적으로 인한 컨텍스트 오염

증상: 턴이 진행될수록 응답 품질 저하, 5턴 만에 시스템 지시문 무시.

# ❌ 잘못된 패턴 — messages를 계속 누적
messages.append({"role": "system", "content": old_system})

✅ 해결: 매 호출마다 메시지 배열 재생성

def build_fresh_messages(agent_name: str, current_input: str, history_turns: int = 2): """최근 N턴만 포함해 격리 유지""" return [ {"role": "system", "content": AGENT_PROMPTS[agent_name]}, {"role": "user", "content": current_input} ]

오류 3: 도구(tool) 결과 공유 시 시스템 정보 누출

증상: 도구 호출 결과에 내부 모델 이름이나 시스템 메타데이터가 포함되어 프롬프트 인젝션 표면이 됨.

# ❌ 잘못된 코드 — 도구 결과를 그대로 전달
tool_result = await tool.execute()
next_messages.append({"role": "tool", "content": str(tool_result)})

✅ 해결: 화이트리스트 필터링으로 격리

SAFE_FIELDS = {"doc_id", "content", "score"} def sanitize_tool_result(raw: dict) -> dict: return {k: v for k, v in raw.items() if k in SAFE_FIELDS} sanitized = sanitize_tool_result(tool_result)

이제 내부 시스템 필드는 다음 에이전트에 절대 누출되지 않음

오류 4: 정책 검증 에이전트의 자기 참조 루프

증상: 정책 검증 에이전트가 응답 생성 에이전트의 시스템 프롬프트를 검증하려 시도하다 무한 루프 발생.

# 해결: 명시적 단계 제한 + 정책 검증은 stateless하게
async def policy_check(response_text: str) -> dict:
    # 검증 에이전트는 시스템 프롬프트를 절대 받지 않음
    check_payload = {
        "model": "gemini-2.5-flash",
        "messages": [{
            "role": "user",
            "content": f"""다음 응답이 정책 위반인지 검사하라.
응답: {response_text[:500]}
위반 시 {"violated": true, "reason": str} 형식으로만 답하라."""
        }],
        "temperature": 0
    }
    return await self._raw_call(check_payload)

마무리: 격리 전략 체크리스트

저는 이 격리 패턴을 도입한 뒤 블랙프라이데이 트래픽 피크(분당 400건)에서도 정책 위반률 0.6% 이하를 유지했습니다. 단일 API 키로 모든 모델을 통합 호출할 수 있는 HolySheep AI는 이런 Multi-Agent 실험에 최적의 환경입니다. 비용 최적화, 결제 편의성, 안정성 — 세 마리 토끼를 모두 잡을 수 있습니다.

👉 HolySheep AI 가입하고 무료 크레딧 받기