Skip to content

LLM providers

The vendor-neutral provider protocol and its implementations (OpenAI, Anthropic, and a deterministic mock for tests), so the agent loop is decoupled from any single LLM vendor. The real providers import their SDKs lazily, so this layer is importable without the optional llm extra and only fails if a real provider is instantiated without its SDK.

AnthropicProvider defaults to Claude Opus 5 (claude-opus-5) and OpenAIProvider to gpt-5-mini. Both send a sampling temperature only when a caller passes one, cap each reply at 16,000 tokens by default, and normalize the model's stop reason (end_turn, tool_use, max_tokens, refusal, pause_turn, or stop_sequence) so the agent loops can act on it.

llm

Vendor-neutral LLM provider layer for the Fugacio copilot.

Importing this package pulls in only the neutral types and the in-memory MockProvider; the real providers (OpenAIProvider, AnthropicProvider) import their SDKs lazily, so they are importable here without the optional llm extra installed and only fail if instantiated without the SDK.

Modules:

Name Description
anthropic

Anthropic (Claude) Messages-API adapter for the Fugacio copilot.

base

Provider-neutral chat/tool-calling types for the Fugacio copilot.

mock

A deterministic in-memory provider for testing the agent loop without an API.

openai

OpenAI chat-completions adapter for the Fugacio copilot.

Classes:

Name Description
AnthropicProvider

An LLMProvider backed by the Anthropic Messages API.

ChatResponse

A model's reply: free-text content and/or requested tool calls.

LLMProvider

A function-calling chat model.

Message

One turn in a chat transcript.

ToolCall

A model's request to invoke a tool.

MockProvider

A scripted LLMProvider.

OpenAIProvider

An LLMProvider backed by the OpenAI API.

Functions:

Name Description
anthropic_tools

Map engine tool schemas to Anthropic's {"name", "description", "input_schema"}.

openai_tools

Wrap engine tool schemas in OpenAI's {"type": "function", ...} envelope.

AnthropicProvider

AnthropicProvider(
    model: str = "claude-opus-5",
    *,
    client: Any | None = None,
    api_key: str | None = None,
    fallbacks: str | None = "default",
)

An LLMProvider backed by the Anthropic Messages API.

Parameters:

Name Type Description Default
model str

Claude model id.

'claude-opus-5'
client Any | None

An existing anthropic.Anthropic client; one is created if omitted.

None
api_key str | None

API key passed to a freshly created client.

None
fallbacks str | None

Server-side refusal-fallback mode. With the default, "default", requests go to the beta Messages endpoint with the FALLBACK_BETA header, and the API routes a declined request to Anthropic's recommended fallback model for the refusal category. None disables fallbacks and uses the standard endpoint, as platforms without server-side fallbacks (Amazon Bedrock, Vertex AI, and Microsoft Foundry) require.

'default'

Methods:

Name Description
chat

Call the Messages API and parse the reply into a ChatResponse.

chat

chat(
    messages: Sequence[Message],
    *,
    tools: Sequence[JsonDict] = (),
    temperature: float | None = None,
    max_tokens: int = 16000,
) -> ChatResponse

Call the Messages API and parse the reply into a ChatResponse.

Parameters:

Name Type Description Default
messages Sequence[Message]

The conversation so far; system messages become the top-level system prompt.

required
tools Sequence[JsonDict]

Engine tool schemas the model may call.

()
temperature float | None

Sampling temperature. None (the default) omits it, as recent Claude models, including Claude Opus 5, require.

None
max_tokens int

Per-reply output token cap, thinking included.

16000

Returns:

Type Description
ChatResponse

The parsed reply. A "refusal" reply carries no content, and a

ChatResponse

"max_tokens" reply keeps its text but drops its tool calls.

ChatResponse dataclass

ChatResponse(
    content: str = "",
    tool_calls: tuple[ToolCall, ...] = (),
    stop_reason: str | None = None,
    provider_blocks: tuple[Any, ...] = (),
    raw: Any = None,
)

