Providers

  1. Anthropic
  2. OpenAI
  3. Swapping LLMs is one line
  4. Writing your own provider
  5. Choosing a model

An AiResearchProvider is the LLM backend. Two reference providers ship, both over PSR-18 (no SDK dependency - the SDK is only a suggest).

interface AiResearchProvider
{
    /** @param list<ResearchTool> $tools */
    public function research(ResearchRequest $request, array $tools): ResearchResult;
}

Anthropic

use Gawrys\Counterparty\Ai\Research\AnthropicResearchProvider;

$provider = new AnthropicResearchProvider(
    http: $jsonHttpClient,
    apiKey: $anthropicKey,
    model: 'claude-haiku-4-5', // default
);

Calls the Messages API, exposes the tools for native tool use, and returns the validated findings JSON.

OpenAI

use Gawrys\Counterparty\Ai\Research\OpenAiResearchProvider;

$provider = new OpenAiResearchProvider(
    http: $jsonHttpClient,
    apiKey: $openAiKey,
    model: 'gpt-4o-mini', // default
);

Calls Chat Completions in JSON mode, with tools for function calling.

Swapping LLMs is one line

Because both implement the same port, switching is a constructor change. On the bridges, it is driven by which key is present / by config - see Recipes -> Swap LLM.

Writing your own provider

Extend AbstractAiResearchProvider and implement a single method - the base class handles the force-JSON / parse / validate / retry loop and the tool-use loop scaffolding:

use Gawrys\Counterparty\Ai\Research\{AbstractAiResearchProvider, ResearchRequest};

final readonly class MyLlmProvider extends AbstractAiResearchProvider
{
    protected function complete(ResearchRequest $request, array $tools): string
    {
        // 1. send $request->systemPrompt / $request->userPrompt to your model
        // 2. expose $tools for function calling (use $this->runTool(), $this->encodeToolResult())
        // 3. return the final raw findings JSON string
    }
}

Helpers available to subclasses: runTool(), encodeToolResult(), objectList(), stringKeyedArguments(), and maxToolHops. Malformed output is retried up to maxAttempts and never trusted; on exhaustion the strategy falls back to “human review required”.

Choosing a model

  • Use a cheaper, fast model for triage (the defaults are deliberately small).
  • Reach for a stronger model for deep dives on flagged counterparties.
  • Keep maxTokens modest; the findings JSON is small.

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

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