llm

On this page

A small, dependency-light unified interface for calling LLM providers from Python. One consistent shape — generate(), generate_async(), astream() — over seven backends, with unified tool/function-calling support across all seven.

ProviderClassBacking SDK
OpenAIOpenAIInterfaceopenai
Anthropic (Claude)AnthropicInterfaceanthropic
Google GeminiGeminiInterfacegoogle-genai
Mistral AIMistralInterfacemistralai
GroqGroqInterfacegroq
Ollama (local)OllamaInterfacerequests / aiohttp

All seven inherit from BaseLLMInterface, an ABC that defines the contract every provider follows.

[!NOTE] Scope of this documentation This documentation covers exactly what's in the llm/ package: 11 modules, ~2,100 lines. It does not describe a larger "Zerda" ecosystem — only what exists in this specific package is documented here, and every claim below is checked against the source in llm/.

Why this exists

Swapping LLM providers normally means rewriting call sites because every SDK has its own client shape, message format, streaming API, and function-calling schema. This package normalizes that: construct any interface with a model_name and api_key, then call the same three methods regardless of provider — and describe tools once, in one schema, regardless of provider.

Python · 4 lines
from zerda.llm import OpenAIInterface, AnthropicInterface

for llm in [OpenAIInterface(api_key="sk-..."), AnthropicInterface(api_key="sk-ant-...")]:
    print(llm.generate(prompt="Explain RAG in one sentence."))

Tool / function calling

All seven interfaces accept a tools argument (and an optional tool_choice) on generate() and generate_async(), using one unified schema regardless of provider:

Python · 13 lines
tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather for a city.",
        "input_schema": {  # alias: "parameters"
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    }
]

result = llm.generate(prompt="What's the weather in Cairo?", tools=tools)

If the model doesn't call a tool, the return value is a plain string, exactly as without tools — fully backward compatible. If it does, the return value becomes a dict:

Python · 5 lines
{
    "content": "...",   # or None
    "tool_calls": [{"id": "...", "name": "get_weather", "arguments": {"city": "Cairo"}}],
    "stop_reason": "...",
}

Each interface converts the unified schema and normalizes the response internally — see Tool Calling for the full contract, provider-by-provider conversion details, and the llm.tools module reference.

What it is not

  • Not a prompt-templating or chain-orchestration layer — it only wraps generate calls.
  • Not a token-counting or cost-tracking layer — no usage/cost accounting is implemented.
  • Not a routing/fallback layer — there is no automatic provider failover in this package; the caller chooses the provider explicitly.
  • Not a tool-execution layer — the package converts tool schemas and parses tool-call responses, but it does not call your Python functions for you or manage a multi-turn tool-result loop; you still send the tool's result back yourself as a follow-up message.

Where to start

  • New to the package? Start with Installation then Quickstart.
  • Need the exact contract every interface follows? See Architecture.
  • Calling tools/functions? See Tool Calling.
  • Tuning defaults (temperature, max tokens, provider-specific model names)? See Configuration.
  • Looking for one specific provider's behavior? Jump straight to its page under API Reference in the nav.
  • Hitting an error? See Troubleshooting.