A model's reply: free-text content and/or requested tool calls.

Check stop_reason before trusting the content: a "refusal" reply carries no usable content, and a "max_tokens" reply was cut off.

Attributes:

Name Type Description
content str

The assistant's text (the final answer when there are no calls).

tool_calls tuple[ToolCall, ...]

Any tool calls the model wants executed before continuing.

stop_reason str | None

Why the model stopped, normalized as described in the module docstring, or None when unknown.

provider_blocks tuple[Any, ...]

Opaque provider content blocks (for example, Anthropic thinking and redacted_thinking blocks) that must be echoed verbatim when this reply is replayed; pass them to Message.assistant.

raw Any

The provider's raw response object, for debugging (not portable).

has_tool_calls property

has_tool_calls: bool

Whether the model requested at least one tool call.

LLMProvider

Bases: Protocol

A function-calling chat model.

Implementations translate the neutral Message / tool-schema inputs to their own API and parse the reply into a ChatResponse.

Methods:

Name Description
chat

Return the model's reply to messages with tools available.

chat

chat(
    messages: Sequence[Message],
    *,
    tools: Sequence[JsonDict] = (),
    temperature: float | None = None,
    max_tokens: int = 16000,
) -> ChatResponse

Return the model's reply to messages with tools available.

Parameters:

Name Type Description Default
messages Sequence[Message]

The conversation so far.

required
tools Sequence[JsonDict]

Engine tool schemas the model may call.

()
temperature float | None

Sampling temperature. None (the default) omits the parameter, which models that reject sampling parameters require.

None
max_tokens int

Per-reply output token cap.

16000

Returns:

Type Description
ChatResponse

The parsed ChatResponse.

Message dataclass

Message(
    role: str,
    content: str = "",
    tool_calls: tuple[ToolCall, ...] = (),
    tool_call_id: str | None = None,
    name: str | None = None,
    is_error: bool = False,
    provider_blocks: tuple[Any, ...] = (),
)

One turn in a chat transcript.

Attributes:

Name Type Description
role str

"system", "user", "assistant" or "tool".

content str

Text content (may be empty for an assistant tool-call turn).

tool_calls tuple[ToolCall, ...]

Tool calls requested by an assistant turn.

tool_call_id str | None

For a "tool" turn, the id of the call it answers.

name str | None

For a "tool" turn, the tool name (some providers want it).

is_error bool

For a "tool" turn, whether the call failed.

provider_blocks tuple[Any, ...]

For an assistant turn, the opaque provider blocks (ChatResponse.provider_blocks) its provider echoes back verbatim.

Methods:

Name Description
system

A system instruction message.

user

A user message.

assistant

An assistant message, optionally requesting tool calls.

tool

A tool-result message answering a specific tool call.

system classmethod

system(content: str) -> Message

A system instruction message.

user classmethod

user(content: str) -> Message

A user message.

assistant classmethod

assistant(
    content: str = "",
    tool_calls: Sequence[ToolCall] = (),
    provider_blocks: Sequence[Any] = (),
) -> Message

An assistant message, optionally requesting tool calls.

Parameters:

Name Type Description Default
content str

The assistant's text.

''
tool_calls Sequence[ToolCall]

Tool calls the assistant requested.

()
provider_blocks Sequence[Any]

The reply's ChatResponse.provider_blocks, kept so the provider can echo them on the next request.

()

Returns:

Type Description
Message

The assistant Message.

tool classmethod

tool(
    content: str,
    tool_call_id: str,
    name: str | None = None,
    *,
    is_error: bool = False,
) -> Message

A tool-result message answering a specific tool call.

Parameters:

Name Type Description Default
content str

The serialized tool result.

required
tool_call_id str

Id of the call this message answers.

required
name str | None

The tool name.

None
is_error bool

Whether the call failed (content then explains why).

