NAME
Langertha::ToolChoice - Immutable canonical tool-selection policy with cross-provider conversion
VERSION
version 0.503
SYNOPSIS
use Langertha::ToolChoice;
my $choice = Langertha::ToolChoice->specific('get_weather');
$choice->to('openai'); # { type => 'function', function => { name => 'get_weather' } }
$choice->to('anthropic'); # { type => 'tool', name => 'get_weather' }
$choice->to('responses'); # { type => 'function', name => 'get_weather' }
$choice->to('gemini');
# { functionCallingConfig => { mode => 'ANY',
# allowed_function_names => ['get_weather'] } }
# Normalize whatever the caller passed (string, any provider's hash,
# or a ToolChoice object) into the canonical form
my $tc = Langertha::ToolChoice->from_hash('required'); # type 'any'
$tc = Langertha::ToolChoice->from_hash(
{ type => 'function', function => { name => 'extract' } } );
say $tc->type, ' ', $tc->name; # tool extract
# A ToolChoice object is valid tool_choice input on every engine
my $response = await $engine->chat_f(
messages => [ { role => 'user', content => $prompt } ],
tools => [ $tool ],
tool_choice => Langertha::ToolChoice->specific('extract'),
);
DESCRIPTION
Canonical value object for the tool-selection policy of a request: may the model call a tool, must it, and must it call one particular tool. It sits beside Langertha::Tool, Langertha::ToolCall and Langertha::ToolResult in the tool wire-translation seam: request builders normalize the caller's tool_choice with "from_hash" and serialize it with "to", dispatched by the engine's tool_wire_format, so the per-provider spelling lives in this one place (ADR 0001, ADR 0010).
The canonical vocabulary has four policies, held in "type":
auto- the model decides whether to call a toolany- the model must call some tool (OpenAI'srequired)none- the model must not call a tooltool- the model must call the tool named in "name"
A ToolChoice object is valid tool_choice input on every engine and tool wire: "from_hash" hands an object back unchanged, and every request builder serializes it through "to" rather than passing the object through. What happens beyond the serialized value (for example "chat_f" in Langertha::Role::Chat rewriting a named choice into a response_format on engines without the tool_choice_named capability) is the engine's business, not this class's.
Instances are immutable.
type
Required. The canonical policy: one of auto, any, none or tool (the Langertha::ToolChoice::Type enum). Anything else fails the type constraint at construction.
name
The tool to force when "type" is tool; undef by default. A tool choice without a non-empty name serializes as auto on every wire. The name is not checked against the request's tool list.
auto
my $auto = Langertha::ToolChoice->auto;
Class method that builds an auto choice.
any
my $any = Langertha::ToolChoice->any;
Class method that builds an any choice.
none
my $none = Langertha::ToolChoice->none;
Class method that builds a none choice.
specific
my $choice = Langertha::ToolChoice->specific('get_weather');
Class method that builds a tool choice forcing the named tool.
from_hash
my $choice = Langertha::ToolChoice->from_hash($tool_choice);
Class method that normalizes a caller-supplied tool_choice into a ToolChoice, whichever provider's spelling it uses. Accepts:
a ToolChoice object, returned unchanged
the strings
auto,none, andrequiredorany(both giveany)a hash with
typeauto,none,anyorrequireda named-tool hash in the OpenAI Chat Completions shape
{ type => 'function', function => { name => ... } }, the flat Responses shape{ type => 'function', name => ... }, or the Anthropic shape{ type => 'tool', name => ... }; a missing or empty name givesauto
Returns undef for undef and for anything it does not recognize (an unknown string or type, a non-hash reference). It never dies, so callers test the result.
from_openai
Alias for "from_hash", which reads every supported shape regardless of the provider it came from.
from_anthropic
Alias for "from_hash", like "from_openai".
to_openai
my $wire = $choice->to_openai;
Serializes for the OpenAI Chat Completions tool_choice field: the strings auto, none and required (for any), or { type => 'function', function => { name => $name } } for a named tool. The openai entry of "to".
to_anthropic
my $wire = $choice->to_anthropic;
Serializes for the Anthropic Messages tool_choice field: always a hash, { type => 'auto' }, { type => 'any' }, { type => 'none' } or { type => 'tool', name => $name }. The anthropic entry of "to". disable_parallel_tool_use is not part of this value; the request builder in Langertha::Role::AnthropicCompatible folds the engine's parallel_tool_use (Langertha::Role::ParallelToolUse) into the block.
to_perplexity
my $wire = $choice->to_perplexity; # 'none' | 'auto' | 'required'
Legacy. Serializes to the string forms of Perplexity's old Sonar /chat/completions endpoint: none, auto, and required for both any and a named tool (that wire could not force a named tool). Kept for callers of that endpoint; Langertha::Engine::Perplexity no longer uses it. The Agent API (/v1/agent) that engine speaks has no tool_choice field at all, so the engine never sends one (see Langertha::Role::ResponsesCompatible). Not on the to($fmt) dispatch.
to_gemini
my $wire = $choice->to_gemini;
# { functionCallingConfig => { mode => 'ANY', allowed_function_names => ['x'] } }
Serializes for Gemini's toolConfig: { functionCallingConfig => { mode => 'AUTO' | 'ANY' | 'NONE' } }, and for a named tool mode ANY with allowed_function_names holding that one name. Gemini's VALIDATED mode has no canonical equivalent and is never produced. The gemini entry of "to".
to_responses
my $wire = $choice->to_responses;
Serializes for the Open-Responses tool_choice field (Langertha::Engine::OpenAIResponses): the strings auto, none and required (for any), or the flat { type => 'function', name => $name } for a named tool, without the nested function wrapper of Chat Completions. The responses entry of "to".
to_hash
my $hash = $choice->to_hash; # { type => 'tool', name => 'extract' }
The canonical, provider-neutral form: type, plus name when it is defined. "from_hash" reads it back.
TO_JSON
Returns "to_hash", so a JSON encoder with convert_blessed enabled serializes the object in its canonical form. That is for logging and tracing (for example Langertha::Plugin::Langfuse); a request body gets the wire form from "to".
to
my $wire = $choice->to( $engine->tool_wire_format );
Serializes for a tool_wire_format by dispatching to "to_openai", "to_anthropic", "to_gemini" or "to_responses". Only those four wires carry a tool_choice request field. ollama and hermes have none, so to croaks for them, as it does for any unknown or undefined format. (On the hermes wire Langertha::Role::Chat handles the choice itself: none withholds the tools from the prompt, anything else but auto is ignored with a warning.) "to_perplexity" is not on this dispatch.
SEE ALSO
Langertha::Tool - Sibling value object for tool definitions
Langertha::ToolCall - Sibling value object for the calls a model emits
Langertha::ToolResult - Sibling value object for tool result blocks
"chat_f" in Langertha::Role::Chat - Takes
tool_choiceand rewrites a named choice where the wire cannot force a toolLangertha::Role::Capabilities - The
tool_choice_auto,tool_choice_any,tool_choice_noneandtool_choice_namedflagsADR 0001 and ADR 0010 in docs/adr/ - Why tool wire-translation routes through these value objects
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha/issues.
IRC
Join #langertha on irc.perl.org or message Getty directly.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.