Field note · 2026-04-24
프롬프트 최적화의 합의 구조
안전한 최적화는 합의의 구조를 이해하는 것에서 시작된다 APO(Automatic Prompt Optimization) 시스템은 단일 Reward Signal 기반 반복 최적화에서 구조적 허점이 생긴다. 평균 점수 개선은 라벨 불일치·분포 회귀·비용 초과 수렴이라는 세 실패 모드를 가린다. 본 문서는 NLP 앙상블의 합의
프롬프트 최적화의 합의 구조
안전한 최적화는 합의의 구조를 이해하는 것에서 시작된다
기술 요약
APO(Automatic Prompt Optimization) 시스템은 단일 Reward Signal 기반 반복 최적화에서 구조적 허점이 생긴다. 평균 점수 개선은 라벨 불일치·분포 회귀·비용 초과 수렴이라는 세 실패 모드를 가린다. 본 문서는 NLP 앙상블의 합의 구조를 APO에 적용하는 설계 원칙 세 가지—Signal Layering, Signal Conflict Detection, Cost-Benefit Tracking—를 제시하고, 강화된 Safety Gate와 다층적 Convergence Detection의 Python 구현을 기술한다. 참조 구현체는 DSPy, TextGrad, OPRO다.
0. 배경: 세 가지 숨은 가정
성능이 65%에서 100%로 올라갔다고 하자. 4라운드를 거쳐서, 매번 점수 개선이 보였고, 비용도 들었고, 최종 프롬프트를 배포했다.
그런데 이 여정에는 세 가지 숨은 가정이 있다.
가정 1: HITL 라벨이 일관되다. 5개 샘플에서 3개는 라벨이 일치한다. 나머지 2개는? 라벨링 기준이 흐려졌는가, 샘플 자체가 경계 케이스인가, 아니면 라벨 정의가 잘못되었는가?
가정 2: 개선이 진정한 수렴이다. 라운드 1→2: +10%, 2→3: +5%, 3→4: +1%. 지수 감소인가? 아니면 더 이상 개선 여지가 없다는 신호인가? 계속 반복할 가치가 있는가?
가정 3: 회귀가 없다. 평균 점수는 올라갔지만, 분포는? 70개 샘플은 아주 좋고, 30개는 여전히 형편없다면? 우리는 무엇을 얻은 것인가?
이 세 가정 중 하나라도 틀리면, 우리는 "좋아 보이는 최적화"를 배포하는 셈이다. 실제로는 구조적 문제를 덮은 것일 수 있다.
이 글에서 제시하는 방법론은 하나다: NLP 앙상블의 합의 구조를 프롬프트 최적화에 적용하라. 그러면 세 가정을 모두 검증할 수 있고, 실패 모드를 명확히 관측할 수 있고, 정말 안전한 최적화를 설계할 수 있다.
프롬프트 최적화의 위험은 점수 올리기에 있지 않다. 점수가 올라가는 과정을 제대로 보지 못하는 데 있다.
1. 기존 APO 프레임워크의 공통 한계
현재 주요 APO 프레임워크들은 최적화 루프 자체는 정교하게 설계하면서도, Reward Signal의 다층성을 명시적으로 다루지 않는다.
1.1 주요 프레임워크 비교
| 프레임워크 | 핵심 접근 | Reward Signal | 구조적 한계 |
|---|---|---|---|
| DSPy (Stanford NLP) | 컴파일된 프롬프트. Optimizer가 instruction + few-shot을 공동 최적화 | metric(example, prediction) → float 단일 함수 | 다수결·분포 충돌이 메트릭 내부에 숨음 |
| TextGrad (Zou Group) | 텍스트 역전파. loss.backward() → optimizer.step()으로 프롬프트 업데이트 | TextLoss (자연어 평가 기준) 단일 방향 gradient | 신호 간 충돌 감지 없음; worst-case 무시 |
| OPRO (Google DeepMind) | LLM이 meta-prompt로 이전 점수+솔루션을 보고 새 프롬프트 생성 | 스칼라 점수 하나 | 분포 정보 없음; worst-case 추적 불가 |
DSPy의 optimizer(BootstrapFewShot, MIPROv2, COPRO 등)는 metric 함수 하나를 기준으로 후보 프로그램을 평가한다:
import dspy
# DSPy — 단일 Reward Signal로 최적화
metric = dspy.evaluate.answer_exact_match
tp = dspy.MIPROv2(metric=metric, auto="light", num_threads=8)
optimized = tp.compile(program, trainset=trainset)
MIPROv2가 Bayesian Optimization으로 instruction × few-shot 조합을 탐색하는 동안, worst-case 샘플이나 라벨 불일치율은 추적 대상 밖에 있다.
TextGrad는 gradient를 텍스트로 확장하지만 신호 구조는 동일하다:
import textgrad as tg
model = tg.BlackboxLLM("gpt-4o")
prompt = tg.Variable("...", requires_grad=True, role_description="system prompt")
loss_fn = tg.TextLoss("답이 간결하고 정확한가?")
response = model(prompt)
loss = loss_fn(response)
loss.backward() # LLM이 텍스트 gradient 생성
optimizer.step() # 프롬프트 업데이트
TextLoss를 합산해 다중 신호를 구성할 수는 있지만, 충돌 감지나 worst-case 분석은 별도 구현이 필요하다.
1.2 공통 한계
세 프레임워크 모두 단일 스칼라 신호를 최적화 기준으로 사용한다. 이것이 이 문서가 다루는 구조적 맹점의 출발점이다.
2. 단일 신호의 맹점
현재의 최적화 방식이 세 가정을 검증하지 않는 이유는 명확하다.
단일 점수에 의존하기 때문이다.
최적화 팀은 보통 하나의 Reward Signal만 추적한다. HITL 정확도. 또는 confidence. 또는 ROUGE 점수. "더 높으면 더 좋다"는 믿음 아래에서. 그리고 이 신호가 올라가면 라운드를 통과시킨다.
이것이 가져오는 결과:
-
라벨 불일치를 보지 못한다. 평균 정확도 88%라는 숫자 뒤에서, 12개 샘플이 계속 라벨 불일치 상태로 남을 수 있다. 프롬프트가 좋아진 게 아니라 관측이 부족할 뿐이다.
-
회귀를 감추는 것을 모른다. 평균이 +3% 올라갔지만, 최악의 경우는 -15%일 수 있다. 큰 그룹의 약간의 개선이 작은 그룹의 심각한 악화를 덮는다.
-
수렴의 신호를 놓친다. 개선도가 지수 감소하는데도, "최적화가 계속 진행 중"이라고 믿는다. 실제로는 비용이 효과를 초과한 상황일 수 있다.
원칙이 없으면, 우리는 단일 지표의 노예가 된다. 그 지표의 맹점을 모르고, 실패 모드를 감지할 수 없고, 멈춰야 할 시점을 못 본다.
중요한 것은 신호가 올라가는가가 아니라, 어떤 신호가 올라가고, 어떤 신호는 떨어지는가를 보는 것이다.
3. Reward Signal의 분류 체계
[[2026-04-23_HITL은-두-번-설계되어야-한다_v4#1.1 앙상블이 있을 때 — 합의의 구조적 관측|HITL 아티클의 1.1 섹션]]은 이렇게 말한다:
어떤 출력은 투표로 모을 수 있고, 어떤 출력은 겹침을 봐야 하며, 어떤 출력은 품질 기준을 다시 세워야 한다.
프롬프트 최적화는 정확히 이 원칙이 필요한 영역이다. 프롬프트의 성능을 어떻게 평가하는가에 따라 합의의 의미가 달라지고, 그에 따라 볼 수 있는 신호와 놓치는 신호도 달라진다. **따라서 첫 번째 원칙은 Reward Signal의 형태를 명시적으로 정의하는 것이다
3.1 Reward Signal의 4가지 형태
| Reward Signal 형태 | 의미 | 집계 방식 | 대표 실패 모드 |
|---|---|---|---|
| HITL-Based (이진 신호) | 인간이 명시적으로 라벨한 정답 vs 오답 | 다수결 — agreement ratio | 라벨 불일치, boundary ambiguity |
| Proxy (Confidence-Based) | 모델의 자체 신뢰도 점수 | 가중 평균 | calibration 실패 — 자신감과 정답률의 gap |
| Semantic Similarity | ROUGE, BLEU, BERTScore 기반 의미론적 유사도 | 분포 기반 (중앙값, 상위 백분위수) | 의미 다양성 간과, 분산에 편차가 숨음 |
| Task-Specific | F1 (NER), Exact Match (QA), BLEU (번역) | 과제별 맞춤 집계 | 메트릭 편향, 길이 과적합 |
3.2 DSPy에서 다중 메트릭 병렬 평가
DSPy의 metric은 임의의 Python 함수다. 여러 신호를 병렬로 계산하되 합산 전에 방향을 기록해 충돌을 가시화할 수 있다:
import dspy
import logging
logger = logging.getLogger("apo.signals")
def hitl_metric(example, pred, trace=None) -> float:
"""이진 HITL 신호 — 인간 라벨과의 일치"""
return float(example.label == pred.label)
def confidence_metric(example, pred, trace=None) -> float:
"""Proxy 신호 — 모델 자체 신뢰도"""
return getattr(pred, "confidence", 0.5)
# DSPy 내장 의미론적 F1
semantic_f1 = dspy.SemanticF1()
def layered_metric(example, pred, trace=None) -> float:
h = hitl_metric(example, pred, trace)
c = confidence_metric(example, pred, trace)
s = float(semantic_f1(example, pred, trace))
# 합산 전 충돌 감지 — 이 로그가 최적화 루프에서 쌓인다
if abs(h - c) > 0.3:
logger.warning("signal_conflict hitl=%.2f confidence=%.2f", h, c)
return 0.5 * h + 0.3 * c + 0.2 * s
layered_metric은 신호 충돌을 로깅으로만 드러낼 뿐, 평균 합산이라는 점에서 여전히 충돌을 묻는다. 다음 섹션의 Signal Conflict Detection이 이를 명시적인 판단으로 끌어올린다.
3.3 각 형태에 따른 최적화의 의미
HITL-Based Reward가 주축일 때: 프롬프트 최적화는 "인간과의 합의를 최대화하기"라는 뜻이다. 라벨 불일치는 단순한 오류가 아니라 구조적 신호다.
- 라벨 불일치 패턴이 보인다면? 의도 분류에서 5개 샘플 중 2개는 A의도, 3개는 B의도로 분류된다면, 이건 모델 오류가 아니라 의도 경계 자체가 흐리다는 신호다.
- 라벨 불일치율을 라운드별로 추적해야 한다. 불일치율이 올라간다면 프롬프트가 더 애매해졌다는 뜻이다.
Proxy Reward (Confidence)가 주축일 때: 프롬프트 최적화는 "모델의 자신감을 보정하기"라는 뜻이다.
- 신뢰도가 올라가는데 정답률은 안 올라간다면? 프롬프트가 모델을 더 과신하게 만들었다는 뜻이다.
- ECE(Expected Calibration Error)를 라운드별로 추적해야 한다. 신뢰도와 정답률의 보정 오류가 줄어드는가 벌어지는가가 진짜 개선 여부를 판단한다.
Semantic Similarity가 주축일 때: 프롬프트 최적화는 "생성물의 일관성을 높이기"라는 뜻이다.
- 평균 ROUGE 점수는 올라갔는데 분산이 크다면? 어떤 입력에는 매우 좋은 프롬프트가, 어떤 입력에는 나쁜 프롬프트가 되었다는 뜻이다. 이는 과적합 신호다.
- 100개 샘플 중 90개는 잘 답하는데 10개는 계속 틀렸다면, 이 10개의 특성을 분석하는 게 다음 라운드의 시작점이다.
4. 실패 모드와 신호 충돌
HITL 아티클이 라벨 수집의 실패 모드를 다루었다면, 프롬프트 최적화는 최적화 과정 자체의 구조적 약점을 다뤄야 한다.
아래 패턴들은 단순한 오류가 아니라, 신호 해석의 원칙이 부재할 때 반복되는 문제다.
4.1 라운드 간 불안정성 (Round-to-Round Instability)
| 실패 모드 | 신호 | 진단 |
|---|---|---|
| Regression | N: 0.88, N+1: 0.78 (–10%) | 프롬프트 변경이 특정 샘플 그룹에 부정적 영향 |
| High Variance | 평균↑ but 표준편차 0.15 → 0.28 | 성능↑ but 일관성↓ — 과적합 신호 |
| Plateau | +3% → +1% → +0.3% (지수 감소) | 수렴 근접, 반복 수익 미미 |
| Label Drift | 불일치율 12% → 15% (원인 없이) | 프롬프트 자체가 더 애매해짐 |
아래 함수는 한 라운드의 점수 배열을 입력받아 위 지표들을 한 번에 계산한다:
import numpy as np
from dataclasses import dataclass
@dataclass
class RoundMetrics:
mean: float
std: float
worst_p10: float
deterioration_rate: float # baseline 대비 5% 이상 악화된 샘플 비율
improvement_rate: float # 전 라운드 대비 개선율 (없으면 0.0)
def compute_round_metrics(
scores: list[float],
baseline_mean: float,
prev_round_mean: float | None = None,
) -> RoundMetrics:
arr = np.array(scores)
return RoundMetrics(
mean=float(arr.mean()),
std=float(arr.std()),
worst_p10=float(np.percentile(arr, 10)),
deterioration_rate=float((arr < baseline_mean - 0.05).mean()),
improvement_rate=float(arr.mean() - prev_round_mean) if prev_round_mean else 0.0,
)
4.2 신호 간 충돌 (Directional Disagreement)
여러 신호가 다른 방향을 가리킬 때, 이를 명시적으로 보고하고 대응하라.
| 충돌 패턴 | 진단 | 대응 |
|---|---|---|
| HITL↑ BUT Confidence↓ | 정확도↑ but calibration 실패 | ECE 재점검 |
| Accuracy↑ BUT Variance↑↑ | 평균↑ but 일관성↓ | 과적합 케이스 분석 |
| Mean↑ BUT Worst-P10↓ | 큰 그룹 개선, 작은 그룹 심각 악화 | 트레이드오프 명시 |
| HITL↑ BUT Disagreement↑ | 자신감↑ but 인간 합의 역행 | 고위험 — HITL 기준 재검토 |
def detect_signal_conflicts(
hitl_delta: float,
confidence_delta: float,
disagreement_rate: float,
prev_disagreement_rate: float,
metrics: RoundMetrics,
baseline_mean: float,
) -> list[str]:
"""신호 충돌 패턴을 감지하고 진단 메시지 목록을 반환."""
conflicts = []
if hitl_delta > 0 and confidence_delta < -0.05:
conflicts.append("HITL↑ but Confidence↓ — calibration 점검 필요")
if metrics.improvement_rate > 0 and metrics.std > 0.20:
conflicts.append("Mean↑ but Variance↑↑ — 과적합 의심")
if metrics.mean > baseline_mean and metrics.worst_p10 < baseline_mean - 0.10:
conflicts.append("Mean↑ but Worst-P10↓ — 소수 샘플 심각 악화")
if hitl_delta > 0 and disagreement_rate > prev_disagreement_rate + 0.05:
conflicts.append("HITL↑ but Disagreement↑ — 고위험: 인간 합의 역행")
return conflicts
4.3 평균의 함정
APO Safety Gate는 보통 평균만 본다:
$$\text{delta} = \text{mean(optimized)} - \text{baseline_mean}$$
$$\text{PASSED} = \text{delta} \geq -0.05$$
Baseline 0.650, Optimized 평균 0.680(+3%)이어도 실제 분포가 아래와 같을 수 있다:
- 샘플 1~70: 0.95 (개선)
- 샘플 71~100: 0.10 (심각 악화)
평균으로는 +3% 개선이지만, 30%의 샘플을 망쳤다. Safety Gate는 이를 "PASSED"로 판정한다. 이것이 위험하다.
5. 설계 원칙: 세 가지
신호의 형태를 정의했고 실패 모드를 인식했다면, 이제 실제 최적화의 원칙을 세워야 한다. 이것이 세 가정을 깨는 방법이다.
Principle 1. 신호의 다층성 (Signal Layering)
단일 Reward Signal에 의존하지 말아라. 최소 2가지 이상의 신호를 병렬로 추적하라.
from dataclasses import dataclass
@dataclass
class MultiSignalResult:
hitl: float
confidence: float
disagreement_rate: float
semantic_f1: float
conflicts: list[str]
decision: str # "accept" | "investigate" | "reject"
def evaluate_round(
prev_metrics: RoundMetrics,
curr_metrics: RoundMetrics,
disagreement_rate: float,
prev_disagreement_rate: float,
baseline_mean: float,
) -> MultiSignalResult:
hitl_delta = curr_metrics.mean - prev_metrics.mean
conf_delta = curr_metrics.mean - prev_metrics.mean # placeholder
conflicts = detect_signal_conflicts(
hitl_delta=hitl_delta,
confidence_delta=conf_delta,
disagreement_rate=disagreement_rate,
prev_disagreement_rate=prev_disagreement_rate,
metrics=curr_metrics,
baseline_mean=baseline_mean,
)
# 모든 신호가 같은 방향이면 accept
all_improving = (
hitl_delta > 0
and disagreement_rate < prev_disagreement_rate
and curr_metrics.worst_p10 >= baseline_mean - 0.05
)
decision = (
"accept" if all_improving and not conflicts
else "investigate" if conflicts
else "reject"
)
return MultiSignalResult(
hitl=curr_metrics.mean,
confidence=conf_delta,
disagreement_rate=disagreement_rate,
semantic_f1=curr_metrics.mean,
conflicts=conflicts,
decision=decision,
)
단일 신호가 올라갔다고 해서 Accept하지 말아라. 신호들이 같은 방향을 가리키는지 확인하라.
Principle 2. 신호 충돌의 명시화 (Signal Conflict Detection)
여러 신호가 다른 방향을 가리킬 때, 이를 명시적으로 보고하고 대응하라.
위 4.2절의 detect_signal_conflicts가 이 원칙의 구현체다. conflicts 리스트가 비어 있으면 라운드를 안전하게 통과시키고, 충돌이 있으면 조사 단계로 넘긴다.
| 충돌 | 진단 | 대응 |
|---|---|---|
| HITL↑ BUT Confidence↓ | 정확도↑ but calibration 실패 | Calibration 재검토 |
| Accuracy↑ BUT Variance↑↑ | 평균↑ but 일관성 악화 | 과적합 케이스 분석 |
| Mean↑ BUT Worst-P10↓ | 큰 그룹 개선, 작은 그룹 악화 | 트레이드오프 명시 |
충돌을 무시하지 말아라. 그것이 구조적 문제를 알리는 신호다.
Principle 3. 비용-효율성의 추적 (Cost-Benefit Tracking)
각 라운드마다 개선의 경제성을 계산하라.
def compute_cost_efficiency(
improvement_rate: float, # 이번 라운드 개선율 (0.0~1.0)
tokens_used: int, # 이번 라운드 소비 토큰
threshold: float = 5_000.0, # tokens per 1% improvement 허용 상한
) -> dict:
"""
개선율이 0에 가까울 때 ZeroDivision 방지 — 1e-6 floor 사용.
efficiency > threshold이면 반복 중단 권고.
"""
efficiency = tokens_used / max(improvement_rate * 100, 1e-6)
return {
"tokens_per_pct_improvement": round(efficiency, 1),
"economically_viable": efficiency < threshold,
}
# 예시 — 라운드별 경제성 추이
round_a = compute_cost_efficiency(improvement_rate=0.0625, tokens_used=40_000)
# → tokens_per_pct_improvement = 640.0 (경제적)
round_b = compute_cost_efficiency(improvement_rate=0.003, tokens_used=40_000)
# → tokens_per_pct_improvement = 133_333.3 (비경제적 — 중단 권고)
이 수치가 비즈니스 threshold를 넘으면, 계속 반복할 가치가 없다는 신호다.
5.1 의사결정 배분과 HITL
| 단계 | 의사결정 주체 | 이유 |
|---|---|---|
| 1. Baseline 설정 | 인간 (먼저 검토) | 모든 개선의 척도. 잘못된 선택은 전체를 오염 |
| 2. 라운드 신호 수집 | AI (계산) | 점수와 신호 계산은 AI가 객관적 |
| 3. 신호 해석 | AI + 인간 (합동) | AI가 충돌 감지 → 인간이 구조적 원인 판단 |
| 4. Convergence 판정 | 인간 (reflective) | 수렴은 수치만이 아니라 비용-효과 기반 결정 |
| 5. 예외 처리 | 인간 (최종) | Safety Gate 실패 시 트레이드오프 가능 여부 판단 |
| 6. 배포 결정 | 인간 (권한) | 최적화 결과의 비즈니스 적합성 판단 |
5.2 강화된 Safety Gate
기본 Safety Gate는 이것만 본다:
$$\text{delta} = \text{mean(optimized)} - \text{baseline_mean}$$
$$\text{PASS if } \text{delta} \geq -0.05$$
이것만으로는 "평균 +3% / 하위 30% -15%"를 PASSED로 통과시킨다.
강화된 Safety Gate — 4가지 AND 조건:
$$\text{PASS} \iff \begin{cases} \Delta \geq -0.05 \ P_{10}(\text{optimized}) \geq \mu_{\text{baseline}} - 0.10 \ \text{deterioration_rate} \leq 0.05 \ r_{\text{disagreement}} \leq r_{\text{prev}} + 0.05 \end{cases}$$
import numpy as np
def enhanced_safety_gate(
optimized_scores: list[float],
baseline_mean: float,
hitl_disagreement_rate: float,
prev_disagreement_rate: float,
delta_threshold: float = -0.05,
worst_p10_margin: float = -0.10,
max_deterioration_rate: float = 0.05,
disagreement_slack: float = 0.05,
) -> tuple[bool, dict[str, bool]]:
scores = np.array(optimized_scores)
delta = float(scores.mean() - baseline_mean)
worst_p10 = float(np.percentile(scores, 10))
# baseline 대비 delta_threshold 이상 악화된 샘플 비율
deterioration_rate = float((scores < baseline_mean + delta_threshold).mean())
checks = {
"delta_ok": delta >= delta_threshold,
"worst_p10_ok": worst_p10 >= baseline_mean + worst_p10_margin,
"deterioration_ok": deterioration_rate <= max_deterioration_rate,
"disagreement_ok": hitl_disagreement_rate <= prev_disagreement_rate + disagreement_slack,
}
passed = all(checks.values())
return passed, checks
# 사용 예시
passed, checks = enhanced_safety_gate(
optimized_scores=[0.95] * 70 + [0.10] * 30,
baseline_mean=0.650,
hitl_disagreement_rate=0.14,
prev_disagreement_rate=0.12,
)
# passed = False
# checks = {
# "delta_ok": True, # 평균 0.695 > 0.600
# "worst_p10_ok": False, # P10 = 0.10 < 0.550
# "deterioration_ok": False, # 30% 샘플 악화
# "disagreement_ok": True,
# }
모든 조건을 통과할 때만 SAFE to deploy. 하나라도 실패하면 reflective review를 트리거한다.
5.3 Convergence의 다층적 감지
수렴을 감지할 때 세 단계를 본다:
| 수준 | 신호 | 의미 |
|---|---|---|
| 기술적 | 개선율 < 2% (최근 3라운드 연속) | 라운드 간 수익 미미 |
| 경제적 | tokens/pct > threshold | 계속 반복할 가치 없음 |
| 구조적 | 모든 신호의 분산이 안정화 | 더 이상 개선 불가능 |
def detect_convergence(
improvement_history: list[float], # 라운드별 개선율
cost_history: list[int], # 라운드별 소비 토큰
signal_std_history: list[float], # 라운드별 신호 표준편차
technical_threshold: float = 0.02,
economic_threshold: float = 5_000.0,
structural_std_threshold: float = 0.005,
window: int = 3,
) -> dict[str, bool]:
if len(improvement_history) < window:
return {"technical": False, "economic": False, "structural": False, "converged": False}
recent_imp = improvement_history[-window:]
recent_cost = cost_history[-window:]
recent_std = signal_std_history[-window:]
technical = all(r < technical_threshold for r in recent_imp)
economic = all(
c / max(r * 100, 1e-6) > economic_threshold
for c, r in zip(recent_cost, recent_imp)
)
structural = all(s < structural_std_threshold for s in recent_std)
return {
"technical": technical,
"economic": economic,
"structural": structural,
# 기술+경제 둘 다 수렴이면 중단 권고; 구조적은 참고 지표
"converged": technical and economic,
}
세 가지가 모두 수렴을 가리킬 때만, 최적화를 멈춰라.
6. 결론
프롬프트 최적화의 위험은 두 층위에서 나타난다.
첫 번째: 점수는 올라가는데, 실제로는 특정 샘플을 망쳤을 수 있다. 평균이 +3%이지만 30% 샘플이 -15%면, 그건 개선이 아니라 특정 그룹의 악화다.
두 번째: 점수가 올라가는 과정을 제대로 보지 못한다. 라벨 불일치가 올라가는데 정확도만 본다. 신뢰도와 정답률이 다른 방향을 가리키는데 평균만 본다.
따라서 안전한 최적화는:
- 신호를 다층화하고 (Signal Layering)
- 신호 간 충돌을 명시적으로 감지하며 (Signal Conflict Detection)
- 비용-효율을 추적하고 (Cost-Benefit Tracking)
- 의사결정의 책임을 명확히 하고 (Decision Allocation)
- 강화된 안전 게이트를 적용하고 (Enhanced Safety Gate)
- 수렴을 다층적으로 감지한다 (Multi-Level Convergence Detection)
점수를 올리는 것은 최적화가 아니다. 점수가 올라가는 구조를 이해하는 것이 최적화다.
참고 자료
프레임워크 구현체
- DSPy — Stanford NLP: 컴파일된 프롬프트 최적화 프레임워크.
BootstrapFewShot,MIPROv2,COPRO,SIMBA등 optimizer 제공 - DSPy Optimizers 문서: optimizer별 metric 설정 및 API 참조
- TextGrad — Zou Group: 텍스트 역전파(
TGD) 기반 프롬프트 최적화.Variable,BlackboxLLM,TextLossAPI - OPRO — Google DeepMind: LLM을 optimizer로 활용하는 meta-prompt 접근.
optimize_instructions.py가 핵심 루프
논문
- Large Language Models as Optimizers — Yang et al., 2023: OPRO 원논문. 이전 점수+솔루션을 meta-prompt에 누적해 LLM이 새 솔루션을 생성. GSM8K에서 인간 설계 프롬프트 대비 최대 8% 개선
- A Survey of Automatic Prompt Optimization — Cui et al., 2025: APO 방법론을 최적화 위치·대상·동인·생성 연산자·탐색 알고리즘 5차원으로 분류
- Intent-based Prompt Calibration — Levi et al., 2024: boundary case 합성 데이터를 통해 프롬프트를 사용자 의도에 보정. 제한된 주석 데이터만으로 기존 방법 능가
참고: 이 아티클은 [[2026-04-23_HITL은-두-번-설계되어야-한다_v4|HITL 아티클]]의 1.1절 "앙상블이 있을 때 — 합의의 구조적 관측"과 연계되며, [[APO-Ontology-Core]]와 [[APO-Ontology-Relationships]]의 정식 정의를 기반으로 합니다.