스킬 기반 프롬프트 아키텍처 실전 적용

모놀리식 프롬프트에서 플랫폼형 스킬 시스템까지의 진화 경로

실제 멀티 Agent 프로젝트(RAG Chatbot, Data Standardizer, Code Analyzer)에 스킬 기반 프롬프트 작성 원리를 적용하는 3단계 진화 전략을 정리한다. POC 단계의 모놀리식 프롬프트를 플랫폼형 스킬 시스템으로 발전시키는 구체적 방법론을 다룬다.

Agent
Architecture
Prompt Engineering
저자

Kwangmin Kim

공개

2026년 03월 12일

1 배경

멀티 Agent 프로젝트를 개발 중이다. 현재 POC 단계이며, 3개의 메인 Agent를 개발하고 있다.

  • QnA Chatbot: RAG 기반 도메인 Q&A
  • Data Standardizer: 용어 추출 및 한/영 논리명 생성
  • Code Analyzer: 코드 구조 분석 (개발 예정)
  • Code Standardizer: 코드 표준화 (추후 개발 예정)

최종 목표는 이 Agent들을 추상화하여, 특정 계약 아래에 Agent가 찍혀져 나오는 플랫폼으로 확장하는 것이다.

이 글에서는 스킬 역분석에서 추출한 8개 프롬프트 작성 원리를 실제 프로젝트에 적용하는 방법을 다룬다.

2 현재 프롬프트 관리 상태

2.1 프로젝트 구조 (프롬프트 관련)

src/agent/
├── prompts/
│   ├── chatbot_data_standardization_rules.yml   # 350줄 단일 파일
│   ├── data_standardizer.yml                    # 단일 파일
│   └── code_analyzer.yml                        # 비어있음
├── config/
│   └── settings.py                              # PromptConfig 포함
└── pages/
    ├── 1_QnA_Chatbot.py                         # load_prompt()로 YAML 로드
    ├── 2_Data_Standardizer.py
    └── 3_Code_Analyzer.py

2.2 현재 프롬프트 로딩 패턴

from langchain_core.prompts import load_prompt

PROMPT_FILE_PATH = os.path.join(
    os.path.dirname(__file__), "..", "prompts",
    "chatbot_data_standardization_rules.yml"
)
prompt = load_prompt(PROMPT_FILE_PATH)

2.3 현재 YAML 프롬프트 구조

_type: "prompt"
domain: "Data Standardization"
name: "chatbot_data_standardization_rules"

template: |
  ## Role
  당신은 PCR 분자진단 전문가입니다.

  ## Instructions
  (350줄에 걸친 모든 지시사항이 하나의 template 안에...)

  {chat_history}
  {context}
  {question}

input_variables: ["question", "context", "chat_history"]

핵심 문제: Agent당 YAML 파일 1개, 모든 지시사항이 하나의 template 필드에 들어있는 모놀리식 구조이다.

3 문제 진단: 8개 원리 대입

이전 글에서 추출한 8개 원리를 현재 프로젝트에 대입하면:

원리 현재 상태 문제
1. 트리거 기반 description 없음 Agent가 3개 → N개로 늘어나면 라우팅 불가
2. XML 의미론적 태그 없음 350줄 프롬프트에서 역할별 구분이 안 됨
3. WRONG → CORRECT 대조 부분적 (few-shot만) 흔한 실수 교정 패턴 없음
4. 의사결정 테이블 없음 분기 판단을 자연어로 서술
5. boundaries 없음 Agent 행동 범위 무제한
6. 프로그레시브 디스클로저 없음 모든 지시를 한번에 로드
7. 최소 완전 예시 few-shot 존재 하지만 과도하게 길음
8. 컨텍스트별 분기 없음 모든 상황에 동일 프롬프트

4 3단계 진화 전략

4.1 Phase 1: 현재 POC에 즉시 적용

파일 구조를 바꾸지 않고, 기존 YAML 프롬프트의 내용만 개선한다.

4.1.1 Before (현재)

template: |
  ## Role
  당신은 PCR 분자진단 전문가입니다.

  ## 답변 규칙
  - 문서에 기반하여 답변하세요
  - 인용을 포함하세요
  - 범위 밖 질문은 거절하세요
  (... 350줄 ...)

4.1.2 After (개선)

_type: "prompt"
domain: "Data Standardization"
name: "chatbot_data_standardization_rules"
description: "INVOKE when the user asks questions about PCR molecular
  diagnostics, data standardization rules, or terminology standards.
  Covers domain Q&A, citation-based answers, and standardization
  guideline lookup."

