fix(products): work around Gemini int-enum schema rejection in parse-text

Gemini API rejects int-valued enums (StrengthLevel) in response_schema,
raising a validation error before any request is sent. Fix by introducing
AIActiveIngredient (inherits ActiveIngredient, overrides strength_level and
irritation_potential as Optional[int]) and ProductParseLLMResponse used only
as the Gemini schema. The two-step validation converts ints back to StrengthLevel
via Pydantic coercion. Adds a test covering the numeric strength level path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Piotr Oleszczyk 2026-03-01 22:00:48 +01:00
parent 921fe3ef61
commit 914c6087bd
2 changed files with 35 additions and 2 deletions

View file

@ -143,6 +143,19 @@ class ProductParseResponse(SQLModel):
needle_length_mm: Optional[float] = None
class AIActiveIngredient(ActiveIngredient):
# Gemini API rejects int-enum values in response_schema; override with plain int.
strength_level: Optional[int] = None
irritation_potential: Optional[int] = None
class ProductParseLLMResponse(ProductParseResponse):
# Gemini response schema currently requires enum values to be strings.
# Strength fields are numeric in our domain (1-3), so keep them as ints here
# and convert via ProductParseResponse validation afterward.
actives: Optional[list[AIActiveIngredient]] = None
class InventoryCreate(SQLModel):
is_opened: bool = False
opened_at: Optional[date] = None
@ -373,7 +386,7 @@ def parse_product_text(data: ProductParseRequest) -> ProductParseResponse:
config=genai_types.GenerateContentConfig(
system_instruction=_product_parse_system_prompt(),
response_mime_type="application/json",
response_schema=ProductParseResponse,
response_schema=ProductParseLLMResponse,
max_output_tokens=16384,
temperature=0.0,
),
@ -387,7 +400,8 @@ def parse_product_text(data: ProductParseRequest) -> ProductParseResponse:
except json.JSONDecodeError as e:
raise HTTPException(status_code=502, detail=f"LLM returned invalid JSON: {e}")
try:
return ProductParseResponse.model_validate(parsed)
llm_parsed = ProductParseLLMResponse.model_validate(parsed)
return ProductParseResponse.model_validate(llm_parsed.model_dump())
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors())