Repository navigation
fix(proto): stringify map values in encode_by_name - #7962
Merged
Merged
Conversation
livekit/protocol#1853 declares FunctionCall.extra as map<string, string>. encode_by_name copied llm.FunctionCall.extra (dict[str, Any]) into it unconverted, so with that protocol any call whose extra holds a non-str value raised "TypeError: bad argument type for built-in operation" on every ChatItem serialization path. Every inference.LLM tool call carries {"livekit": {...}}, and ctx.update() sets a bool flag. ChatMessage.extra already got str() at its call site; the coercion now applies to every map field in encode_by_name.
u9g
marked this pull request as ready for review
October 19, 2026 21:53
Member
|
Right I guess that's why I wasn't a fan of auto-forwarding args |
theomonnom
approved these changes
Oct 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
livekit/protocol#1853 adds
map<string, string> extra = 6tolivekit.agent.FunctionCall. Once livekit-agents runs with a livekit-protocol that has the field, encoding a function call whoseextraholds anything other than a string raises:From
SessionHostthis comes back assession request run_input failed: bad argument type for built-in operation. Agent-simulation text runs on livekit-agents 1.7.1 with a livekit-protocol built from protocol main failed every simulation on turn 1 with this error.Who hits it, and when
No released livekit-protocol on PyPI has the field yet. The latest is 1.1.27, and the staged v1.1.28 (livekit/python-sdks#823) still pins a protocol commit from before #1853. The first PyPI release built from a protocol that includes livekit/protocol@1e406a8 will have it. livekit-agents accepts that release (
livekit-protocol>=1.1.21,<2), so upgrading is enough to trigger the bug. These calls carry non-stringextra:inference.LLM, for every model. The LiveKit Inference gateway putsextra_content.livekit(deployment and tier metadata) on every streamed tool call, and the client stores it asFunctionCall.extra = {"livekit": {...}}(inference/llm.py, generation.py). Gemini 3 calls also carry{"google": {"thought_signature": ...}}.openai.LLMand the plugins built on its stream, when the endpoint returnsextra_content. It reuses the same stream class (openai/llm.py).ctx.update(), with any LLM. The first update setsextra["__livekit_agents_tool_non_blocking"] = True(events.py).The
google.LLMplugin is not affected. It keeps thought signatures on the LLM instance, not inFunctionCall.extra(google/llm.py).Everything that serializes a ChatItem fails on such a call:
SessionHost:run_input(remote_session.py),get_chat_history,get_agent_infoandconversation_item_addedevents._upload_session_reportraises at the first such item in the transcript (traces.py). The rest of the transcript, the evaluations, the outcome and the audio recording are never uploaded. The only trace is the log linefailed to upload the session report to LiveKit Cloud(job.py).Root cause
#6756 changed
encode_by_nameto copy every field the proto declares off the source object by name. That way a new schema field is carried without editing a call site (_proto.py), and the FunctionCall branch depends on it entirely (L100-L101).llm.FunctionCall.extraisdict[str, Any](chat_context.py), but the wire field ismap<string, string>(proto), andencode_by_namepasses the dict through unconverted.ChatMessage.extrahas the same types and never failed, because its call site converts each value withstr(v)(L97). Both serializers that existed before #6756 did the same for that map.Fix
The
str(v)conversion moves intoencode_by_name, next to the Timestamp conversion, so it applies to every map field. Every map these protos declare ismap<string, string>. The ChatMessage call site no longer needs its own copy, so it is removed.ChatMessageoutput is unchanged. It is the samestr(v).FunctionCall.extra, nothing else changes.{"livekit": "{'inference_deployment': ...}"}. That is the same formChatMessage.extraalready uses for dict values.Tests
test_map_values_are_stringifiedencodes a dict value and a bool value throughencode_by_name. On main it raises theTypeErrorwith any protocol version. With the fix it passes.FunctionCallnow has anextrawith a dict value. With a protocol that declares the field, the guard exercises it. Run against main's_proto.pywith a protocol-main build,function_callandtest_saturated_items_cover_every_item_typefail with theTypeError. With the fix they pass.extra={"livekit": {...}}. On main:RuntimeError: session request run_input failed: bad argument type for built-in operation. With the fix, the turn returnsfunction_call,function_call_outputandmessage.Results:
pytest --unit, locked livekit-protocol 1.1.21): 5359 passed, 13 failed. The same 13 fail on a pristineorigin/mainin this environment: 12 intest_vad_buffer_resize.py(numpyresizerefcheck on Python 3.14) and 1 intest_google_credentials.py(it picks up local ADC).tests/test_chat_item_proto_coverage.pyandtests/test_session_host.pypass.ruff check,ruff format --checkandscripts/check_types.pyare clean.agents-js
agents-js is not affected. Its encoder lists FunctionCall fields explicitly and never sets
extra(proto.ts), and its lockfile pins@livekit/protocol1.50.4.@livekit/protocol1.53.0 already declares the field. When agents-js moves to it, its coverage guard (test) will reportfunction_call drops: extra. The fix there is theString(v)that its ChatMessage path already uses (proto.ts).