template: |
  <role>
  당신은 PCR 분자진단 데이터 표준화 전문가이다.
  </role>

  <response-format>
  모든 응답은 다음 구조를 따른다:
  1. Introduction: 질문 핵심 요약 (1-2문장)
  2. Body: 상세 답변 (근거 문서 인용 포함)
  3. Conclusion: 핵심 정리
  4. Related Questions: 후속 질문 2-3개
  </response-format>

  <citation-rules>
  | 상황 | 처리 |
  |------|------|
  | 문서에 근거가 있음 | [출처: 문서명, 페이지] 형식으로 인용 |
  | 문서에 근거가 없음 | "제공된 문서에서 관련 내용을 찾지 못했다" |
  | 부분적 근거 | 근거 있는 부분만 인용, 나머지는 명시 |
  </citation-rules>

  <fix-hallucination>
  # WRONG: 문서에 없는 내용을 생성
  "PCR 검사의 민감도는 일반적으로 95% 이상이다."

  # CORRECT: 문서 기반으로만 답변
  "제공된 문서에 따르면, [문서명]에서 PCR 검사의 민감도에 대해
  다음과 같이 기술하고 있다: [인용]"
  </fix-hallucination>

  <fix-out-of-scope>
  # WRONG: 범위 밖 질문에 일반 지식으로 답변
  "COVID-19 백신의 효과는..."

  # CORRECT: 범위 제한 명시
  "이 질문은 PCR 분자진단 데이터 표준화 범위에 포함되지 않는다.
  제공된 문서 내에서 관련 내용을 찾지 못했다."
  </fix-out-of-scope>

  <boundaries>
  ### 할 수 있는 것
  - 제공된 문서 기반 Q&A
  - 데이터 표준화 규칙 설명
  - 용어/코드 표준 안내

  ### 할 수 없는 것
  - 문서에 없는 의학적 판단
  - 환자 진단 관련 조언
  - 문서 외 출처의 정보 제공
  </boundaries>

  #Chat History:
  {chat_history}

  #Context:
  {context}

  #Question:
  {question}

  #Answer:

input_variables: ["question", "context", "chat_history"]

4.1.3 적용한 원리

변경 사항 적용 원리
description에 트리거 조건 추가 원리 1: 트리거 기반 description
<role>, <response-format> 등 태그 도입 원리 2: 의미론적 XML 태그
<fix-hallucination>, <fix-out-of-scope> 추가 원리 3: WRONG → CORRECT 대조
<citation-rules>에 테이블 사용 원리 4: 의사결정 테이블
<boundaries> 섹션 추가 원리 5: boundaries
힌트

Phase 1은 기존 YAML 파일의 template 내용만 수정하면 된다. 코드 변경 없이 프롬프트 품질을 올릴 수 있는 가장 빠른 방법이다.

4.2 Phase 2: Agent 3~4개 완성 시 — 스킬 분리

Agent가 늘어나면 공통 스킬과 Agent별 스킬을 분리한다.

4.2.1 디렉토리 구조

src/agent/prompts/
├── _base/                              # 공통 스킬 (모든 Agent가 공유)
│   ├── response-format.yml             # 응답 구조 규칙
│   ├── citation-rules.yml              # 인용 규칙
│   └── error-handling.yml              # 에러/범위 밖 처리
│
├── chatbot/                            # QnA Chatbot 전용
│   ├── _manifest.yml                   # 스킬 목록 + description (Level 0)
│   ├── domain-qa.yml                   # 도메인 Q&A 스킬
│   └── terminology-lookup.yml          # 용어 조회 스킬
│
├── data-standardizer/                  # Data Standardizer 전용
│   ├── _manifest.yml
│   ├── term-extraction.yml             # 용어 추출
│   ├── naming-rules-korean.yml         # 한국어 논리명 규칙
│   └── naming-rules-english.yml        # 영어 논리명 규칙
│
├── code-analyzer/                      # Code Analyzer 전용
│   ├── _manifest.yml
│   ├── structure-analysis.yml          # 코드 구조 분석
│   ├── docstring-generation.yml        # 독스트링 생성
│   └── quality-check.yml              # 코드 품질 검사
│
└── code-standardizer/                  # Code Standardizer 전용 (예정)
    ├── _manifest.yml
    ├── naming-convention.yml
    └── refactoring-patterns.yml

4.2.2 _manifest.yml (Level 0)

Agent의 스킬 목록과 트리거 조건을 정의한다. LangChain Skills의 AGENTS.md에 해당한다.

