LLM Provider Tool Support in Phoenix Playground#
The Phoenix playground supports tool definitions and tool-choice configuration across multiple LLM providers — OpenAI, Azure OpenAI, Anthropic, AWS Bedrock, Google Gemini, Perplexity, DeepSeek, xAI, and Ollama. Each provider uses a different wire format, so the implementation normalizes everything to a canonical schema layer: provider-specific shapes are converted to shared canonical types for Zustand store storage and GraphQL transport, then projected back to provider wire formats at the API boundary .
Key Files#
| File | Role |
|---|---|
app/src/schemas/toolSchemas.ts | Zod schemas for each provider's tool definition format; bidirectional conversion functions |
app/src/schemas/toolChoiceSchemas.ts | Zod schemas for each provider's tool choice format; conversion utilities |
app/src/components/generative/ToolChoiceSelector.tsx | UI component for selecting tool choice mode per provider |
app/src/pages/playground/schemas.ts | Playground-level Zod schemas for span attribute parsing, including toolJSONSchemaSchema |
app/src/pages/playground/playgroundUtils.ts | Utility functions for constructing ChatCompletionInput from playground state |
Provider Tool Definition Schemas#
Each provider has a distinct wire format for tool definitions. All schemas are defined in toolSchemas.ts using Zod.
| Provider | Top-level shape | Schema |
|---|---|---|
| OpenAI / Azure / DeepSeek / xAI / Ollama | { type: "function", function: { name, description?, parameters } } | openAIToolDefinitionSchema |
| Anthropic | { name, description, input_schema, strict? } | anthropicToolDefinitionSchema |
| AWS Bedrock | { toolSpec: { name, description, inputSchema: { json }, strict? } } | awsToolDefinitionSchema |
| Google Gemini | { name, description, parameters } (flat, no type: "function" wrapper) | added in PR #10358 |
The llmProviderToolDefinitionSchema is a z.union of all known formats — plus a jsonLiteralSchema fallback — and is used where any provider format must be accepted.
All schemas use .passthrough() on the parameters / properties objects to allow extra keys in JSON Schema definitions without blocking validation .
Canonical Tool Definition#
PR #12108 introduced CanonicalToolDefinition ({ name, description?, parameters?, strict? }) as the internal store representation, replacing provider-specific shapes in the Zustand store. Key conversion functions:
toCanonicalToolDefinition(raw)— tries each provider schema in order and produces the canonical form.getToolDefinitionDisplay(canonical, provider)— renders canonical form back to the provider-specific JSON editor shape.
Note:
fromOpenAIToolDefinitionstill passes OpenAI format directly for
AWS Bedrock Known Bug (#13934)#
When replaying a Bedrock trace, tools stored via openinference-instrumentation-bedrock contain only the inner toolSpec body ({ name, description, inputSchema }) — not the required { toolSpec: { ... } } wrapper. The Python playground client passes these raw objects to converse_stream, producing a botocore.exceptions.ParamValidationError. The fix is to detect the missing wrapper and add it before dispatch .
Tool Choice Canonicalization#
Tool choice (whether the model should auto-select, always use, or target a specific tool) is canonicalized independently of tool definitions.
Provider-Specific Choice Formats#
Defined in toolChoiceSchemas.ts:
| Provider group | Wire format example |
|---|---|
| OpenAI / Azure / DeepSeek / xAI / Ollama / Perplexity | String "auto" / "none" / "required", or { type: "function", function: { name } } |
| Anthropic | `{ type: "auto" |
| AWS Bedrock | `{ type: "auto" |
| Google Gemini | `{ function_calling_config: { mode: "AUTO" |
The DEFAULT_TOOL_CHOICES_BY_PROVIDER constant in ToolChoiceSelector.tsx defines which simple choices (e.g. "required", "auto", "none") are rendered by default per provider. GOOGLE is currently unsupported in the selector UI, returning a hardcoded "auto" .
Canonical Tool Choice#
PR #12108 introduced CanonicalToolChoice:
{ type: "NONE" | "ZERO_OR_MORE" | "ONE_OR_MORE" | "SPECIFIC_FUNCTION", functionName?: string }
Raw span-attribute values are converted directly to canonical via rawSpanToolChoiceToCanonical(), which tries each provider's schema in sequence (OpenAI → Anthropic → Google → AWS) and maps to the canonical enum without pivoting through OpenAI format .
safelyConvertToolChoiceToProvider (from toolChoiceSchemas.ts) safely converts a canonical (or raw) choice to the target provider format; it is used in playgroundUtils.ts when building ChatCompletionInput.
ToolChoiceSelector Component#
ToolChoiceSelector renders a dropdown with:
- Default choices for the current provider (from
DEFAULT_TOOL_CHOICES_BY_PROVIDER). - Per-tool entries for each defined tool name (prefixed with
tool_to avoid key collisions).
On selection, it calls makeOpenAIToolChoice, makeAnthropicToolChoice, or makeAwsToolChoice to produce the correct wire format for the active provider .
Provider-Specific Notes#
OpenAI (and Azure OpenAI)#
The reference format. All conversion functions use OpenAI as a common intermediate. Supports a strict field on parameters for structured output enforcement . Tool choice accepts strings or a { type: "function", function: { name } } object.
Anthropic#
Uses input_schema instead of parameters and omits the type: "function" wrapper. Supports an optional strict boolean field that enables strict schema validation for the tool's input parameters. Bidirectional converters: anthropicToolToOpenAI and openAIToolToAnthropic. Tool choice adds optional disable_parallel_tool_use flag .
AWS Bedrock#
Requires a toolSpec wrapper: { toolSpec: { name, description, inputSchema: { json }, strict? } } . Supports an optional strict boolean field that enables strict schema validation for the tool's input parameters. Converters: awsToolToOpenAI and openAIToolToAws. The description field must be non-empty (min(1)). A known bug (#13934) causes ParamValidationError when replaying traces where the instrumentation stored the inner spec body without the toolSpec wrapper.
Google Gemini#
Flat format: { name, description, parameters } — no type: "function" wrapper . Added in PR #10358. Tool choice uses function_calling_config with mode and optional allowed_function_names array. The ToolChoiceSelector does not yet fully support Gemini in the UI (returns hardcoded "auto"). A backend GoogleToolChoiceConversion class handles to_google / from_google mapping .
Perplexity#
Fully OpenAI-compatible. The PerplexityStreamingClient subclasses OpenAIBaseStreamingClient and reuses all OpenAI tool/choice schemas . Added alongside DeepSeek, xAI, and Ollama in the DEFAULT_TOOL_CHOICES_BY_PROVIDER OPENAI group .
DeepSeek / xAI / Ollama#
Treated as OpenAI-compatible: same tool definition schema, same ["required", "auto", "none"] tool-choice options .
Span Attribute Parsing and Tool Loading#
When a trace span is loaded into the playground, tools are reconstructed from OpenInference span attributes. toolJSONSchemaSchema in playground/schemas.ts:
- Parses the
llm.tools.{i}.tool.json_schemaattribute string as JSON. - Validates it against
llmProviderToolDefinitionSchema(accepts any provider format). - Returns the parsed
LlmProviderToolDefinitionobject.
llmToolSchema wraps this into the full llm.tools attribute structure following OpenInference semantic conventions.
Tool names and descriptions can be extracted from any format via findToolDefinitionName and findToolDefinitionDescription, which handle both the OpenAI function.name path and the Anthropic/AWS flat name path.
Provider auto-detection for unmarked tool blobs is handled by detectToolDefinitionProvider, which tries schemas in priority order: OpenAI → Anthropic → AWS → UNKNOWN.