100 lines
3.1 KiB
Python
100 lines
3.1 KiB
Python
"""관리자 감정 관측용 Jev 감정 전이 trace 계약."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
|
|
|
|
CLIENT_AFFECT_DIMENSIONS = (
|
|
"anxiety",
|
|
"sadness",
|
|
"anger",
|
|
"shame",
|
|
"guilt",
|
|
"loneliness",
|
|
"relief",
|
|
"hope",
|
|
"trust",
|
|
)
|
|
|
|
ClientAffectDecision = Literal["accepted", "tentative", "held"]
|
|
|
|
|
|
class ClientAffectPolicyV1(BaseModel):
|
|
model_config = ConfigDict(extra="forbid", frozen=True, protected_namespaces=())
|
|
|
|
version: Literal["jev-affect-v1"]
|
|
min_confidence: float = Field(ge=0.0, le=1.0)
|
|
accepted_alpha: float = Field(ge=0.0, le=1.0)
|
|
accepted_cap: float = Field(ge=0.0, le=1.0)
|
|
tentative_alpha: float = Field(ge=0.0, le=1.0)
|
|
tentative_cap: float = Field(ge=0.0, le=1.0)
|
|
tentative_confidence_floor: float = Field(ge=0.0, le=1.0)
|
|
adjacent_probability_threshold: float = Field(ge=0.0, le=1.0)
|
|
|
|
|
|
class ClientAffectContextV1(BaseModel):
|
|
model_config = ConfigDict(extra="forbid", frozen=True, protected_namespaces=())
|
|
|
|
stage: str
|
|
resistance: float = Field(ge=0.0, le=1.0)
|
|
effective_openness: float = Field(ge=0.0, le=1.0)
|
|
rapport_credit: float = Field(ge=0.0)
|
|
|
|
|
|
class ClientAffectDimensionTraceV1(BaseModel):
|
|
model_config = ConfigDict(extra="forbid", frozen=True, protected_namespaces=())
|
|
|
|
key: str
|
|
before: float = Field(ge=0.0, le=1.0)
|
|
target: float | None = Field(default=None, ge=0.0, le=1.0)
|
|
after: float = Field(ge=0.0, le=1.0)
|
|
confidence: float | None = Field(default=None, ge=0.0, le=1.0)
|
|
probabilities: tuple[float, float, float, float, float] | None = None
|
|
decision: ClientAffectDecision
|
|
|
|
@model_validator(mode="after")
|
|
def require_probability_distribution(self) -> "ClientAffectDimensionTraceV1":
|
|
if self.probabilities is None:
|
|
return self
|
|
if any(
|
|
not math.isfinite(value) or value < 0.0 or value > 1.0
|
|
for value in self.probabilities
|
|
):
|
|
raise ValueError("probabilities must be finite values within 0..1")
|
|
return self
|
|
|
|
|
|
class ClientAffectTraceV1(BaseModel):
|
|
model_config = ConfigDict(extra="forbid", frozen=True, protected_namespaces=())
|
|
|
|
schema_version: Literal[1]
|
|
provider: str
|
|
model: str
|
|
latency_ms: int = Field(ge=0)
|
|
input_tokens: int = Field(ge=0)
|
|
output_tokens: int = Field(ge=0)
|
|
cost_usd: float | None = Field(default=None, ge=0.0)
|
|
turn_seq: int = Field(ge=1)
|
|
policy: ClientAffectPolicyV1
|
|
context: ClientAffectContextV1
|
|
dimensions: tuple[ClientAffectDimensionTraceV1, ...] = Field(min_length=9, max_length=9)
|
|
|
|
@model_validator(mode="after")
|
|
def require_fixed_dimension_order(self) -> "ClientAffectTraceV1":
|
|
if tuple(dimension.key for dimension in self.dimensions) != CLIENT_AFFECT_DIMENSIONS:
|
|
raise ValueError("dimensions must use the fixed client affect order")
|
|
return self
|
|
|
|
|
|
__all__ = [
|
|
"CLIENT_AFFECT_DIMENSIONS",
|
|
"ClientAffectContextV1",
|
|
"ClientAffectDecision",
|
|
"ClientAffectDimensionTraceV1",
|
|
"ClientAffectPolicyV1",
|
|
"ClientAffectTraceV1",
|
|
]
|