-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodels.py
More file actions
60 lines (49 loc) · 2.37 KB
/
Copy pathmodels.py
File metadata and controls
60 lines (49 loc) · 2.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from typing import Literal
from datetime import date
from pydantic import BaseModel, Field, field_validator
ClassificationType = Literal[
"zugzwang", "fork", "zwischenzug", "prophylaxis",
"gambit", "overextension", "tempo_loss"
]
CONFIDENCE_LEVELS = ("low", "medium", "high")
REASONING_MAX_LENGTH = 800
class Diagnosis(BaseModel):
"""Structured output contract for Gemini.
Field order here is deliberate and matters: Gemini's controlled JSON
generation fills fields in the order they're declared in the schema.
`reasoning` is declared FIRST so the model has to articulate its
analysis before committing to a label — a lightweight chain-of-thought
effect achieved purely through schema ordering, not extra prompting.
`classification` comes after reasoning; `confidence_bucket` comes last
since a confidence assessment naturally follows having already stated
both the reasoning and the label.
max_length was originally 500, raised to REASONING_MAX_LENGTH (800)
after real eval testing showed the more detailed FORK/ZUGZWANG
tie-breaker instructions (added to fix a separate classification bug)
made Gemini's typical reasoning length longer, causing repeated
ValidationErrors that survived all 3 retries — retrying didn't help
because temperature=0.3 produces consistently similar-length output
for the same input, so a failure like this repeats instead of clearing
up on its own. See gemini_client.py for the accompanying prompt
instruction to keep reasoning brief, and the truncation fallback that
now handles it locally instead of burning more API calls.
"""
reasoning: str = Field(max_length=REASONING_MAX_LENGTH)
classification: ClassificationType
confidence_bucket: Literal["low", "medium", "high"]
class EntryCreateRequest(BaseModel):
situation_text: str = Field(min_length=1, max_length=1000)
entry_date: date
@field_validator("situation_text")
@classmethod
def strip_and_check_not_blank(cls, v: str) -> str:
stripped = v.strip()
if not stripped:
raise ValueError("situation_text cannot be blank or whitespace-only")
return stripped
@field_validator("entry_date")
@classmethod
def reject_future_dates(cls, v: date) -> date:
if v > date.today():
raise ValueError("entry_date cannot be in the future")
return v