# src/agent/prompts/data-standardizer/_manifest.yml
agent: data-standardizer
description: "데이터 표준화 Agent. 용어 추출, 한/영 논리명 생성, 코드 매핑을 수행한다."

skills:
  - name: term-extraction
    description: "INVOKE when the user provides raw text, terms, or data
      fields that need to be identified and extracted for standardization."

  - name: naming-rules-korean
    description: "INVOKE when generating Korean logical names (한국어 논리명)
      for extracted terms. Covers naming conventions, abbreviation rules."

  - name: naming-rules-english
    description: "INVOKE when generating English logical names for extracted
      terms. Covers camelCase, snake_case, and domain-specific abbreviations."

base_skills:
  - response-format
  - citation-rules
  - error-handling

input_variables: ["question", "context"]

4.2.3 개별 스킬 파일 예시

# src/agent/prompts/data-standardizer/term-extraction.yml
name: term-extraction
description: "INVOKE when the user provides raw text, terms, or data fields
  that need to be identified and extracted for standardization."

template: |
  <overview>
  텍스트에서 표준화 대상 용어를 식별하고 추출한다. 추출된 용어에 대해
  후보 표준명과 설명을 함께 제공한다.
  </overview>

  <when-to-use>
  | 이 스킬을 사용할 때 | 다른 스킬을 사용할 때 |
  |---------------------|---------------------|
  | 원문 텍스트에서 용어를 추출할 때 | 이미 추출된 용어의 논리명을 생성할 때 |
  | 데이터 필드명을 식별할 때 | 코드에서 변수명을 분석할 때 |
  </when-to-use>

  <ex-term-extraction>
  입력: "환자의 Ct value가 35 이하이면 양성으로 판정한다"
  출력:
  | 추출 용어 | 유형 | 후보 표준명 |
  |-----------|------|-------------|
  | Ct value | 측정값 | cycle_threshold_value |
  | 양성 | 판정결과 | positive |
  </ex-term-extraction>

  <fix-over-extraction>
  # WRONG: 일반 단어까지 추출
  "환자" → 추출 대상 아님 (도메인 표준화 대상이 아닌 일반 명사)

  # CORRECT: 도메인 특화 용어만 추출
  "Ct value" → 추출 대상 (PCR 진단 도메인 전문 용어)
  </fix-over-extraction>

  <boundaries>
  ### 할 수 있는 것
  - 텍스트에서 도메인 전문 용어 식별
  - 용어 유형 분류 (측정값, 판정결과, 시약명 등)
  - 후보 표준명 제안

  ### 할 수 없는 것
  - 최종 표준명 확정 (naming-rules 스킬에서 처리)
  - 용어 간 관계 매핑
  </boundaries>

4.2.4 스킬 로더 구현

# src/agent/prompts/loader.py
import yaml
from pathlib import Path
from langchain_core.prompts import PromptTemplate

PROMPTS_DIR = Path(__file__).parent


def load_manifest(agent_name: str) -> dict:
    """Level 0: Agent의 스킬 목록을 로드한다."""
    manifest_path = PROMPTS_DIR / agent_name / "_manifest.yml"
    with open(manifest_path, encoding="utf-8") as f:
        return yaml.safe_load(f)


def load_skill(agent_name: str, skill_name: str) -> str:
    """Level 2: 특정 스킬의 전체 내용을 로드한다."""
    skill_path = PROMPTS_DIR / agent_name / f"{skill_name}.yml"
    with open(skill_path, encoding="utf-8") as f:
        return yaml.safe_load(f)["template"]


def load_base_skill(skill_name: str) -> str:
    """공통 스킬을 로드한다."""
    skill_path = PROMPTS_DIR / "_base" / f"{skill_name}.yml"
    with open(skill_path, encoding="utf-8") as f:
        return yaml.safe_load(f)["template"]


def compose_prompt(
    agent_name: str,
    skill_names: list[str],
) -> PromptTemplate:
    """선택된 스킬들을 조합하여 최종 프롬프트를 생성한다."""
    manifest = load_manifest(agent_name)

    # 공통 스킬 로드
    parts = []
    for base_skill in manifest.get("base_skills", []):
        parts.append(load_base_skill(base_skill))

    # Agent 전용 스킬 로드
    for skill_name in skill_names:
        parts.append(load_skill(agent_name, skill_name))

    template = "\n\n".join(parts)
    return PromptTemplate(
        template=template,
        input_variables=manifest.get(
            "input_variables", ["question", "context"]
        ),
    )

