Documentation
¶
Overview ¶
Package provider abstracts the LLM behind a tool-use loop. The agent in internal/agent is provider-agnostic: it builds Requests in the shape defined here and feeds them through whichever Provider implementation the caller selected on the command line. Each provider package (anthropic.go, openai.go, gemini.go, compat.go) converts to and from the upstream SDK's own request/response types.
Why a shared shape: every modern tool-use API converges on the same abstract flow -- a system prompt, a conversation of typed content blocks, a tool registry, and a stop reason that signals whether the model wants another tool call or is done. Mapping into this shape at the adapter boundary keeps the agent loop a few dozen lines and makes it trivial to add or swap providers without rewriting the orchestration.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Anthropic ¶
type Anthropic struct {
// contains filtered or unexported fields
}
Anthropic is the reference Provider implementation. It maps the provider.Request/Response abstraction directly to Anthropic's native tool-use protocol, which is the shape the abstraction was designed around -- the conversion is mostly field-renaming.
func NewAnthropic ¶
NewAnthropic constructs a Provider backed by Anthropic's Messages API. apiKey is required (empty string surfaces an error on the first Complete call rather than at construction; this lets callers build the provider once and resolve credentials lazily). Model names are passed through verbatim -- the SDK accepts every current Claude model string ("claude-opus-4-7", "claude-sonnet-4-6", ...) without us mapping them.
type BlockType ¶
type BlockType string
BlockType discriminates the union in ContentBlock. We do not try to encode every provider-specific block type (image, thinking, citations) because the audit agent does not need them; if a future feature requires it, add a new BlockType here and a fresh case in the adapter conversions.
type ContentBlock ¶
type ContentBlock struct {
Type BlockType
// Type == BlockText
Text string
// Type == BlockToolUse (assistant turn)
ToolUseID string
ToolName string
ToolInput json.RawMessage
// Type == BlockToolResult (user turn following a tool_use)
ToolResultID string
ToolResult string
IsError bool
}
ContentBlock is the tagged union for one piece of message content. Only the fields relevant to Type are populated; consumers must switch on Type before reading provider-specific fields.
type Gemini ¶
type Gemini struct {
// contains filtered or unexported fields
}
Gemini maps the provider abstraction onto google.golang.org/genai. Each Complete call constructs a fresh client; the SDK is cheap to reinitialise and statelessness avoids leaking a long-lived HTTP connection across audit runs that may use different credentials.
func NewGemini ¶
NewGemini constructs a Provider backed by Google's Gemini API. apiKey lands in genai.ClientConfig.APIKey; we always force BackendGeminiAPI rather than letting the SDK auto-detect Vertex -- the audit agent is firmly a Gemini API consumer and an accidental switch to Vertex would silently change auth semantics.
Network errors and bad credentials surface only at the first Complete call, not here; the underlying genai.Client also does lazy authentication.
type Message ¶
type Message struct {
Role Role
Content []ContentBlock
}
Message is a single turn in the conversation. Content carries one or more typed blocks so a single assistant turn can interleave natural-language reasoning with tool_use blocks the way Anthropic and the OpenAI Responses API natively do.
type OpenAI ¶
type OpenAI struct {
// contains filtered or unexported fields
}
OpenAI maps provider.Request/Response to the Responses API. It covers the canonical openai.com endpoint and every OpenAI- compatible service that speaks the same wire shape.
func NewOpenAI ¶
NewOpenAI constructs a Provider backed by the OpenAI Responses API. apiKey is the only required parameter; pass an empty apiBase to hit api.openai.com. The same factory powers NewOpenAICompatible, which simply forwards a non-empty apiBase so DeepSeek, Groq, Mistral, vLLM, Ollama, and any other OpenAI-compatible endpoint can use the identical Request/Response conversion code.
func NewOpenAICompatible ¶
NewOpenAICompatible builds a Provider for any service that speaks the OpenAI Responses API at a different base URL: DeepSeek, Groq, Mistral's la-plateforme, vLLM, Ollama, llama.cpp's server, etc.
The implementation is the OpenAI adapter with the base URL pinned to apiBase and the reported provider name overridden so logs and error messages distinguish "openai-compatible" from the canonical "openai" path -- otherwise a misconfigured api-base would look like a real OpenAI failure.
apiBase is required; an empty base would silently fall back to api.openai.com, which is almost certainly not what the operator asked for when they selected --provider openai-compatible.
func NewOpenAIWithBase ¶
NewOpenAIWithBase mirrors NewOpenAI but lets the caller pin the API base URL. Empty apiBase means "use the SDK default", which is api.openai.com.
type Provider ¶
type Provider interface {
// Name returns a short identifier ("anthropic", "openai",
// "gemini", "openai-compatible") used in logs and error messages.
Name() string
// Complete runs one round-trip against the model. The agent loop
// is responsible for repeated calls when StopReasonToolUse comes
// back; the provider only owns the single network call.
Complete(ctx context.Context, req Request) (*Response, error)
}
Provider is the single seam between the agent and an LLM. An implementation must be safe to call concurrently from independent goroutines; the agent does not assume any per-call sticky state on the provider side beyond what the underlying SDK already provides (HTTP keep-alive, prompt cache, etc.).
type Request ¶
type Request struct {
System string
Messages []Message
Tools []ToolSpec
Model string
MaxTokens int
}
Request is the abstract conversation handed to the model. System goes into whichever first-class field the provider exposes (system, system instruction, developer role) rather than being stuffed into the message list -- providers cache the system prompt differently from user turns and we want them to.
type Response ¶
type Response struct {
Content []ContentBlock
StopReason StopReason
Usage Usage
}
Response is what a single Complete call produced. Content carries the same ContentBlock union, this time populated by the provider (an assistant turn). Usage is best-effort -- some providers do not report per-call usage at all.
type Role ¶
type Role string
Role enumerates who produced a Message. Tool results travel under RoleUser because every provider treats them as user-supplied input to the model regardless of MCP's "tool" role concept.
type StopReason ¶
type StopReason string
StopReason tells the agent loop why the model stopped. EndTurn and ToolUse are the two important cases; MaxTokens and Other surface so the agent can record a useful error rather than retrying blindly.
const ( StopReasonEndTurn StopReason = "end_turn" StopReasonToolUse StopReason = "tool_use" StopReasonMaxTokens StopReason = "max_tokens" StopReasonOther StopReason = "other" )