Skip to content

fix(llm): apply structured response defaults#6444

Open
u9g wants to merge 5 commits into
mainfrom
fix/response-format-default-types
Open

fix(llm): apply structured response defaults#6444
u9g wants to merge 5 commits into
mainfrom
fix/response-format-default-types

Conversation

@u9g

@u9g u9g commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • keep defaulted structured-response fields nullable so the LLM is not required to invent a value
  • add response validation that replaces null sentinels with their Pydantic defaults
  • preserve real null values for fields whose declared type is nullable
  • cover nested models and lists

Context

Strict JSON schemas require every property and do not support Python defaults. LiveKit therefore represents a defaulted field as nullable: null means use the Python default. This lets the LLM avoid generating a value it cannot know.

Tool execution already resolves that sentinel before validation. Structured responses did not, so direct Pydantic validation could reject a null that was valid according to the generated schema. This change keeps the existing schema behavior and adds validate_response_format as the matching middleware step: it recursively replaces null with the declared default only when the original field type is non-nullable, then performs Pydantic validation.

Testing

  • 101 focused unit tests passed
  • Ruff formatting and lint passed
  • focused type checking reports no new errors

@u9g u9g requested a review from a team as a code owner July 15, 2026 15:56
Comment thread livekit-agents/livekit/agents/llm/_strict.py Outdated
Comment on lines -121 to -132
# Treat any parameter with a default value as optional. If the parameter’s type doesn't
# support None, the default will be used instead.
t = json_schema.get("type")
if isinstance(t, str):
json_schema["type"] = [t, "null"]

elif isinstance(t, list):
types = t.copy()
if "null" not in types:
types.append("null")

json_schema["type"] = types

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you verify it doesn't break existing behaviour? I think the reasoning behind that is that users using default values on parameters don't want to require the LLMs to generate a value for it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in 80a6538. I kept the existing default-as-null schema behavior, so the LLM is still not required to generate a value it cannot know. The new validate_response_format helper recursively replaces null with the Pydantic default before validation, while preserving null for fields whose declared type is actually nullable. The focused response-format and tool suites pass (101 tests).

@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 bugs or issues to report.

Open in Devin Review

@u9g u9g force-pushed the fix/response-format-default-types branch from b960f6c to 2ba5b76 Compare July 15, 2026 16:03
@u9g u9g changed the title fix(llm): preserve response format nullability fix(llm): apply structured response defaults Jul 15, 2026
u9g added 3 commits July 15, 2026 13:02
…ema_defaults

The helper is not specific to response formats; prepare it for reuse by
tool argument preparation.
Tool argument preparation only replaced null with the parameter default
for top-level parameters, so a defaulted non-nullable field inside a
nested model argument failed validation. Reuse the recursive
_inject_schema_defaults pass (shared with structured responses) instead
of walking the signature.

A null for a required parameter with no default is now rejected by
pydantic validation instead of a hand-written ValueError; both surface
as ToolError.
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.

2 participants