pi-llm-agent
General-purpose agent framework with tool execution and event streaming. Built on pi-llm.
Features
- Agent class — Stateful wrapper managing conversation, tools, and lifecycle
- Tool execution — Subclass
AgentToolor usefrom_function()factory - Parallel tools — Execute tool calls concurrently (default) or sequentially
- Event streaming — Subscribe to 10 event types for live UI updates
- Hooks —
before_tool_call/after_tool_callfor access control and post-processing - Steering & follow-up — Queue messages to interrupt or extend agent runs
- Cancellation — Cooperative cancellation via
CancellationToken
Installation
pip install pi-llm-agent
Requires pi-llm (installed automatically as a dependency).
Quick Start
import asyncio
from pi_llm_agent import Agent, AgentOptions, InitialAgentState, AgentTool, AgentToolResult
from pi_llm import get_model, TextContent, stream_simple
from pi_llm.providers import register_builtin_providers
register_builtin_providers()
class WeatherTool(AgentTool):
def __init__(self):
super().__init__(
name="get_weather",
label="Get Weather",
description="Get current weather for a city",
parameters={
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
)
async def execute(self, tool_call_id, params, cancellation=None, on_update=None):
return AgentToolResult(
content=[TextContent(text=f"25°C and sunny in {params['city']}")]
)
async def main():
agent = Agent(AgentOptions(
initial_state=InitialAgentState(
model=get_model("openai", "gpt-4o"),
system_prompt="You are a helpful assistant with weather access.",
tools=[WeatherTool()],
),
stream_fn=stream_simple,
get_api_key=lambda _: "sk-...",
))
# Subscribe to events for live output
agent.subscribe(lambda event, cancel: (
print(event.type) if event.type != "message_update" else None
))
await agent.prompt("What's the weather in Paris?")
asyncio.run(main())
Event Flow
Simple prompt (no tools)
prompt("Hello")
├─ agent_start
├─ turn_start
├─ message_start { user message }
├─ message_end { user message }
├─ message_start { assistant message }
├─ message_update { streaming chunks... }
├─ message_end { assistant message }
├─ turn_end
└─ agent_end
With tool calls
prompt("Weather in Tokyo?")
├─ agent_start
├─ turn_start
├─ message_start/end { user message }
├─ message_start { assistant + tool call }
├─ message_update...
├─ message_end
├─ tool_execution_start { get_weather, {city: "Tokyo"} }
├─ tool_execution_end { result }
├─ message_start/end { tool result message }
├─ turn_end
│
├─ turn_start { next turn }
├─ message_start { final assistant response }
├─ message_update...
├─ message_end
├─ turn_end
└─ agent_end
Agent Options
agent = Agent(AgentOptions(
initial_state=InitialAgentState(
system_prompt="...",
model=get_model("openai", "gpt-4o"),
thinking_level="off", # off, minimal, low, medium, high, xhigh
tools=[my_tool],
messages=[], # pre-existing history
),
stream_fn=stream_simple, # custom stream function
get_api_key=lambda provider: "...", # dynamic API key
tool_execution="parallel", # parallel (default) or sequential
steering_mode="one-at-a-time", # how steering queue drains
follow_up_mode="one-at-a-time", # how follow-up queue drains
# Hooks
before_tool_call=my_before_hook, # block or inspect before execution
after_tool_call=my_after_hook, # override results after execution
))
Agent State
agent.state.system_prompt = "New prompt"
agent.state.model = get_model("openai", "gpt-4o-mini")
agent.state.thinking_level = "medium"
agent.state.tools = [new_tool]
agent.state.messages # conversation transcript
agent.state.is_streaming # True during processing
agent.state.streaming_message # partial message being streamed
agent.state.pending_tool_calls # set of tool call IDs in progress
Methods
# Prompting
await agent.prompt("Hello")
await agent.prompt("Describe this", images=[image_content])
await agent.continue_() # resume from current state
# Control
agent.abort() # cancel current run
await agent.wait_for_idle() # wait for completion
agent.reset() # clear everything
# Events
unsubscribe = agent.subscribe(listener) # returns unsubscribe fn
unsubscribe()
# Steering (interrupt between turns)
agent.steer(user_message)
agent.follow_up(user_message)
agent.clear_all_queues()
Hooks
async def my_before_hook(context, cancellation):
"""Block dangerous tools."""
if context.tool_call.name == "delete_file":
return BeforeToolCallResult(block=True, reason="Deletion not allowed")
return None # allow execution
async def my_after_hook(context, cancellation):
"""Annotate results."""
return AfterToolCallResult(
details={**context.result.details, "audited": True}
)
Low-Level API
For direct control without the Agent class:
from pi_llm_agent import agent_loop, AgentContext, AgentLoopConfig
context = AgentContext(
system_prompt="You are helpful.",
messages=[],
tools=[],
)
config = AgentLoopConfig(
model=get_model("openai", "gpt-4o"),
convert_to_llm=lambda msgs: [m for m in msgs if m.role in ("user", "assistant", "toolResult")],
)
stream = agent_loop([user_message], context, config)
async for event in stream:
print(event.type)
License
MIT
Metadata
Release files for pi-llm-agent 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pi_llm_agent-0.1.4.tar.gz | 27.3 kB | Without provenance | Details for pi_llm_agent-0.1.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pi_llm_agent-0.1.4-py3-none-any.whl | Python 3 | none | any | Details for pi_llm_agent-0.1.4-py3-none-any.whl |
Total release size: 46.7 kB
Release files / pi_llm_agent-0.1.4.tar.gz
| Download URL | pi_llm_agent-0.1.4.tar.gz |
|---|---|
| Size | 27.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
71b8e92868aec6d0b4aedf5735ade677370033538b6aa345a2f6e7d920dc1fe6
|
|
BLAKE2b-256 checksum How to use checksums |
1d80ec14b86140577e980c9e26c0876c25b021d3fe099e15eabbdb9cc504cd90
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / pi_llm_agent-0.1.4-py3-none-any.whl
| Download URL | pi_llm_agent-0.1.4-py3-none-any.whl |
|---|---|
| Size | 19.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3344d28b3adae3a30f50395aa2c4290e6d59235ba668c055fc6b431224db993d
|
|
BLAKE2b-256 checksum How to use checksums |
71c22ebaaa7828c68b78ec0b80e5de394100bf7cac14a50aeacfb671662293df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Security for pi-llm-agent 0.1.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.