Skip to content

fix(llm): support [key] lookup on nested dicts in template placeholders - #8142

Open
ANDROIDHASSAN wants to merge 3 commits into
livekit:mainfrom
ANDROIDHASSAN:fix/safe-render-item-access
Open

ANDROIDHASSAN wants to merge 3 commits into
livekit:mainfrom
ANDROIDHASSAN:fix/safe-render-item-access

Conversation

@ANDROIDHASSAN

@ANDROIDHASSAN ANDROIDHASSAN commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Problem: safe_render (used by Instructions.render and Instructions.resolve_template) converts nested dicts to SimpleNamespace so templates can use dotted access like {user.name}. That conversion drops item access: SimpleNamespace has no __getitem__, so the standard str.format lookup {user[name]} raises TypeError. The error is caught, logged as a missing placeholder, and the field renders empty, even though the key is present in data:

Instructions("Hello {user[name]}").render(data={"user": {"name": "Alex"}})
# main: 'Hello '   (and logs "template placeholder '{user[name]}' has no value")

This also breaks keys that are not valid identifiers, which only [key] can reach, such as {user[support-tier]}.

Fix: convert nested dicts to a small SimpleNamespace subclass that also implements __getitem__ from its __dict__. Dotted access is unchanged and [key] works again, as with a plain str.format on the dict. The subclass keeps the namespace type name, so a whole-dict placeholder such as {user.account} still renders namespace(plan='pro'). A missing key still raises KeyError, so it still renders empty and logs through the existing handler in _Fmt.get_field.

Tests

Six nested-dict-* cases added to the parametrized test_render in tests/test_instructions.py (moved there from test_chat_ctx.py after #8510 folded the instructions tests into that module). {user[name]}, {user[account][plan]}, {user.account[plan]} and {user[support-tier]} each fail on main with an empty render. {user.name} and {user.account} pass on main and stay as guards for dotted access and for the rendered namespace(...) repr.

$ uv run pytest tests/test_instructions.py -k nested -q   # utils/misc.py from main
4 failed, 2 passed, 50 deselected
$ uv run pytest --unit --audio_eot -q   # after merging main
6236 passed, 4 skipped

ruff format, ruff check and make type-check are clean.

Scope: only safe_render in utils/misc.py and its test cases. Missing-placeholder handling, None rendering and the logged list of available paths are unchanged. Lists inside data are not converted, so {items[0][name]} behaves as before.

safe_render turns nested dicts into SimpleNamespace for dotted access, which has no __getitem__, so {user[name]} rendered empty although the key exists. Use a namespace subclass that keeps item access.
@ANDROIDHASSAN
ANDROIDHASSAN requested a review from a team as a code owner October 26, 2026 19:24
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-26T19:26:05.526822Z
We couldn’t complete this review. Comment @codex review to try again.
8701920 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

devin-ai-integration[bot]

This comment was marked as resolved.

The _Namespace subclass changed {user} from namespace(...) to _Namespace(...). Keep the type name; add a guard case.

@RaphaelFakhri RaphaelFakhri 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.

I checked out 83d5ed9 and ran the change against main with a set of edge-case templates outside the new test. Results match plain str.format on the dict in every case I tried:

  • {user[a.b]} (key with a dot) renders dot, {user[name][0]} renders A, and {user[name]:>6} and {user[name]!r} keep the spec and conversion. All of these render empty on main.
  • {items[0][name]} on a list of dicts and {nested_list[xs][0][q]} resolve, since lists stay unconverted.
  • {user[missing]}, {user[account][missing]}, {user[__dict__]} and {user[__class__]} render empty and log through the existing handler. Item lookup reads only __dict__, so it adds no attribute access beyond what dotted lookup already allows.
  • {user[0]} with a string key "0" renders empty, same as str.format, which turns a digit-only index into an int.
  • {user}, {user!r} and nested values render namespace(...) on Python 3.10, 3.11, 3.12, 3.13 and 3.14, so the __name__ override holds across the supported range.

tests/test_chat_ctx.py passes (93 passed, 1 skipped), and the new test fails 4 of 6 cases with utils/misc.py reverted.

One optional follow-up: the safe_render docstring still describes only dotted access. Since keys such as support-tier are now reachable only through [key], you could mention that form there too.

…m-access

# Conflicts:
#	tests/test_chat_ctx.py

This branch has not been deployed

No deployments
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