aboutsummaryrefslogtreecommitdiffstats

multillm

A unified async interface for multiple LLM providers with agentic capabilities. Switch between providers with a single line change.

Features

  • 🔄 Unified API - Same interface for OpenAI, Anthropic, Google, and more
  • 🤖 Agentic by default - Automatic tool execution and multi-turn conversations
  • 🛠️ Interactive tools - AI can ask you questions during execution
  • 📦 Provider flexibility - Switch providers without changing code
  • 🎯 Simple CLI - Quick testing and experimentation
  • Async-first - Built on asyncio for performance

Quick Start

CLI

# Install
pip install multillm-cli multillm-openai

# Simple query
multillm -m openai/gpt-4o -p "What is 2+2?"

# With interactive tools
multillm -m openai/gpt-4o -p "Ask me about my preferences" --use-tools ask_user

# With other tools
multillm -m openai/gpt-4o -p "What's the weather in Tokyo?" --use-tools get_weather

See multillm-cli for full CLI documentation.

Python API

pip install multillm multillm-openai multillm-anthropic

Simple query:

import asyncio
import multillm

async def main():
    client = multillm.Client()

    # Agentic API - works with any provider
    async for msg in client.run("agentwrap/openai/gpt-4o", "Hello!"):
        if msg.type == "text":
            print(msg.content)

asyncio.run(main())

With tools:

import asyncio
import multillm

# Define a tool
calculate = multillm.Tool(
    name="calculate",
    description="Perform a calculation",
    parameters={
        "type": "object",
        "properties": {
            "expression": {"type": "string"}
        },
        "required": ["expression"]
    },
    handler=lambda args: {"result": eval(args["expression"])}
)

async def main():
    client = multillm.Client()

    # AI can use tools automatically
    async for msg in client.run(
        "agentwrap/openai/gpt-4o",
        "What's 25 * 4?",
        tools=[calculate]
    ):
        if msg.type == "text":
            print(msg.content)
        elif msg.type == "tool_use":
            print(f"Using tool: {msg.tool_name}")

asyncio.run(main())

Interactive tools:

import asyncio
import multillm

# Define interactive tool
ask_user = multillm.Tool(
    name="ask_user",
    description="Ask the user a question",
    parameters={
        "type": "object",
        "properties": {
            "question": {"type": "string"}
        },
        "required": ["question"]
    },
    handler=lambda args: {
        "answer": input(f"\n{args['question']}\nYour answer: ")
    }
)

async def main():
    client = multillm.Client()

    # AI can ask you questions!
    async for msg in client.run(
        "agentwrap/openai/gpt-4o",
        "Help me plan a project by asking about my requirements",
        tools=[ask_user]
    ):
        if msg.type == "text":
            print(msg.content)

asyncio.run(main())

Packages

Package Description
multillm Core library with unified client
multillm-cli Command-line interface
Chat Providers
multillm-openai OpenAI GPT models
multillm-anthropic Anthropic Claude chat API
multillm-gemini Google Gemini
multillm-openrouter OpenRouter (access to 100+ models)
Agent Providers
multillm-agentwrap Wrap chat providers with agentic capabilities
multillm-claude Claude native agent with built-in tools

How It Works

The Agentic API

All providers use the same agentic API powered by run():

async for msg in client.run(model, prompt, tools=tools):
    # Process messages

Message types: - system - Session started - text - Text response from AI - tool_use - AI is calling a tool - tool_result - Tool execution result - result - Final result

Provider Format

Chat providers with agentwrap:

"agentwrap/openai/gpt-4o"
"agentwrap/google/gemini-pro"
"agentwrap/anthropic/claude-3-5-sonnet-20241022"

Native agent providers:

"claude/default"
"claude/claude-sonnet-4-20250514"

What is agentwrap?

agentwrap wraps standard chat providers (OpenAI, Google, etc.) with agentic capabilities: - ✅ Automatic tool execution - ✅ Multi-turn conversations - ✅ Tool calling loop - ✅ Conversation history management

This means any chat model can work like an agent!

Interactive Tools

AI models can ask you questions during execution:

CLI:

# Chat providers
multillm -m openai/gpt-4o -p "Ask me about my project" --use-tools ask_user

# Claude agent
multillm -m claude/default -p "Ask me about my project" \
  --allowed-tools AskUserQuestion --permission-mode acceptEdits

