Skip to content

fix(llm): never give a const field the null default sentinel - #6688

Merged
u9g merged 1 commit into
mainfrom
fix/strict-const-null-sentinel
Aug 11, 2026
Merged

fix(llm): never give a const field the null default sentinel#6688
u9g merged 1 commit into
mainfrom
fix/strict-const-null-sentinel

Conversation

@u9g

@u9g u9g commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

A defaulted field gets a null branch in strict schemas so the model can decline a value, which _prepare_function_arguments swaps back for the Python default. A const field has exactly one legal value, so nulling it gains nothing and the emitted schema contradicts itself:

{"const": "celsius", "type": ["string", "null"]}

It also loses information. Strict mode has no discriminator keyword, so the const each variant of a discriminated union pins on its tag is the only thing that resolves the variant on the way back in. Tags are normally written with a default (unit: Literal["celsius"] = "celsius"), so they were taking the sentinel — and a returned null leaves variants that differ only in their tag indistinguishable:

{"temp": {"unit": None, "value": 70.0}}  ->  Celsius(unit='celsius', value=70.0)

The model said Fahrenheit; the tool got Celsius. Covers callable discriminators (Discriminator(fn) + Tag(...)) too, which emit no discriminator keyword to key off.

Testing

  • pytest tests/test_tools.py (105 passed), plus -k "strict or schema or tool" across tests/ (211 passed)
  • tag emitted as {"const": ..., "type": "string"}, required and non-nullable, and the named variant round-trips, for: bare arg, optional-only arg, list element, nested model field, str-Enum-valued tag, callable discriminator, and to_openai_response_format
  • live tool call accepted with the correct tag returned by openai/gpt-4.1-mini and google/gemini-2.5-flash via the inference gateway

@u9g
u9g requested a review from a team as a code owner August 3, 2026 21:40

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

A defaulted field gets a null branch in strict schemas so the model can decline a
value that _prepare_function_arguments then swaps back for the Python default. A
const field has exactly one legal value, so nulling it gains nothing, and the
emitted schema contradicts itself: {"const": "celsius", "type": ["string", "null"]}.

It also loses information. Strict mode has no `discriminator` keyword, so the const
each variant of a discriminated union pins on its tag field is the only thing that
resolves the variant on the way back in. Tags are normally written with a default
(`unit: Literal["celsius"] = "celsius"`), so they were taking the sentinel — and a
returned null leaves variants that differ only in their tag indistinguishable, with
the first one silently winning.

Covers callable discriminators too, which emit no `discriminator` keyword at all.
@u9g
u9g force-pushed the fix/strict-const-null-sentinel branch from ca957e2 to 5dd87db Compare August 9, 2026 12:24
@u9g
u9g requested review from theomonnom and a lite review from Copilot August 9, 2026 12:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes strict tool-schema generation so defaulted const fields (notably discriminated-union tags) are never made nullable via the “null default sentinel”, preventing contradictory schemas and incorrect variant resolution during tool argument parsing.

Changes:

  • Update strict-schema default handling to skip adding a null sentinel for const-constrained fields.
  • Add targeted tests ensuring defaulted Literal[...] tags remain pinned ({"const": ..., "type": "string"}) and discriminated unions round-trip into the correct variant.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
tests/test_tools.py Adds regression tests for defaulted const fields and discriminated-union tag stability under strict schemas.
livekit-agents/livekit/agents/llm/_strict.py Adjusts null-sentinel injection to explicitly exempt const schemas while preserving existing handling for enums/unions/refs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@u9g
u9g merged commit 5c8e864 into main Aug 11, 2026
25 checks passed
@u9g
u9g deleted the fix/strict-const-null-sentinel branch August 11, 2026 11:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants