fix(deriver): coerce bare-string explicit observations from json_object providers#900
Conversation
…ct providers
`minimal_deriver_prompt()` examples and the `PromptRepresentation.explicit`
field description both show explicit facts as bare strings, but the field type
is `list[ExplicitObservationBase]` (objects with a `content` key). Providers
without `json_schema` support (DeepSeek via LiteLLM, some vLLM configs) run in
`json_object` mode and infer shape from that text, so they emit
`{"explicit": ["fact", ...]}`. That fails `PromptRepresentation` validation and,
via the structured-output fallback, is silently turned into `explicit=[]` —
zero observations with no error (plastic-labs#893).
Extend the existing `explicit` before-validator to coerce bare strings into
`{"content": ...}` (dicts/objects pass through unchanged; non-string scalars
still raise). Fix the misleading field-description example so the injected
json_object schema no longer advertises the wrong shape. Add pure
model-validation tests (no DB) covering bare strings, objects, mixed input,
None, and the non-string reject path.
Closes plastic-labs#893
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
Walkthrough
ChangesExplicit observation normalization
Estimated code review effort: 2 (Simple) | ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Linked Issue
Closes #893
Description
minimal_deriver_prompt()'s examples and thePromptRepresentation.explicitfield description both present explicit facts as bare strings, but the field type islist[ExplicitObservationBase](objects with acontentkey). Providers withoutjson_schemastructured-output support (DeepSeek via LiteLLM, some vLLM configs) run injson_objectmode and infer the JSON shape from that prompt/schema text, so they emit:{"explicit": ["alice is 25 years old", "alice has a dog"]}That fails
PromptRepresentationvalidation and, via the structured-output fallback insrc/llm/structured_output.py, is silently turned intoPromptRepresentation(explicit=[])— zero observations, no error surfaced.Fix
explicitmode="before"validator to coerce bare strings into{"content": ...}. Dicts/objects pass through unchanged; non-string scalars (e.g. a strayint) still raise, so genuine shape errors aren't masked. This mirrors the precedent already instructured_output.py, which repairsdeductiveshape drift.explicitfield-description example so the schema injected injson_objectmode no longer advertises the wrong (bare-string) shape.Load-bearing decision: coercion lives on the model boundary (not only in the repair path) so it applies whether the payload arrives via
json_objectrepair or direct validation.Type of Change
Breaking Changes
N/A — objects and
Nonecontinue to validate exactly as before; only the previously-failing bare-string shape now succeeds.Test Coverage
Added
tests/utils/test_representation.py(pure model-validation, no DB — registered in the conftest runtime-mock blocklist alongside the othertests/utilsunit tests). Covers: bare strings coerced, objects still accepted, mixed input normalized,None→[], and non-string scalar still rejected.Verified locally: with the fix all 5 pass; reverting only the validator makes the two coercion tests fail with the exact
ValidationErrorthat becomes a silentexplicit=[]in production.ruff checkandruff format --checkclean.Checklist
Summary by CodeRabbit
Bug Fixes
Documentation