4.2.5 기존 코드에서의 사용

# pages/2_Data_Standardizer.py (변경 후)
from agent.prompts.loader import compose_prompt

# 필요한 스킬만 선택하여 프롬프트 조합
prompt = compose_prompt(
    agent_name="data-standardizer",
    skill_names=["term-extraction", "naming-rules-korean"],
)

chain = (
    RunnablePassthrough.assign(
        context=lambda x: retriever.invoke(x["question"]),
        question=lambda x: x["question"],
    )
    | prompt
    | llm
    | StrOutputParser()
)
노트

Phase 2의 핵심은 프롬프트를 기능 단위로 분리하여 재사용하는 것이다. _base/ 공통 스킬은 모든 Agent가 공유하므로, 응답 형식이나 인용 규칙을 한 곳에서 관리할 수 있다.

4.3 Phase 3: 플랫폼 확장 — 계약 기반 Agent 생성

Agent가 “찍혀져 나오는” 플랫폼에서는 스킬 레지스트리 + 계약(contract) 기반 조합이 필요하다.

4.3.1 플랫폼 디렉토리 구조

platform/
├── skill-registry/                    # 전체 스킬 저장소
│   ├── _base/                         # 공통 스킬
│   │   ├── response-format.yml
│   │   ├── citation-rules.yml
│   │   └── error-handling.yml
│   ├── domain/                        # 도메인별 스킬
│   │   ├── pcr-diagnostics/
│   │   │   ├── terminology.yml
│   │   │   └── test-codes.yml
│   │   ├── genomics/
│   │   └── clinical-data/
│   └── capability/                    # 기능별 스킬
│       ├── rag-qa/
│       ├── data-standardization/
│       │   ├── term-extraction.yml
│       │   ├── naming-rules-korean.yml
│       │   └── naming-rules-english.yml
│       ├── code-analysis/
│       └── code-standardization/
│
├── contracts/                         # 계약 정의
│   ├── client-a-pcr-standardizer.yml
│   ├── client-b-genomics-chatbot.yml
│   └── ...
│
└── agent-factory/                     # Agent 생성기
    └── factory.py

4.3.2 스킬 레지스트리의 설계 원칙

스킬을 도메인(domain)기능(capability)으로 분리한다.

도메인 스킬: "무엇에 대해 아는가"
  → pcr-diagnostics, genomics, clinical-data

기능 스킬: "무엇을 할 수 있는가"
  → rag-qa, data-standardization, code-analysis

이 분리가 중요한 이유:

조합 결과
pcr-diagnostics + rag-qa PCR 진단 Q&A 챗봇
pcr-diagnostics + data-standardization PCR 용어 표준화 Agent
genomics + rag-qa 유전체학 Q&A 챗봇
genomics + code-analysis 유전체학 코드 분석 Agent

도메인 x 기능 = Agent. 새로운 고객이 들어오면 도메인 스킬만 추가하고, 기존 기능 스킬을 재사용할 수 있다.

4.3.3 계약(Contract) 파일

계약 파일은 특정 고객의 Agent를 정의하는 명세서이다.

# contracts/client-a-pcr-standardizer.yml
contract:
  name: "Client A PCR Data Standardizer"
  client: "Client A"
  version: "1.0"

agent:
  type: data-standardizer

  base_skills:
    - _base/response-format
    - _base/citation-rules
    - _base/error-handling

  domain_skills:
    - domain/pcr-diagnostics/terminology
    - domain/pcr-diagnostics/test-codes

  capability_skills:
    - capability/data-standardization/term-extraction
    - capability/data-standardization/naming-rules-korean
    - capability/data-standardization/naming-rules-english

  boundaries:
    can:
      - "PCR 분자진단 관련 용어 표준화"
      - "한/영 논리명 생성"
      - "표준 코드 매핑"
    cannot:
      - "의학적 판단"
      - "환자 데이터 직접 처리"
      - "다른 도메인 용어 표준화"

  config:
    llm:
      model: gpt-4.1
      temperature: 0.3
    retrieval:
      k: 6
      search_type: hybrid

4.3.4 Agent Factory

# platform/agent-factory/factory.py
import yaml
from pathlib import Path
from langchain_core.prompts import PromptTemplate

REGISTRY_DIR = Path("platform/skill-registry")


def load_contract(contract_path: str) -> dict:
    with open(contract_path, encoding="utf-8") as f:
        return yaml.safe_load(f)


