5857 words
29 minutes
[Agent]에이전트 엔지니어링: Improvement Loop
01. Overview
- 멀티에이전트 시스템에서 실패는 버그가 아니라 필연임.
- 아래 사이클은 강화학습의 Agent-Environment 상호작용과 동일한 구조를 따름
Agent → Action → Environment → Reward + New Observation → Agent (반복)3단계 Improvement Loop
┌─────────────┐ ┌─────────────────┐ ┌──────────────────┐│ Feedback │────▶│ Experimentation │────▶│ Continuous ││ Pipelines │ │ │ │ Learning ││ │ │ Shadow Deploy │ │ ││ 관찰/진단/ │ │ A/B Testing │ │ In-Context ││ 우선순위화 │◀────│ Bayesian Bandit │◀────│ Offline Retrain │└─────────────┘ └─────────────────┘ └──────────────────┘ ▲ │ └──────────────────────────────────────────────┘| 단계 | 역할 | 핵심 질문 |
|---|---|---|
| Feedback Pipelines | 관찰 · 진단 · 우선순위화 | 무엇이, 왜 실패했는가? |
| Experimentation | 통제된 환경에서 변경 검증 | 이 개선이 실제로 효과가 있는가? |
| Continuous Learning | 시스템에 개선 내재화 | 어떻게 지속적으로 반영할 것인가? |
Improvement Loop 시뮬레이션
from dataclasses import dataclass, fieldfrom enum import Enum
class Phase(Enum): FEEDBACK = "feedback" EXPERIMENT = "experiment" LEARN = "learn"
@dataclassclass ImprovementLoop: """멀티에이전트 시스템의 피드백 기반 개선 사이클""" cycle: int = 0 insights: list[str] = field(default_factory=list) validated: list[str] = field(default_factory=list)
def feedback(self, failures: list[str]) -> list[str]: """1단계: 실패 관찰 → 패턴 클러스터링 → 우선순위화""" # 실제로는 자동 RCA + HITL 리뷰가 여기서 수행됨 prioritized = sorted(failures, key=lambda f: failures.count(f), reverse=True) self.insights = list(dict.fromkeys(prioritized)) # 중복 제거, 빈도순 유지 return self.insights
def experiment(self, insight: str, improved: bool) -> bool: """2단계: Shadow deploy / A/B test로 개선안 검증""" if improved: self.validated.append(insight) return improved
def learn(self, insight: str, method: str = "in_context") -> str: """3단계: 검증된 개선을 시스템에 내재화""" self.cycle += 1 return f"[Cycle {self.cycle}] '{insight}' applied via {method}"
# --- 시뮬레이션 ---loop = ImprovementLoop()
# 1) Feedback: 반복 실패 패턴 수집raw_failures = [ "tool_selection_error", "prompt_ambiguity", "tool_selection_error", "tool_selection_error", # 빈도 높음 "timeout", "prompt_ambiguity",]insights = loop.feedback(raw_failures)print("우선순위화된 인사이트:", insights)# → ['tool_selection_error', 'prompt_ambiguity', 'timeout']
# 2) Experiment: 가장 빈도 높은 이슈 검증top_issue = insights[0]is_valid = loop.experiment(top_issue, improved=True)print(f"'{top_issue}' 개선 검증: {is_valid}")
# 3) Learn: 시스템 반영result = loop.learn(top_issue, method="prompt_refinement")print(result)# → [Cycle 1] 'tool_selection_error' applied via prompt_refinementKey Takeaways
- 기술 + 조직 문제: 엔지니어링, 데이터 사이언스, PM, UX 간 정렬 필요. 실패 자체도 학습의 input으로 보는 문화가 전제조건
- 순환 구조: 세 단계는 선형이 아니라 사이클. 학습 결과가 다시 피드백 파이프라인의 입력이 되는 구조
- Fine-tuning만이 답이 아님: 프롬프트 조정, 툴 리팩토링, in-context learning 등 비파라메트릭 접근이 선행되어야함
Concept
- Improvement Loop : 피드백 → 실험 → 학습의 반복 사이클. 에이전트 시스템이 실패로부터 자기 개선하는 구조
- Feedback Pipeline : 시스템 인터랙션 데이터를 수집·분석·우선순위화하여 액셔너블 인사이트를 도출하는 자동화 파이프라인
- Experimentation Framework : Shadow deploy, A/B test 등을 통해 개선안을 프로덕션 투입 전 통제된 환경에서 검증하는 체계
- Continuous Learning : In-context 조정(즉시) 또는 Offline retraining(주기적)으로 개선을 시스템에 내재화하는 메커니즘
- Reinforcement Learning 비유 : Agent가 Environment와 상호작용하며 Reward를 받아 행동을 개선하는 구조 — Improvement Loop의 개념적 기반
2. Feedback Pipelines
- 멀티에이전트 시스템은 매일 수천~수만 건의 인터랙션을 처리함
- 사람이 로그를 하나하나 뒤질 수 없으므로, 자동화된 파이프라인이 패턴을 탐지하고 클러스터링하여 액셔너블 인사이트로 변환하는 루프가 필요
자동 프롬프트 최적화 루프 (APO)
Initial Prompt → Target Model → Output ↓ Evaluation Model → Score ↓ Optimization Model → New Prompt (반복)이 루프의 핵심은 사람이 프롬프트를 수동으로 튜닝하는 대신, 데이터셋 기반 스코어링으로 자동 개선한다는 것
대표 프레임워크 3가지:
| 프레임워크 | 핵심 접근 | 특징 |
|---|---|---|
| DSPy | LM 파이프라인을 모듈러 프로그램으로 선언, 옵티마이저가 프롬프트/few-shot 자동 생성 | Signature → Module → Optimizer 구조 |
| Microsoft Trace | 그래디언트 없이 일반 피드백(점수, 자연어 비평, 쌍별 선호)으로 최적화 | Black-box 시스템에 적합 |
| APO | 평가 모델의 점수를 기반으로 최적화 모델이 새 프롬프트 제안 | 가장 범용적 루프 구조 |
DSPy 스타일 자동 프롬프트 최적화 시뮬레이션
from dataclasses import dataclass
@dataclassclass PromptCandidate: text: str score: float = 0.0
def evaluate_prompt(prompt: str, test_cases: list[dict]) -> float: """평가 모델 시뮬레이션: 프롬프트가 테스트 케이스에 얼마나 부합하는지 점수화""" score = 0.0 for case in test_cases: # 실제로는 LLM 호출 → 출력 → 메트릭 비교 if case["keyword"] in prompt: score += 1.0 return score / len(test_cases)
def optimize_prompt(current: str, feedback: str) -> str: """최적화 모델 시뮬레이션: 피드백 기반으로 프롬프트 수정 제안""" # 실제로는 LLM이 피드백을 보고 새 프롬프트를 생성 return f"{current}\nAdditional guidance: {feedback}"
# --- APO 루프 ---test_cases = [ {"input": "Suspicious IP login", "expected": "lookup threat intel", "keyword": "threat intel"}, {"input": "Malware hash detected", "expected": "query logs", "keyword": "logs"}, {"input": "Unusual outbound traffic", "expected": "triage incident", "keyword": "triage"},]
prompt = "You are a SOC analyst. Investigate security alerts."history: list[PromptCandidate] = []
for iteration in range(3): score = evaluate_prompt(prompt, test_cases) history.append(PromptCandidate(text=prompt, score=score)) print(f"[Iter {iteration}] Score: {score:.2f}")
if score >= 1.0: break
# 점수가 낮은 케이스에서 피드백 생성 missing = [c["keyword"] for c in test_cases if c["keyword"] not in prompt] feedback = f"Always consider: {', '.join(missing)}" prompt = optimize_prompt(prompt, feedback)
print(f"\n최종 프롬프트:\n{prompt}")print(f"최종 점수: {history[-1].score:.2f} → {evaluate_prompt(prompt, test_cases):.2f}")자동 이슈 탐지와 Root Cause Analysis (RCA)
수동 디버깅은 스케일하지 않는다. 자동 탐지는 다음 패턴을 감시
- 반복 실패: 특정 tool/skill에서 반복적으로 에러 발생
- 에러율 스파이크: 갑작스러운 오류 증가
- 사용자 만족도 이상: engagement 메트릭 하락
- 버전 간 행동 차이: 배포 환경별 divergent behavior
이슈가 탐지될 경우 RCA가 **“무엇이”가 아니라 “왜”**를 추적
Workflow Tracing → Fault Localization → Pattern Recognition → Impact Assessment(결정 체인 재구성) (문제 컴포넌트 격리) (일회성 vs 반복 패턴) (빈도 × 심각도)자동 이슈 탐지 + RCA 파이프라인
from collections import Counterfrom dataclasses import dataclassfrom enum import Enum
class Severity(Enum): LOW = 1 MEDIUM = 2 HIGH = 3
@dataclassclass FailureEvent: component: str # 어떤 tool/skill에서 발생 error_type: str # 에러 분류 trace_id: str # 워크플로우 추적용
@dataclassclass RCAResult: component: str frequency: int severity: Severity root_cause: str priority_score: float # frequency × severity
class AutomatedFeedbackPipeline: def __init__(self): self.events: list[FailureEvent] = []
def ingest(self, events: list[FailureEvent]): self.events.extend(events)
def detect_patterns(self) -> dict[str, int]: """규칙 기반 + 통계적 클러스터링으로 반복 패턴 탐지""" return Counter( (e.component, e.error_type) for e in self.events )
def run_rca(self, severity_map: dict[str, Severity]) -> list[RCAResult]: """탐지된 패턴에 대해 RCA 수행 → 우선순위화""" patterns = self.detect_patterns() results = [] for (component, error_type), freq in patterns.items(): sev = severity_map.get(error_type, Severity.LOW) results.append(RCAResult( component=component, frequency=freq, severity=sev, root_cause=f"{component}에서 {error_type} 반복 발생", priority_score=freq * sev.value, )) # 우선순위 높은 순 정렬 return sorted(results, key=lambda r: r.priority_score, reverse=True)
# --- 시뮬레이션 ---pipeline = AutomatedFeedbackPipeline()
# SOC 에이전트의 실패 이벤트 수집pipeline.ingest([ FailureEvent("query_logs", "invalid_syntax", "trace-001"), FailureEvent("query_logs", "invalid_syntax", "trace-002"), FailureEvent("query_logs", "invalid_syntax", "trace-003"), FailureEvent("lookup_threat_intel", "timeout", "trace-004"), FailureEvent("triage_incident", "wrong_classification", "trace-005"), FailureEvent("triage_incident", "wrong_classification", "trace-006"),])
severity_map = { "invalid_syntax": Severity.MEDIUM, "timeout": Severity.LOW, "wrong_classification": Severity.HIGH,}
rca_results = pipeline.run_rca(severity_map)for r in rca_results: print(f"[Priority {r.priority_score:.0f}] {r.root_cause} " f"(빈도:{r.frequency}, 심각도:{r.severity.name})")출력:
[Priority 6] triage_incident에서 wrong_classification 반복 발생 (빈도:2, 심각도:HIGH)[Priority 6] query_logs에서 invalid_syntax 반복 발생 (빈도:3, 심각도:MEDIUM)[Priority 1] lookup_threat_intel에서 timeout 반복 발생 (빈도:1, 심각도:LOW)Key Takeaway
- 자동 파이프라인의 역할: 패턴 탐지 + 클러스터링 + 인사이트 서피싱. 사람이 로그를 직접 보는 것을 대체
- APO 루프:
프롬프트 → 모델 출력 → 평가 → 점수 → 최적화 모델 → 새 프롬프트의 반복. DSPy가 이 구조의 대표 구현체. - RCA는 “왜”를 묻는다: Workflow tracing → Fault localization → Pattern recognition → Impact assessment 순서로 진행.
- 자동화의 한계: 맥락적 뉘앙스, 전략적 우선순위 판단은 사람이 해야 한다. 자동 파이프라인은 대체제가 아니라 증폭기(amplifier).
Concept
- DSPy : Stanford NLP 개발. LM 파이프라인을 선언적 프로그램으로 취급, Signature(입출력 명세) → Module(CoT, ReAct) → Optimizer(BootstrapFewshot, MIPROv2)로 자동 프롬프트 최적화
- DSPy Signature/Module/Optimizer 구조 : Signature가 타입 시스템 역할, Module이 추론 전략, Optimizer가 컴파일러 역할을 하는 3계층 아키텍처
- Microsoft Trace : 그래디언트 없이 점수/자연어 비평/쌍별 선호 등 일반 피드백으로 AI 시스템을 최적화하는 프레임워크. Black-box 시스템에 특히 유용
- APO (Automatic Prompt Optimization) : 평가 모델의 점수를 기반으로 최적화 모델이 새 프롬프트를 반복 제안하는 루프 구조
- Root Cause Analysis (RCA) : “무엇이 실패했나”가 아니라 “왜 실패했나”를 추적하는 체계적 분석. Workflow tracing → Fault localization → Pattern recognition → Impact assessment
- Drift : 사용자 쿼리 패턴, 외부 데이터, 위협 벡터 등이 시간에 따라 변화하면서 기존 프롬프트/모델의 성능이 저하되는 현상
- Fault Localization : RCA의 핵심 단계. 실패의 원인이 된 정확한 컴포넌트(프롬프트, 스킬 선택, 툴 파라미터 등)를 격리하는 과정
3. Human-in-the-Loop (HITL) Review
- 자동화 파이프라인은 패턴 탐지에는 강하지만, 맥락적 판단에는 약하다.
- 모호한 사용자 의도, 윤리적 뉘앙스, 상충하는 목표, 처음 보는 엣지 케이스는 사람의 직관과 도메인 전문성이 필요.
- HITL은 자동화의 안전망”이 아닌 일종의 구조화된 에스컬레이션 프로세스
HITL 워크플로우
Input Data → Agent → Generated Output Candidates ↓ Human Reviewer ←── Manual Feedback ↓ Human-Approved Output → End Users ↓ System Feedback (루프 백)핵심은 모든 케이스를 사람이 보는 것이 아니라, 에스컬레이션 기준에 따라 필터링된 케이스만 사람에게 라우팅하는 것
에스컬레이션 기준 설계: 두 축
| 축 | 측정 방법 | 예시 |
|---|---|---|
| 불확실성 (Low Certainty) | 모델 self-assessed confidence score, 엔트로피, 앙상블 분산 | confidence < 0.7이면 에스컬레이션 |
| 영향도 (High Consequence) | 도메인별 심각도, 영향 자산의 중요도 | 데이터 유출 가능성, admin 계정 관련 |
최종 에스컬레이션 결정은 두 축의 조합: risk_score = uncertainty × consequence
에스컬레이션 라우터
from dataclasses import dataclassfrom enum import Enum
class Decision(Enum): AUTO_APPROVE = "auto_approve" ESCALATE = "escalate"
@dataclassclass AgentOutput: incident_id: str response: str confidence: float # 0~1, 모델 자체 평가 severity: str # "low", "medium", "high", "critical" affects_critical_asset: bool
SEVERITY_WEIGHT = {"low": 1, "medium": 2, "high": 3, "critical": 4}
def compute_risk_score(output: AgentOutput) -> float: """uncertainty × consequence 기반 리스크 스코어""" uncertainty = 1.0 - output.confidence consequence = SEVERITY_WEIGHT.get(output.severity, 1) if output.affects_critical_asset: consequence *= 1.5 return uncertainty * consequence
def escalation_router(output: AgentOutput, threshold: float = 1.0) -> Decision: """리스크 스코어 기반 에스컬레이션 판단""" score = compute_risk_score(output) decision = Decision.ESCALATE if score > threshold else Decision.AUTO_APPROVE return decision
# --- 시뮬레이션 ---outputs = [ AgentOutput("INC-001", "False positive from VPN", confidence=0.92, severity="low", affects_critical_asset=False), AgentOutput("INC-002", "Possible data exfiltration", confidence=0.55, severity="critical", affects_critical_asset=True), AgentOutput("INC-003", "Suspicious login attempt", confidence=0.75, severity="medium", affects_critical_asset=False), AgentOutput("INC-004", "Malware hash detected on admin server", confidence=0.60, severity="high", affects_critical_asset=True),]
for o in outputs: score = compute_risk_score(o) decision = escalation_router(o) print(f"[{o.incident_id}] risk={score:.2f} → {decision.value}" f" (conf={o.confidence}, sev={o.severity})")출력:
[INC-001] risk=0.08 → auto_approve (conf=0.92, sev=low)[INC-002] risk=2.70 → escalate (conf=0.55, sev=critical)[INC-003] risk=0.50 → auto_approve (conf=0.75, sev=medium)[INC-004] risk=1.80 → escalate (conf=0.60, sev=high)다학제 리뷰 프로세스
에스컬레이션된 케이스는 단일 엔지니어가 아니라 다학제 팀이 분석한다:
| 역할 | 기여 |
|---|---|
| 엔지니어 | Trace 분석, 기술적 fault localization |
| 데이터 사이언티스트 | 패턴/엣지 케이스 인식, 통계적 분석 |
| PM | 사용자 니즈와의 정렬 여부 판단 |
| UX 리서처 | 자동 메트릭이 놓치는 사용자 마찰 포인트 발견 |
리뷰 프로세스 4단계:
Contextual Analysis → Trace Inspection → Impact Assessment → Resolution Design(통제 환경에서 재현) (로그/트레이스 검토) (범위·심각도 평가) (수정안 설계)리뷰 기록 및 지식 베이스 축적
from dataclasses import dataclass, fieldfrom datetime import datetime
@dataclassclass HITLReview: incident_id: str reviewer: str role: str # "engineer", "data_scientist", "pm", "ux" finding: str resolution: str reviewed_at: datetime = field(default_factory=datetime.now)
@dataclassclass HITLKnowledgeBase: """리뷰 결과를 축적하여 조직 학습에 활용""" reviews: list[HITLReview] = field(default_factory=list)
def add_review(self, review: HITLReview): self.reviews.append(review)
def find_similar(self, keyword: str) -> list[HITLReview]: """과거 유사 케이스 검색 → 반복 문제 식별""" return [r for r in self.reviews if keyword in r.finding]
def recurrence_report(self) -> dict[str, int]: """동일 resolution이 반복되면 시스템적 문제 시그널""" from collections import Counter return Counter(r.resolution for r in self.reviews)
# --- 시뮬레이션 ---kb = HITLKnowledgeBase()
kb.add_review(HITLReview( "INC-002", "Alice", "engineer", finding="triage_incident가 credential stuffing을 IP brute-force로 오분류", resolution="프롬프트에 credential stuffing 예시 추가",))kb.add_review(HITLReview( "INC-004", "Bob", "data_scientist", finding="isolate_host가 critical ops 서버를 확인 없이 격리", resolution="isolate_host에 confirmation step 추가",))kb.add_review(HITLReview( "INC-007", "Carol", "engineer", finding="새로운 공격 벡터(API key 탈취)를 기존 프롬프트가 커버 못함", resolution="프롬프트에 credential stuffing 예시 추가", # 같은 resolution 반복!))
# 반복 resolution 확인 → 시스템적 문제 시그널report = kb.recurrence_report()for resolution, count in report.most_common(): flag = "시스템적 문제!" if count > 1 else "" print(f"[{count}회] {resolution}{flag}")출력:
[2회] 프롬프트에 credential stuffing 예시 추가 시스템적 문제![1회] isolate_host에 confirmation step 추가에스컬레이션 비율 최적화
핵심 균형: 너무 많이 에스컬레이션하면 human fatigue, 너무 적으면 위험한 케이스를 놓친다.
- 실무 가이드라인: 전체 케이스의 ~10% 이하가 에스컬레이션되도록 threshold를 튜닝.
- DSPy 같은 프레임워크로 과거 데이터 기반 시뮬레이션으로 최적 threshold를 찾을 수 있다.
핵심 Takeaway
- HITL은 안전망이 아니라 구조화된 에스컬레이션: 모든 케이스가 아니라
uncertainty × consequence > threshold인 케이스만 라우팅 - 불확실성 측정 방법: self-assessed confidence, 엔트로피, 앙상블 분산(3~5회 추론 후 출력 divergence > 20%), 외부 critic 모델
- 리뷰 결과는 지식 베이스로 축적: 동일 resolution이 반복되면 시스템적 문제의 시그널. 이 데이터가 다시 피드백 파이프라인의 입력이 된다
- ~10% 에스컬레이션 비율 목표: human fatigue와 위험 누락 사이의 균형점
Concept
- Human-in-the-Loop (HITL) : 자동화 파이프라인이 처리하기 어려운 모호하거나 고위험 케이스를 사람에게 라우팅하는 구조화된 에스컬레이션 프로세스
- Escalation Router : uncertainty × consequence 기반 리스크 스코어로 에스컬레이션 여부를 판단하는 라우팅 로직
- Confidence Score : 모델이 자체 출력에 대해 평가하는 확신도 (0~1). 프롬프트에 지시하여 출력 끝에 포함시킬 수 있음
- Ensemble Variance : 동일 입력에 대해 3~5회 추론 후 출력 간 divergence를 측정하여 불확실성을 정량화하는 방법
- Risk Score :
(1 - confidence) × severity_weight로 계산. 불확실하면서 영향이 큰 케이스를 우선 에스컬레이션 - Multidisciplinary Review : 엔지니어, 데이터 사이언티스트, PM, UX가 각자의 관점에서 에스컬레이션 케이스를 분석하는 다학제 리뷰
- Organizational Learning : HITL 리뷰 결과를 지식 베이스로 축적하여 유사 문제 재발 방지, 신규 팀원 온보딩, 시스템 설계 개선에 활용
- Calibration (모델 보정) : 모델이 “confidence 0.8”이라고 말할 때 실제로 80% 확률로 맞는지 검증하는 기법. 잘 보정되지 않은 모델의 confidence score는 에스컬레이션 기준으로 신뢰하기 어렵다
4. Prompt & Tool Refinement
- 피드백 파이프라인과 HITL 리뷰가 **“무엇이, 왜 문제인지”**를 밝혀냈다면, 이제 실제로 고칠 차례.
- 에이전트 시스템에서 가장 직접적이고 효과가 큰 두 레버는 프롬프트(모델에 주는 지시)와 툴(모델이 호출하는 외부 함수/API)이다.
4-1. Prompt Refinement
피드백 루프에서 반복적으로 드러나는 프롬프트 문제 유형:
| 문제 | 증상 | 개선 방향 |
|---|---|---|
| 모호한 지시 | 일관성 없는 응답 | 명시적 포맷/제약 추가 |
| 너무 넓은 프롬프트 | 할루시네이션, 탈선 | 태스크 바운더리 좁히기 |
| 너무 좁은 프롬프트 | 실제 변동성에 대응 못함 | 예시 다양화, 컨텍스트 확장 |
| 에러 처리 부재 | 실패 시 멈추거나 엉뚱한 행동 | 에스컬레이션/폴백 지시 추가 |
개선 전략 4가지:
Rewriting for Clarity → 명확한 지시, 응답 포맷 명시Adding Exemplars → positive/negative 예시로 추론 앵커링Decomposing Tasks → 복잡한 멀티스텝을 순차적 서브프롬프트로 분리Context Expansion → 추가 배경/제약/도메인 지식 주입DSPy ReAct + MIPROv2로 프롬프트 자동 최적화
import dspy
# LM 설정dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
# 모의 툴 정의def lookup_threat_intel(indicator: str) -> str: """위협 인텔리전스 조회""" return f"Intel for {indicator}: potentially malicious"
def query_logs(query: str) -> str: """보안 로그 검색""" return f"Logs for '{query}': suspicious activity detected"
# 학습 데이터: alert → 기대 responsetrainset = [ dspy.Example( alert="Suspicious login from IP 203.0.113.45 to admin account.", response="Lookup threat intel for IP, query auth logs, triage as true positive." ).with_inputs('alert'), dspy.Example( alert="Unusual file download from example.com/malware.exe.", response="Lookup intel for URL and hash, query endpoint logs, isolate host." ).with_inputs('alert'), dspy.Example( alert="Multiple failed logins from new device.", response="Query auth logs, lookup device IP intel, triage if attack pattern." ).with_inputs('alert'),]
# ReAct 모듈: 추론 + 툴 호출을 자동으로 엮음react = dspy.ReAct("alert -> response", tools=[lookup_threat_intel, query_logs])
# MIPROv2 옵티마이저: 프롬프트/few-shot을 자동 생성optimizer = dspy.MIPROv2( metric=dspy.evaluate.answer_exact_match, auto="light", num_threads=24,)optimized_react = optimizer.compile(react, trainset=trainset)
# optimized_react는 내부 프롬프트가 자동 개선된 ReAct 모듈# → 기존 SOC 에이전트 워크플로우에 drop-in 교체 가능핵심 포인트: 사람이 프롬프트를 수동으로 튜닝하는 대신, 데이터셋 + 메트릭 기반으로 옵티마이저가 자동으로 더 나은 프롬프트를 생성한다. MIPROv2는 few-shot 예시 선택과 프롬프트 구조까지 자동 최적화한다.
4-2. Tool Refinement
프롬프트만으로는 부족한 경우가 많다. 에이전트가 호출하는 툴 자체의 내부 로직도 개선 대상이다.
피드백에서 드러나는 툴 문제 유형:
| 문제 | 예시 |
|---|---|
| 잘못된 툴 선택 | threat intel 조회가 필요한데 log query를 호출 |
| 파라미터 오류 | query_logs에 파싱 불가능한 SQL 전달 |
| 툴셋 갭 | 분류 기능이 없어서 triage 단계를 건너뜀 |
| 체이닝 실패 | 앞 툴의 출력 포맷이 뒤 툴의 입력과 불일치 |
툴 리파인먼트 3계층:
Refining Internal Logic → 툴 내부의 프롬프트/모델 최적화Expanding Capabilities → 새로운 시나리오 커버를 위한 기능 확장Integration Improvements → 툴 간 입출력 호환성 보장Python 예제: DSPy BootstrapFewshot로 Tool 내부 분류 로직 최적화
import dspy
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
# 위협 분류기의 Signature 정의class ThreatClassifier(dspy.Signature): """Classify threat level of an indicator as 'benign', 'suspicious', or 'malicious'.""" indicator: str = dspy.InputField(desc="IP, URL, or file hash") threat_level: str = dspy.OutputField(desc="benign, suspicious, or malicious")
# Chain of Thought으로 추론 과정을 거쳐 분류class ThreatClassificationModule(dspy.Module): def __init__(self): super().__init__() self.classify = dspy.ChainOfThought(ThreatClassifier)
def forward(self, indicator): return self.classify(indicator=indicator)
# 학습 데이터: indicator → 정답 threat_leveltrainset = [ dspy.Example(indicator="203.0.113.45", threat_level="suspicious").with_inputs('indicator'), dspy.Example(indicator="example.com/malware.exe", threat_level="malicious").with_inputs('indicator'), dspy.Example(indicator="benign-site.net", threat_level="benign").with_inputs('indicator'), dspy.Example(indicator="abc123def456", threat_level="malicious").with_inputs('indicator'), dspy.Example(indicator="192.168.1.1", threat_level="benign").with_inputs('indicator'), dspy.Example(indicator="obfuscated.url/with?params", threat_level="suspicious").with_inputs('indicator'),]
# 정확도 메트릭def threat_match(example, pred, trace=None): return example.threat_level.lower() == pred.threat_level.lower()
# BootstrapFewshot: 학습 데이터에서 few-shot 예시를 자동 선별하여 프롬프트에 삽입optimizer = dspy.BootstrapFewshotWithRandomSearch( metric=threat_match, max_bootstrapped_demos=4, # 자동 생성 예시 최대 수 max_labeled_demos=4, # 라벨된 예시 최대 수)optimized_classifier = optimizer.compile( ThreatClassificationModule(), trainset=trainset)
# 최적화된 분류기를 tool 함수에 통합def classify_threat(indicator: str) -> str: """최적화된 DSPy 모듈로 위협 수준 분류""" prediction = optimized_classifier(indicator=indicator) return prediction.threat_levelMIPROv2 vs BootstrapFewshot 비교:
| 옵티마이저 | 최적화 대상 | 적합한 상황 |
|---|---|---|
| MIPROv2 | 프롬프트 구조 + few-shot 예시 + 지시문 | 전체 에이전트 워크플로우의 프롬프트 최적화 |
| BootstrapFewshot | few-shot 예시 선별·생성 | 개별 툴/모듈 내부의 분류/추론 로직 최적화 |
리파인먼트 원칙
모든 프롬프트/툴 변경은 반드시:
- 문서화: 어떤 문제 → 어떤 변경 → 어떻게 효과 측정할 것인지
- 오프라인 검증: held-out 데이터 또는 합성 케이스로 테스트
- 라이브 검증: shadow deployment 또는 A/B test로 프로덕션 검증
- 모니터링: 사소한 프롬프트 수정도 시스템 전체에 파급 효과 가능
핵심 Takeaway
- 프롬프트는 사용자 의도와 에이전트 행동 사이의 브릿지: 미세한 워딩 변화가 추론, 툴 선택, 출력 품질에 큰 영향
- DSPy의 핵심 가치: 수동 trial-and-error 대신 데이터 기반 자동 최적화. “프롬프트 엔지니어링”을 “프롬프트 컴파일링”으로 전환
- 툴 리파인먼트는 3계층: 내부 로직 최적화, 기능 확장, 통합 개선. 프롬프트만 고치면 안 되고 툴도 함께 개선해야 한다
- 변경 → 문서화 → 오프라인 검증 → 라이브 검증 → 모니터링: 이 파이프라인을 건너뛰면 리그레션 위험
Concept
- Prompt Refinement : 피드백 분석 결과를 바탕으로 프롬프트의 워딩, 구조, 예시, 컨텍스트를 개선하는 과정. 명확화, 예시 추가, 태스크 분해, 컨텍스트 확장 4가지 전략
- Tool Refinement : 에이전트가 호출하는 외부 함수/API의 내부 로직, 파라미터 구조, 체이닝 호환성을 개선하는 과정. 3계층(내부 로직, 기능 확장, 통합 개선)
- DSPy ReAct Module : 추론(Chain of Thought)과 행동(Tool 호출)을 자동으로 엮는 모듈. 옵티마이저와 결합하면 추론-행동 프롬프트가 자동 최적화됨
- MIPROv2 : 프롬프트 구조, few-shot 예시, 지시문을 동시에 최적화하는 DSPy 옵티마이저. 전체 워크플로우 수준의 최적화에 적합
- BootstrapFewshotWithRandomSearch : 학습 데이터에서 효과적인 few-shot 예시를 자동 선별·생성하는 DSPy 옵티마이저. 개별 모듈/툴 수준의 최적화에 적합
- DSPy Signature : 태스크의 입출력을 선언적으로 정의하는 타입 명세.
indicator -> threat_level처럼 무엇을 받아 무엇을 내놓는지를 명시 - Exemplar Anchoring : 프롬프트에 positive/negative 예시를 포함하여 모델의 추론을 원하는 방향으로 고정(앵커링)하는 기법
- DSPy Assertions & Constraints : 모듈 출력에 런타임 제약을 걸어 hallucination이나 포맷 위반을 자동 차단하는 DSPy 기능
관련 문서
- [[agentic_design_pattern_01|[Agent]AI Agent 디자인패턴: 01. Agent개념의 이해와 병렬처리 패턴]]
tag overlap 1.00 - [[agentic_design_pattern_02|[Agent]AI Agent 디자인패턴: 핵심 워크플로우 관련 패턴들]]
tag overlap 1.00 - [[agentic_design_pattern_03|[Agent]AI Agent 디자인패턴: MCP와 A2A관련 패턴들]]
tag overlap 1.00 - [[agentic_design_pattern_04|[Agent]AI Agent 디자인패턴: 리소스 핸들링과 가드레일 패턴들]]
tag overlap 1.00 - [[agentic_design_pattern_05|[Agent]AI Agent 디자인패턴: 우선순위 전략패턴 및 Agent Frameworks 선택]]
tag overlap 1.00
[Agent]에이전트 엔지니어링: Improvement Loop
https://yjinheon.netlify.app/posts/04ml/agent/11_improvement_loop/