Python:

ask_user_tool = multillm.Tool(
    name="ask_user",
    description="Ask the user a question",
    parameters={"type": "object", "properties": {"question": {"type": "string"}}},
    handler=lambda args: {"answer": input(f"{args['question']}\nYour answer: ")}
)

async for msg in client.run("agentwrap/openai/gpt-4o", "Ask me questions", tools=[ask_user_tool]):
    if msg.type == "text":
        print(msg.content)

When the AI calls the tool, you'll see:

======================================================================
❓ QUESTION FROM ASSISTANT
======================================================================

What is your favorite programming language?

Your answer: _

See CLI documentation for more interactive tool examples.

Configuration

Environment Variables

export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-...
export GOOGLE_API_KEY=...

Programmatic

client = multillm.Client(config={
    "openai": {"api_key": "sk-..."},
    "anthropic": {"api_key": "sk-ant-..."},
})

Config Files

Create ~/.config/multillm/providers/<provider>.json:

{
  "api_key": "sk-..."
}

See provider-specific documentation for all options: - OpenAI configuration - Anthropic configuration - Google Gemini configuration - Claude Agent configuration

Examples

Chat with Different Providers

import asyncio
import multillm

async def chat(model: str, prompt: str):
    client = multillm.Client()
    async for msg in client.run(model, prompt):
        if msg.type == "text":
            print(msg.content)

# All use the same API!
asyncio.run(chat("agentwrap/openai/gpt-4o", "Hello"))
asyncio.run(chat("agentwrap/google/gemini-pro", "Hello"))
asyncio.run(chat("agentwrap/anthropic/claude-3-5-sonnet-20241022", "Hello"))
asyncio.run(chat("claude/default", "Hello"))

Custom Tools

import asyncio
import multillm
from datetime import datetime

# Define custom tools
get_time = multillm.Tool(
    name="get_current_time",
    description="Get the current time",
    parameters={"type": "object", "properties": {}},
    handler=lambda args: {"time": datetime.now().isoformat()}
)

weather = multillm.Tool(
    name="get_weather",
    description="Get weather for a location",
    parameters={
        "type": "object",
        "properties": {"location": {"type": "string"}},
        "required": ["location"]
    },
    handler=lambda args: {"temp": 72, "condition": "sunny"}
)

async def main():
    client = multillm.Client()

    async for msg in client.run(
        "agentwrap/openai/gpt-4o",
        "What time is it and what's the weather in Tokyo?",
        tools=[get_time, weather]
    ):
        if msg.type == "text":
            print(msg.content)

asyncio.run(main())

Agent Options

import asyncio
import multillm

async def main():
    client = multillm.Client()

    options = multillm.AgentOptions(
        max_turns=10,  # Max tool execution iterations
        extra={
            "temperature": 0.7,
            "max_tokens": 2000
        }
    )

    async for msg in client.run(
        "agentwrap/openai/gpt-4o",
        "Complex task requiring multiple steps",
        options=options
    ):
        if msg.type == "text":
            print(msg.content)

asyncio.run(main())

Claude Native Agent

Claude has a native agent provider with built-in tools:

import asyncio
import multillm

async def main():
    client = multillm.Client()

    async for msg in client.run(
        "claude/default",
        "List Python files in current directory",
        options=multillm.AgentOptions(
            allowed_tools=["Bash", "Glob"],
            permission_mode="acceptEdits",
            max_turns=5
        )
    ):
        if msg.type == "text":
            print(msg.content)

asyncio.run(main())

Built-in tools: Bash, Read, Write, Edit, Glob, Grep, Task, WebFetch, WebSearch, and more.

See Claude Agent documentation for details.

Development

This is a uv workspace:

# Install
uv sync

# Run examples
uv run python examples/test-agentwrap.py
uv run python examples/test-interactive-tools.py

# Run CLI
uv run multillm -m openai/gpt-4o -p "Hello"

Documentation

Migration from single()

If you're using the deprecated single() API:

Old:

result = await client.single("openai/gpt-4o", "Hello")
print(result.text)

New:

async for msg in client.run("agentwrap/openai/gpt-4o", "Hello"):
    if msg.type == "text":
        print(msg.content)

See Migration Guide for details.

License

MIT