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-streaming —
stream_simple()/complete_simple()with async iteration - Model catalog —
get_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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pi_llm-0.1.4.tar.gz.
File metadata
- Download URL: pi_llm-0.1.4.tar.gz
- Upload date:
- Size: 46.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5bbef3018016196488e4f40cf74c57426c24e5600d23639dae780f49859dc0bd
|
|
| MD5 |
a11d62f284e1e0249adc7e3055db6b97
|
|
| BLAKE2b-256 |
ce6e8e6f445ce0a96b38533c35f433e7ad0804eb559eafe59fea5e633f9294f7
|
File details
Details for the file pi_llm-0.1.4-py3-none-any.whl.
File metadata
- Download URL: pi_llm-0.1.4-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.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e1d7828dd2e446b6ae6f734f91fdfb4322cba9b943f48bc2c6bc2bc25a06904f
|
|
| MD5 |
c466548741958b423e29272c57fe49fc
|
|
| BLAKE2b-256 |
475c10bf75aa07a088c9c86c488f3c245de9237fac5bbfb6c278e3edc865f95d
|