Custom tools

  1. Example: an internal blocklist tool
  2. Context-aware tools
  3. Web search
  4. Good tool design

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
}

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.

Counterparty Verification - a due-diligence aid, not a compliance product. MIT licensed.

This site uses Just the Docs, a documentation theme for Jekyll.