Skip to content

Tool-selection capability interfaces, and Agent support for toolChoice and effort - #29

Merged
MarcelloDuarte merged 1 commit into
mainfrom
tool-choice-capabilities
Jul 31, 2026
Merged

Tool-selection capability interfaces, and Agent support for toolChoice and effort#29
MarcelloDuarte merged 1 commit into
mainfrom
tool-choice-capabilities

Conversation

@MarcelloDuarte

Copy link
Copy Markdown
Contributor

Capability by type, not by probe

Adds ToolSelectableInterface (can force required/none) and NamedToolSelectableInterface extends it (can also force one named tool). Callers ask before calling instead of catching:

if ($provider instanceof NamedToolSelectableInterface) {
    $agent->run($prompt, ['toolChoice' => ['name' => 'propose_edit']]);
} elseif ($provider instanceof ToolSelectableInterface) {
    $agent->run($prompt, ['toolChoice' => 'required']);
}

extends rather than siblings, because forcing a named tool is strictly more than forcing a tool, so the common case is one check. This matches how Embedding/Image/Video capabilities already work. supportsTool() does not answer this question: it returns true on both Ollama and Cohere, which throw on forced choice.

FailoverProvider deliberately implements neither. Its capability depends on the providers it holds at runtime, and instanceof is fixed at class definition, so claiming a capability it might not honour would be worse than claiming none.

Agent can now reach both options

Agent could set neither toolChoice nor effort, which made both unreachable from the high-level API.

They behave differently, on purpose:

  • toolChoice forces the opening call only. Applying it every turn leaves the model unable to answer in plain text, so the loop could only ever end by exhausting maxTurns and throwing. There is a test asserting call 0 carries it and call 1 does not.
  • effort applies to every turn, which is what asking to think harder means.

Compatibility

Purely additive: two new interfaces, two new option keys read from an array callers already pass. ProviderInterface untouched, no signature changed. Shipping as a patch so everything on ^0.15 picks it up with no re-tagging.

252 tests, Psalm clean, 88% coverage.

…ce and effort from Agent

Adds ToolSelectableInterface and NamedToolSelectableInterface, so callers can
ask what a provider can enforce instead of catching a ProviderException.
NamedToolSelectableInterface extends the other because forcing a named tool is
strictly more than forcing some tool, which makes the common case one check.
Capability by type matches how Embedding, Image and Video already work.

Agent can now pass toolChoice and effort, neither of which it could reach
before. They differ on purpose: a forced tool applies to the opening call only,
because forcing one every turn leaves the model unable to answer in plain text
and the loop can only end by exhausting maxTurns. Effort applies throughout,
which is what asking to think harder means.
@MarcelloDuarte
MarcelloDuarte merged commit 6b8ca87 into main Jul 31, 2026
6 checks passed
@MarcelloDuarte
MarcelloDuarte deleted the tool-choice-capabilities branch July 31, 2026 14:40
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.

1 participant