feat(eval): 평가 AI fast-loop에 골든셋 few-shot 주입 — 합성 데이터 실활용
- evaluator._load_fewshot_examples/_fewshot_block: data/golden 골든셋에서 기법 다양성 커버하는 상담자 발화 6개를 few-shot 예시로 로드(GOLDEN_DIR env, 없으면 graceful 빈 블록). - build_fast_messages 프롬프트에 주입 → 평가 라벨 일관성 보정. - 윤찬 6결정 '재귀학습 few-shot부터' 실제 구현, P4-P7 골든셋 실활용. - 검증: 9기법 커버(empathy/reflection/validation/holding/exploration/ facilitative_question/interpretation/normalization/principle_explanation), app.main import OK.
This commit is contained in:
parent
4187936848
commit
c953aa5bea
1 changed files with 60 additions and 2 deletions
|
|
@ -25,6 +25,7 @@ MASTERPLAN §2.3 (평가 AI 2-tier 루프):
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
|
@ -300,6 +301,62 @@ def _client_state_candidates_block() -> str:
|
|||
return f"[내담자 상태 후보 — code:라벨]\n- {items}"
|
||||
|
||||
|
||||
# ─ few-shot 골든셋 예시(data/golden) — 발화→기법 라벨 일관성 보정 ────────────
|
||||
# 윤찬 6결정 '재귀학습 few-shot부터' + taxonomy '0615 합성변형' 설계의 실제 활용.
|
||||
# 컨테이너/배포에선 GOLDEN_DIR env 로 마운트 경로 지정. 없으면 graceful(빈 블록).
|
||||
_GOLDEN_DIR = os.environ.get("GOLDEN_DIR") or os.path.join(
|
||||
os.path.dirname(__file__), "..", "..", "..", "..", "data", "golden"
|
||||
)
|
||||
|
||||
|
||||
def _load_fewshot_examples(max_n: int = 6) -> list[dict[str, Any]]:
|
||||
"""골든셋에서 기법 다양성을 커버하는 상담자 발화 few-shot 예시(없으면 빈 리스트)."""
|
||||
out: list[dict[str, Any]] = []
|
||||
seen: set[str] = set()
|
||||
try:
|
||||
files = sorted(f for f in os.listdir(_GOLDEN_DIR) if f.endswith(".jsonl"))
|
||||
except OSError:
|
||||
return out
|
||||
for fn in files:
|
||||
try:
|
||||
with open(os.path.join(_GOLDEN_DIR, fn), encoding="utf-8") as fh:
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
d = json.loads(line)
|
||||
if d.get("speaker") != "counselor" or not d.get("techniques"):
|
||||
continue
|
||||
if set(d["techniques"]) - seen: # 새 기법 커버 예시 우선
|
||||
out.append(d)
|
||||
seen.update(d["techniques"])
|
||||
if len(out) >= max_n:
|
||||
return out
|
||||
except (OSError, json.JSONDecodeError):
|
||||
continue
|
||||
return out
|
||||
|
||||
|
||||
def _fewshot_block() -> str:
|
||||
"""발화→기법 few-shot 예시 블록(평가 라벨 일관성 보정). 골든셋 없으면 빈 문자열."""
|
||||
ex = _load_fewshot_examples()
|
||||
if not ex:
|
||||
return ""
|
||||
lines = ["[few-shot 예시 — 발화 → 기법 code (골든셋 참고, 라벨 일관성 보정용)]"]
|
||||
for e in ex:
|
||||
techs = ",".join(e.get("techniques", []))
|
||||
text = (e.get("text") or "").replace("\n", " ")[:70]
|
||||
rat = next(
|
||||
(c.get("text", "") for c in e.get("comments", []) if c.get("kind") == "rationale"),
|
||||
"",
|
||||
)
|
||||
line = f'- "{text}…" → {techs}'
|
||||
if rat:
|
||||
line += f" (근거: {rat[:48]}…)"
|
||||
lines.append(line)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _theory_mode(ctx: "TurnContext") -> Optional[str]:
|
||||
"""페르소나 theory_target 에서 이론 모드 힌트(이론부합 평가용). 없으면 None."""
|
||||
tt = getattr(ctx.persona, "theory_target", None)
|
||||
|
|
@ -325,10 +382,11 @@ def build_fast_messages(ctx: "TurnContext", client_reply: str) -> list[EngineMes
|
|||
)
|
||||
|
||||
system = "\n\n".join(
|
||||
[
|
||||
p for p in [
|
||||
_EVAL_ROLE,
|
||||
_technique_candidates_block(),
|
||||
_client_state_candidates_block(),
|
||||
_fewshot_block(), # 골든셋 few-shot(없으면 빈 문자열 → 필터됨)
|
||||
(
|
||||
"[평가 4차원]\n"
|
||||
"① technique: 이번 *상담자(학습자)* 발화에 부착되는 기법(복수 가능, 후보 code 만).\n"
|
||||
|
|
@ -338,7 +396,7 @@ def build_fast_messages(ctx: "TurnContext", client_reply: str) -> list[EngineMes
|
|||
"없으면 null. 이 항목은 가장 중요하다 — 무리한 생성 금지, 진짜 이탈만.\n"
|
||||
"추가로 rapport_signal(−1~+1): 이 발화가 라포에 끼친 방향(공감·반영=+, 조언점프·평가=−)."
|
||||
),
|
||||
]
|
||||
] if p
|
||||
)
|
||||
|
||||
user = (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue