Skip to content

fix(llm): serialize Responses provider tools#1643

Open
rosetta-livekit-bot[bot] wants to merge 3 commits into
mainfrom
port-responses-provider-tools-5865
Open

fix(llm): serialize Responses provider tools#1643
rosetta-livekit-bot[bot] wants to merge 3 commits into
mainfrom
port-responses-provider-tools-5865

Conversation

@rosetta-livekit-bot
Copy link
Copy Markdown
Contributor

@rosetta-livekit-bot rosetta-livekit-bot Bot commented May 29, 2026

Summary

While wiring the basic voice agent up to xAI (Grok) with its WebSearch / XSearch provider tools, the tools never reached the API — Grok answered from memory instead of searching. This PR fixes the root cause and adds visibility into server-side tool execution.

Changes

fix(llm): let the caller specify the provider tool type for responses

to_responses_fnc_ctx hardcoded openai.tools.OpenAITool, so the xAI LLM (which reuses openai's Responses serializer) had its WebSearch / XSearch tools silently dropped — they subclass XAITool, not OpenAITool, and never reached the API.

  • The caller now passes its plugin's provider-tool type via provider_tool_type, removing the core→plugin import of livekit.plugins.openai. The openai Responses LLM defaults it to OpenAITool; the xAI LLM overrides the _provider_tool_type class attribute to XAITool.
  • Adds a shared DictProviderTool base (a ProviderTool declaring to_dict()). The dict-based plugin tool bases (OpenAITool, XAITool, AnthropicTool, MistralTool) now extend it, so the to_dict contract is declared once instead of in every plugin, and the serializer is typed without a Protocol or type: ignore. (Gemini keeps ProviderTool + to_tool_config, since its schema is a typed types.Tool.)

feat(openai): log server-side provider tool execution in responses LLM

Emits an info log when the Responses API runs a tool server-side, e.g. xAI web_search and x_search (which decomposes into custom_tool_call subcalls like x_keyword_search), including the issued query and result. Detection is grounded in openai's ResponseOutputItem discriminated union (the authoritative parser): only message, reasoning, function_call, and function_call_output are produced/consumed by the agent itself — every other union member is a server-side tool, so the denylist is provably complete.

Test plan

  • Verified with the basic voice agent on xai.responses.LLM: provider tools now reach the API, searches execute, and provider tool executed logs appear on completion.
  • make lint passes on touched files; runtime check confirms all four dict-based plugin tools subclass DictProviderTool and serialize correctly.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 29, 2026

🦋 Changeset detected

Latest commit: 2f40db8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 33 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment thread agents/src/llm/utils.ts
Comment on lines 263 to +264
const tool = toolCtx[toolCall.name]!;
if (!isFunctionTool(tool)) {
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.

🔴 Missing null guard before isFunctionTool causes uncaught TypeError when tool name is not in toolCtx

executeToolCall at agents/src/llm/utils.ts:263 retrieves the tool via toolCtx[toolCall.name]! which can be undefined at runtime if the tool name doesn't exist. The next line calls isFunctionTool(tool) without a null check. Inside isFunctionTool (agents/src/llm/tool_context.ts:347), line tool[FUNCTION_TOOL_SYMBOL] accesses a property on undefined, throwing an uncaught TypeError. Before this PR, the undefined tool would crash later inside a try-catch block and return a graceful FunctionCallOutput with isError: true. The correct pattern is already used in the same PR at agents/src/llm/utils.ts:177: if (!tool || !isFunctionTool(tool)).

Suggested change
const tool = toolCtx[toolCall.name]!;
if (!isFunctionTool(tool)) {
const tool = toolCtx[toolCall.name];
if (!tool || !isFunctionTool(tool)) {
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

0 participants