False

Returns:

Type Description
Message

The tool-result Message.

ToolCall dataclass

ToolCall(id: str, name: str, arguments: JsonDict = dict())

A model's request to invoke a tool.

Attributes:

Name Type Description
id str

Provider-assigned call id (echoed back with the result).

name str

Tool name (must exist in the registry).

arguments JsonDict

Parsed JSON arguments for the tool. When the provider couldn't parse them, they hold the INVALID_ARGUMENTS and ARGUMENTS_ERROR markers instead (see invalid).

Methods:

Name Description
invalid

A call whose raw arguments couldn't be parsed into a JSON object.

invalid classmethod

invalid(
    call_id: str, name: str, raw: str, error: str
) -> ToolCall

A call whose raw arguments couldn't be parsed into a JSON object.

The agent loops don't run such a call. They answer it with an error result quoting raw and error, so the model can resend it.

Parameters:

Name Type Description Default
call_id str

Provider-assigned call id.

required
name str

Requested tool name.

required
raw str

The argument text exactly as the model produced it.

required
error str

Why the text isn't a valid JSON object.

required

Returns:

Type Description
ToolCall

A ToolCall whose arguments carry the parse-failure markers.

MockProvider dataclass

MockProvider(
    script: Script,
    calls: list[
        tuple[list[Message], list[JsonDict]]
    ] = list(),
    call_kwargs: list[JsonDict] = list(),
    _index: int = 0,
)

A scripted LLMProvider.

Attributes:

Name Type Description
script Script

Either a list of replies returned in order, or a function (messages) -> ChatResponse evaluated on each call.

calls list[tuple[list[Message], list[JsonDict]]]

Recorded (messages, tools) for each chat invocation.

call_kwargs list[JsonDict]

Recorded {"temperature", "max_tokens"} for each chat invocation.

Methods:

Name Description
chat

Return the next scripted reply (or evaluate the script callable).

chat

chat(
    messages: Sequence[Message],
    *,
    tools: Sequence[JsonDict] = (),
    temperature: float | None = None,
    max_tokens: int = 16000,
) -> ChatResponse

Return the next scripted reply (or evaluate the script callable).

OpenAIProvider

OpenAIProvider(
    model: str = "gpt-5-mini",
    *,
    client: Any | None = None,
    api_key: str | None = None,
)

An LLMProvider backed by the OpenAI API.

Parameters:

Name Type Description Default
model str

Chat model name.

'gpt-5-mini'
client Any | None

An existing openai.OpenAI client; one is created if omitted.

None
api_key str | None

API key passed to a freshly created client.

None

Methods:

Name Description
chat

Call chat-completions and parse the reply into a ChatResponse.

chat

chat(
    messages: Sequence[Message],
    *,
    tools: Sequence[JsonDict] = (),
    temperature: float | None = None,
    max_tokens: int = 16000,
) -> ChatResponse

Call chat-completions and parse the reply into a ChatResponse.

Parameters:

Name Type Description Default
messages Sequence[Message]

The conversation so far.

required
tools Sequence[JsonDict]

Engine tool schemas the model may call.

()
temperature float | None

Sampling temperature. None (the default) omits it, as reasoning models require.

None
max_tokens int

Per-reply output token cap, sent as max_completion_tokens.

16000

Returns:

Type Description
ChatResponse

The parsed reply. A "refusal" reply (a content-filter stop or a

ChatResponse

model refusal) carries no content, and a "max_tokens" reply

ChatResponse

keeps its text but drops its tool calls.

anthropic_tools

anthropic_tools(
    schemas: Sequence[JsonDict],
) -> list[JsonDict]

Map engine tool schemas to Anthropic's {"name", "description", "input_schema"}.

openai_tools

openai_tools(schemas: Sequence[JsonDict]) -> list[JsonDict]

Wrap engine tool schemas in OpenAI's {"type": "function", ...} envelope.