ToolChoice is one of the few parts of skyl where all four adapters support
everything. Four modes, all mapped everywhere.
Reference#
type ToolChoice struct {
Mode ToolChoiceMode
Name string
}
| Mode | Value | Effect |
|---|---|---|
ToolChoiceAuto | auto | The model decides. The default. |
ToolChoiceNone | none | Tools are offered but must not be called this turn. |
ToolChoiceRequired | required | At least one call is forced. |
ToolChoiceSpecific | tool | A named tool is forced. Name is required. |
Caveats
- Nil means
ToolChoiceAuto— you rarely need to set it. Nameis required forToolChoiceSpecificand rejected locally otherwise:tool choice "tool" requires a name.Nameis ignored in every other mode.- An unknown mode is rejected by
ValidatewithErrBadRequest. - With
Required, still check the call list — compatible hosts implement OpenAI's format without always implementing its semantics.
Usage#
Forcing a final answer
// The cleanest way to end a tool loop: stop offering the option.
req.ToolChoice = &skyl.ToolChoice{Mode: skyl.ToolChoiceNone}// The cleanest way to end a tool loop: stop offering the option.
req.ToolChoice = &skyl.ToolChoice{Mode: skyl.ToolChoiceNone}Structured output, portably
req.Tools = []skyl.Tool{extract}
req.ToolChoice = &skyl.ToolChoice{Mode: skyl.ToolChoiceSpecific, Name: "record_invoice"}
resp, err := client.Complete(ctx, req)
if err != nil {
return err
}
calls := resp.ToolCalls()
if len(calls) == 0 {
return errors.New("no extraction produced")
}
return json.Unmarshal(calls[0].Arguments, &invoice)req.Tools = []skyl.Tool{extract}
req.ToolChoice = &skyl.ToolChoice{Mode: skyl.ToolChoiceSpecific, Name: "record_invoice"}
resp, err := client.Complete(ctx, req)
if err != nil {
return err
}
calls := resp.ToolCalls()
if len(calls) == 0 {
return errors.New("no extraction produced")
}
return json.Unmarshal(calls[0].Arguments, &invoice)A forced tool call gives you structured output using machinery every provider implements identically — unlike the native JSON modes, which differ in dialect, guarantees and model coverage.
Verifying Required was honoured
if len(resp.ToolCalls()) == 0 {
return fmt.Errorf("%s returned no tool call despite tool_choice=required", resp.Provider)
}if len(resp.ToolCalls()) == 0 {
return fmt.Errorf("%s returned no tool call despite tool_choice=required", resp.Provider)
}Troubleshooting#
ErrBadRequest: tool choice “tool” requires a name
ToolChoiceSpecific needs Name set to the tool you want forced.
Setting Name with Auto did nothing
Expected — Name is only read in ToolChoiceSpecific mode.
Required produced only text on a compatible host
Some OpenAI-compatible hosts accept tool_choice without enforcing it. Check
the call list and treat an empty one as an error from that host.