런타임 계약과 학습자 흐름 보강
This commit is contained in:
parent
f456b8997a
commit
206018b088
56 changed files with 4306 additions and 1008 deletions
|
|
@ -34,12 +34,15 @@ from typing import TYPE_CHECKING, Any, Optional
|
|||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..config import settings
|
||||
from ..contracts.engine_gateway import (
|
||||
ENGINE_GATEWAY_DEFAULT_MODEL_SENTINEL,
|
||||
structured_payload_from_response,
|
||||
)
|
||||
from ..engine_client import (
|
||||
EngineClient,
|
||||
EngineError,
|
||||
EngineMessage,
|
||||
GenerateRequest,
|
||||
GenerateResponse,
|
||||
)
|
||||
from ..taxonomy import (
|
||||
CLIENT_STATE_KO,
|
||||
|
|
@ -121,7 +124,7 @@ def _evaluator_cache_key(req: GenerateRequest) -> str:
|
|||
"version": _EVALUATOR_CACHE_VERSION,
|
||||
"ai_role": req.ai_role,
|
||||
"messages": [m.model_dump() for m in req.messages],
|
||||
"model": req.model or "gateway-default",
|
||||
"model": req.model or ENGINE_GATEWAY_DEFAULT_MODEL_SENTINEL,
|
||||
"max_tokens": req.max_tokens,
|
||||
"temperature": req.temperature,
|
||||
"structured_schema": req.structured_schema,
|
||||
|
|
@ -480,7 +483,7 @@ def build_fast_messages(ctx: "TurnContext", client_reply: str) -> list[EngineMes
|
|||
client_reply_masked = guardrail.mask_pii(client_reply).text_masked
|
||||
recent = "\n".join(
|
||||
f"{('상담자' if t.get('speaker') == 'counselor' else '내담자')}: {t.get('text', '')}"
|
||||
for t in (ctx.recent_turns or [])[-4:]
|
||||
for t in (ctx.memory.recent_turns or [])[-4:]
|
||||
) or "(직전 맥락 없음)"
|
||||
|
||||
crisis_note = ""
|
||||
|
|
@ -572,36 +575,6 @@ def build_deep_messages(
|
|||
]
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
# 4. 응답 파싱 — structured 우선, 없으면 text(JSON) 폴백, 실패는 빈 결과
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
def _structured_payload(resp: GenerateResponse) -> Optional[dict[str, Any]]:
|
||||
"""게이트웨이 structured 우선, 없으면 text 에서 JSON 추출(코드펜스/잡텍스트 관용)."""
|
||||
if resp.structured is not None and isinstance(resp.structured, dict):
|
||||
return resp.structured
|
||||
raw = (resp.text or "").strip()
|
||||
if not raw:
|
||||
return None
|
||||
# ```json ... ``` 펜스 제거
|
||||
if raw.startswith("```"):
|
||||
raw = raw.split("```", 2)[1] if raw.count("```") >= 2 else raw.strip("`")
|
||||
if raw.lstrip().lower().startswith("json"):
|
||||
raw = raw.lstrip()[4:]
|
||||
try:
|
||||
obj = json.loads(raw)
|
||||
return obj if isinstance(obj, dict) else None
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
# 본문 안에 묻힌 첫 객체만 시도
|
||||
start, end = raw.find("{"), raw.rfind("}")
|
||||
if 0 <= start < end:
|
||||
try:
|
||||
obj = json.loads(raw[start : end + 1])
|
||||
return obj if isinstance(obj, dict) else None
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def _parse_intent_deviation(d: Any) -> Optional[IntentDeviation]:
|
||||
if not isinstance(d, dict):
|
||||
return None
|
||||
|
|
@ -770,7 +743,7 @@ async def evaluate_turn(
|
|||
base.error = f"eval_error: {e}"
|
||||
return base
|
||||
|
||||
payload = _structured_payload(resp)
|
||||
payload = structured_payload_from_response(resp)
|
||||
if payload is None:
|
||||
base.error = "no_structured_output"
|
||||
return base
|
||||
|
|
@ -853,7 +826,7 @@ async def evaluate_session(
|
|||
base.error = f"eval_error: {e}"
|
||||
return base
|
||||
|
||||
payload = _structured_payload(resp)
|
||||
payload = structured_payload_from_response(resp)
|
||||
if payload is None:
|
||||
base.error = "no_structured_output"
|
||||
return base
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue