Skip to main content

Unified LLM provider abstraction for Python — streaming, tool calling, extended thinking, cost tracking

Project description

pi-llm

Unified LLM provider abstraction for Python. Provides streaming and non-streaming interfaces to LLM APIs with automatic model discovery, token and cost tracking, and tool call support.

Features

  • Streaming & non-streamingstream_simple() / complete_simple() with async iteration
  • Model catalogget_model() with built-in pricing and metadata for OpenAI models
  • Tool calling — Define tools with JSON Schema, validate arguments automatically
  • Extended thinking — Unified reasoning level interface across providers
  • Cost tracking — Automatic token counting and dollar cost calculation
  • Event-driven — Fine-grained streaming events (text, thinking, tool calls)
  • Cross-provider types — Pydantic models with camelCase/snake_case interop

Supported Providers

  • OpenAI (Responses API)

More providers coming soon.

Installation

pip install pi-llm

Quick Start

import asyncio
from pi_llm import get_model, stream_simple, complete_simple, Context, UserMessage
from pi_llm import register_builtin_providers, SimpleStreamOptions

# Register providers (call once at startup)
register_builtin_providers()

model = get_model("openai", "gpt-4o")

context = Context(
    system_prompt="You are a helpful assistant.",
    messages=[UserMessage(content="What is Python?", timestamp=0)],
)

# Option 1: Non-streaming
async def main():
    message = await complete_simple(
        model, context,
        SimpleStreamOptions(api_key="sk-...")
    )
    for block in message.content:
        if hasattr(block, "text"):
            print(block.text)

asyncio.run(main())
# Option 2: Streaming
async def main_stream():
    event_stream = stream_simple(
        model, context,
        SimpleStreamOptions(api_key="sk-...")
    )
    async for event in event_stream:
        if event.type == "text_delta":
            print(event.delta, end="", flush=True)

asyncio.run(main_stream())

Tools

Define tools with JSON Schema and handle tool calls in a loop:

from pi_llm import Tool, ToolCall, ToolResultMessage, TextContent
import time

weather_tool = Tool(
    name="get_weather",
    description="Get current weather for a city",
    parameters={
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
    },
)

context = Context(
    system_prompt="You can check the weather.",
    messages=[UserMessage(content="What's the weather in Tokyo?", timestamp=0)],
    tools=[weather_tool],
)

async def tool_loop():
    while True:
        message = await complete_simple(
            model, context,
            SimpleStreamOptions(api_key="sk-...")
        )
        context.messages.append(message)

        # Check for tool calls
        tool_calls = [c for c in message.content if isinstance(c, ToolCall)]
        if not tool_calls:
            break  # No more tool calls, done

        # Execute tool calls and add results
        for tc in tool_calls:
            result = f"25°C and sunny in {tc.arguments['city']}"
            context.messages.append(ToolResultMessage(
                tool_call_id=tc.id,
                tool_name=tc.name,
                content=[TextContent(text=result)],
                timestamp=int(time.time() * 1000),
            ))

    # Print final response
    for block in message.content:
        if hasattr(block, "text"):
            print(block.text)

Streaming Events

The event stream emits these events in order:

Event Description
StartEvent Streaming begins
TextStartEvent Text block begins
TextDeltaEvent Incremental text chunk
TextEndEvent Text block complete
ThinkingStartEvent Reasoning block begins
ThinkingDeltaEvent Reasoning chunk
ThinkingEndEvent Reasoning block complete
ToolCallStartEvent Tool call begins
ToolCallDeltaEvent Tool call argument chunk
ToolCallEndEvent Tool call complete
DoneEvent Streaming finished successfully
ErrorEvent Streaming ended with error

Extended Thinking

Enable reasoning for supported models:

message = await complete_simple(
    model, context,
    SimpleStreamOptions(
        api_key="sk-...",
        reasoning="medium",  # minimal, low, medium, high, xhigh
    )
)

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pi_llm-0.1.2.tar.gz (46.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pi_llm-0.1.2-py3-none-any.whl (36.6 kB view details)

Uploaded Python 3

File details

Details for the file pi_llm-0.1.2.tar.gz.

File metadata

  • Download URL: pi_llm-0.1.2.tar.gz
  • Upload date:
  • Size: 46.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for pi_llm-0.1.2.tar.gz
Algorithm Hash digest
SHA256 bd3ae075aafe7350bb564cd9e306e2744933bfc0686c7ecba4bed60494a87b75
MD5 f9d511f527c2a44af2abf0a6fa6141eb
BLAKE2b-256 9220e8babebeaa3e7b9e16d064a13c07c676918a5aa74d0eb551d01b08be6531

See more details on using hashes here.

File details

Details for the file pi_llm-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: pi_llm-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 36.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for pi_llm-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 f0cc028831d9eb0acca0fd861f5f00c0a46b925258a93028418ff7dc2614175d
MD5 8afcfa0162c423c9c75c8d067d71af7e
BLAKE2b-256 f28882540c2ffbd1067859a11b8d1e27c2daa1afaa710f29f3bd0330060d8744

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page