Custom tools
Expose your own data to the model by implementing ResearchTool. Tools are the only way
the AI obtains facts, so each result must carry a source URL.
interface ResearchTool
{
public function name(): string; // function name exposed to the model
public function description(): string;
public function schema(): array; // JSON-schema for the arguments
public function execute(array $arguments): ToolResult;
}
Example: an internal blocklist tool
use Gawrys\Counterparty\Ai\Tool\{ResearchTool, ToolResult};
final readonly class BlocklistTool implements ResearchTool
{
public function __construct(private BlocklistRepository $repo) {}
public function name(): string { return 'internal_blocklist'; }
public function description(): string
{
return 'Check the company against our internal blocklist by tax id.';
}
public function schema(): array
{
return [
'type' => 'object',
'properties' => ['nip' => ['type' => 'string', 'description' => 'Polish NIP']],
'required' => ['nip'],
];
}
public function execute(array $arguments): ToolResult
{
$nip = is_string($arguments['nip'] ?? null) ? $arguments['nip'] : '';
$hit = $this->repo->find($nip);
return $hit === null
? ToolResult::ok(['listed' => false])
: ToolResult::ok(['listed' => true, 'reason' => $hit->reason], sourceUrl: $hit->url);
}
}
Register it alongside the bundled tools:
$strategy = new AiRiskStrategy($provider, new RiskPromptBuilder(),
tools: [new RegistryTool($registries), new ReportLookupTool(), new BlocklistTool($repo)],
cache: $cache);
Context-aware tools
If a tool must operate on the counterparty under verification (not on arbitrary model input),
implement ContextAwareTool; the strategy binds it before each run:
use Gawrys\Counterparty\Ai\Tool\{ContextAwareTool, ResearchContext};
final readonly class MyTool implements ContextAwareTool
{
public function __construct(private ?Counterparty $counterparty = null) {}
public function forContext(ResearchContext $context): static
{
return new self($context->counterparty);
}
// name()/description()/schema()/execute() as usual; use $this->counterparty
}
Web search
WebSearchTool ships but needs a WebSearchClient you provide (search vendors vary and are
licensed separately):
interface WebSearchClient
{
/** @return list<WebSearchResult> */
public function search(string $query, int $limit = 5): array;
}
Each WebSearchResult (title, url, snippet) is returned with its URL, so findings derived
from it are grounded by construction.
Good tool design
- Return structured data plus a source URL; avoid free prose the model might over-trust.
- Keep
schema()tight (only the arguments you support) so the model calls it correctly. - Fail soft:
ToolResult::failed('reason')rather than throwing.