def load_skill_from_registry(skill_ref: str) -> str:
    """레지스트리에서 스킬을 로드한다."""
    skill_path = REGISTRY_DIR / f"{skill_ref}.yml"
    with open(skill_path, encoding="utf-8") as f:
        return yaml.safe_load(f)["template"]


def generate_boundaries_skill(boundaries: dict) -> str:
    """계약의 boundaries를 스킬 형식으로 변환한다."""
    can_items = "\n".join(f"- {item}" for item in boundaries["can"])
    cannot_items = "\n".join(f"- {item}" for item in boundaries["cannot"])
    return f"""<boundaries>
### 할 수 있는 것
{can_items}

### 할 수 없는 것
{cannot_items}
</boundaries>"""


def create_agent_from_contract(contract_path: str):
    """계약 파일로부터 Agent를 생성한다."""
    contract = load_contract(contract_path)
    agent_spec = contract["agent"]

    # 모든 스킬 참조를 수집
    all_skill_refs = (
        agent_spec.get("base_skills", [])
        + agent_spec.get("domain_skills", [])
        + agent_spec.get("capability_skills", [])
    )

    # 스킬 로드
    parts = [load_skill_from_registry(ref) for ref in all_skill_refs]

    # boundaries 추가
    if "boundaries" in agent_spec:
        parts.append(generate_boundaries_skill(agent_spec["boundaries"]))

    # 프롬프트 조합
    template = "\n\n".join(parts)
    prompt = PromptTemplate(
        template=template,
        input_variables=["question", "context"],
    )

    # LLM 설정
    llm_config = agent_spec.get("config", {}).get("llm", {})

    return {
        "prompt": prompt,
        "llm_config": llm_config,
        "retrieval_config": agent_spec.get("config", {}).get("retrieval", {}),
    }

4.3.5 사용 예시

# 계약 파일 하나로 Agent 생성
agent = create_agent_from_contract(
    "contracts/client-a-pcr-standardizer.yml"
)

# 새 고객 추가: 계약 파일만 작성
# contracts/client-b-genomics-chatbot.yml
# → 기존 스킬 재사용, 도메인 스킬만 추가

5 3단계 진화 요약

Phase 1 (POC)          Phase 2 (멀티 Agent)      Phase 3 (플랫폼)
┌──────────────┐      ┌──────────────────┐      ┌──────────────────────┐
│ 단일 YAML    │      │ _base/ (공통)    │      │ skill-registry/      │
│ 파일 내에    │  →   │ agent/ (전용)    │  →   │   _base/             │
│ 태그 구조화  │      │ _manifest.yml    │      │   domain/            │
│              │      │ loader.py        │      │   capability/        │
│              │      │                  │      │ contracts/           │
│              │      │                  │      │ agent-factory/       │
└──────────────┘      └──────────────────┘      └──────────────────────┘
 코드 변경 없음         스킬 분리 + 로더         도메인 x 기능 = Agent
 프롬프트만 개선         공통 스킬 재사용         계약 기반 자동 생성
Phase 시점 작업 효과
1 즉시 기존 YAML에 태그/테이블/boundaries 추가 할루시네이션 감소, 범위 제한
2 Agent 3~4개 완성 시 공통/전용 스킬 분리, 로더 구현 스킬 재사용, 일관된 품질
3 플랫폼 확장 시 스킬 레지스트리 + 계약 시스템 고객별 Agent 자동 생성
중요

각 Phase는 이전 Phase의 결과물을 포함한다. Phase 2에서 분리한 스킬 파일이 Phase 3의 레지스트리에 그대로 들어간다. 처음부터 Phase 3를 구축할 필요 없이, Phase 1부터 점진적으로 진화시키면 된다.

6 즉시 실행 가능한 액션

우선순위 작업 적용 원리 효과
1 기존 YAML에 <role>, <boundaries>, <fix-...> 태그 추가 원리 2, 3, 5 할루시네이션 감소, 범위 이탈 방지
2 description을 트리거 기반으로 변경 원리 1 추후 멀티 Agent 라우팅 준비
3 인용/범위 판단을 의사결정 테이블로 변환 원리 4 판단 정확도 향상
4 WRONG → CORRECT 대조 3~5개 추가 원리 3 가장 빈번한 실수 교정
5 few-shot 예시를 최소 완전 예시로 축소 원리 7 컨텍스트 윈도우 절약

1~5번은 기존 YAML 파일 수정만으로 즉시 적용 가능하다.

7 참고

Subscribe

Enjoy this blog? Get notified of new posts by email: