← Laragents 12 / 18

Capabilities

A capability is something the model does on the provider's side. Switch it on and the provider does it itself; nothing reaches your server.

use EduLazaro\Laragents\Capabilities\WebSearch;

$loop->run(
    messages: $messages,
    model: 'gpt-4.1',
    session: $session,
    context: $context,
    tools: $registry->definitions(),
    capabilities: [new WebSearch(maxUses: 3)],
);

Tools and capabilities go in the same call, because they are not alternatives: a run can perfectly well have your database tools and the provider's web search at once.

Why it is not a Tool

A tool is a class of yours that the loop runs against your database. You see the call, you charge for it, you log it, you can stop it.

A capability is a switch. There is no execute() and no parameters(), because there is nothing to run and nothing for you to define. You find out it happened because the answer already has the information in it.

Three consequences follow from "you never see the call", and all three are the sort of thing you want to know before turning one on rather than after:

  • It does not pass through your redactor. A redacted conversation will happily send the provider a search for «AP_1».
  • It does not pass through your usage recorder. The cost arrives inside the reply's tokens, not as a call you can price.
  • It does not pass through the empty-streak guard. You never see the result, so the loop cannot tell whether it found anything.

The two that ship

new WebSearch(maxUses: 3, allowedDomains: ['boe.es']);
new FileSearch(storeIds: ['vs_abc123']);

WebSearch is worth weighing rather than assuming. It is the same job a scraping tool of yours does, done by someone else, billed inside the reply, and pointed at whatever the provider decides to read. For "what happened this week" that is the right trade. For an answer that has to cite a source you trust it is not: you cannot aim it at your own corpus, and a model that searched will cite what it found.

FileSearch searches the provider's own vector stores, so it only makes sense if your documents are already there.

Each provider spells them differently

The Translator turns them into what each side expects, which is why a capability has a name in this package's words rather than the provider's:

web_search   OpenAI: 'web_search_preview'
             Anthropic: 'web_search_20250305', versioned by date on their side

file_search  OpenAI: 'file_search' with vector_store_ids
             Anthropic: no equivalent, silently dropped

That last line is the honest one. Anthropic's answer to file search is their Files API plus a tool of yours, which is a different design, so a FileSearch passed to the Anthropic client is filtered out rather than translated into something that is not the same thing.

Writing your own

use EduLazaro\Laragents\Capabilities\Capability;

class CodeInterpreter implements Capability
{
    public function name(): string
    {
        return 'code_interpreter';
    }

    public function options(): array
    {
        return [];
    }
}

The interface is two methods because that is all a switch needs. The translation for a name the shipped Translator does not know has to be added there, or the client will drop it.