larzagent
A tiny, zero-dependency AI agent framework. The tool-calling loop,
function-to-schema tooling, and conversation memory you need to build an agent —
in pure Python, over plain urllib, against any OpenAI-compatible endpoint.
from larzagent import Agent, LLM, tool
@tool
def get_price(symbol: str) -> float:
"""Get the current price of a ticker symbol."""
return {"LARZ": 0.0, "BTC": 64000.0}.get(symbol, 0.0)
agent = Agent(
LLM(model="gpt-4o-mini", api_key="sk-...",
base_url="https://api.openai.com/v1"),
system="You are a terse market assistant.",
tools=[get_price],
)
print(agent.run("What's BTC trading at?"))
# -> the model calls get_price("BTC"), sees 64000.0, and answers.
No SDK. No async runtime. No dependencies. One small library you can read in an afternoon.
Why
- Zero dependencies. Pure standard library — the model calls go over
urllib. Nothing to install, nothing to compile. - Any backend. It speaks the OpenAI
/v1/chat/completionsshape, so pointbase_urlat OpenAI, your own gateway, Ollama, LM Studio, vLLM, OpenRouter — the same agent code runs against all of them. - Tools are just functions. Decorate a function with
@tooland larzagent reads its signature, type hints, and docstring to build the JSON schema. No hand-written schemas, no drift. - Robust loop. Tool errors, unknown tools, and bad JSON arguments are fed back to the model so it can recover instead of crashing. A step limit stops runaway loops.
- Observable & persistable. Every model call and tool result flows through
Memory, so it's trivial to log, save, replay, or inspect. - Testable offline. Inject a
transport=callable and drive the whole loop with canned responses — no network, no keys. (That's how this repo's 26 tests run.)
Install
pip install larzagent
Tools from plain functions
from larzagent import tool
@tool
def search(query: str, limit: int = 5) -> list:
"""Search the knowledge base."""
return kb.search(query)[:limit]
larzagent turns that into:
{"type": "function", "function": {
"name": "search",
"description": "Search the knowledge base.",
"parameters": {"type": "object",
"properties": {"query": {"type": "string"}, "limit": {"type": "integer"}},
"required": ["query"]}}}
The decorated function is still directly callable in normal code (search("x")),
so your tools are just... functions.
The loop
agent.run(message) does the standard agentic loop:
- Send
system+ memory + the new user message to the model, with your tools. - If the model returns tool calls, run each one, append the results, and go back to step 1.
- When the model returns plain text, that's the answer.
Errors are handled defensively — a tool that raises, a call to a tool that
doesn't exist, or malformed arguments all get returned to the model as a tool
result it can react to, rather than blowing up your program. max_steps
(default 8) guards against loops.
# trace every step
agent = Agent(llm, tools=[...], on_step=lambda step, msg, results: print(step, results))
# one-shot call that doesn't mutate memory
answer = agent.ask("quick question")
# persist / resume a conversation
agent.memory.save("session.json")
Point it at your own models
# OpenAI
LLM(model="gpt-4o-mini", api_key="sk-...", base_url="https://api.openai.com/v1")
# a local Ollama
LLM(model="llama3.1", base_url="http://localhost:11434/v1")
# your own gateway
LLM(model="my-model", api_key="...", base_url="https://gateway.example.com/v1")
API at a glance
@tool / @tool(name=, description=) |
make a function callable by the model |
LLM(model, api_key=, base_url=, transport=, ...) |
OpenAI-compatible client |
Agent(llm, system=, tools=, memory=, max_steps=, on_step=) |
the agent |
agent.run(msg) → str |
run the tool-calling loop, return the answer |
agent.ask(msg) |
one-shot; leaves memory unchanged |
agent.add_tool(func) |
register a tool at runtime |
Memory(messages=, max_messages=) · .save() · .load() |
conversation state |
Scope
larzagent is intentionally small: the loop, tools, and memory done well. It is not (yet) a streaming, multi-agent, or RAG-orchestration framework — it's the solid core you build those on. Bring your own vector store, your own model, your own app.
Tests
python -m unittest discover -s tests -v # 26 tests, no network, zero deps
The Larz stack
Pure-Python, zero-dependency building blocks:
- larz — money-native web framework
- larzchain — from-scratch PoW blockchain
- larzmoney — exact, penny-perfect money
- larzcrypt — pure-Python cryptography toolkit
- larzdb — crash-safe embedded database
- larzagent — this framework
License
MIT © larz-scripter
Metadata
Release files for larzagent 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| larzagent-0.1.0.tar.gz | 14.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| larzagent-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 25.8 kB
Release files / larzagent-0.1.0.tar.gz
| Download URL | larzagent-0.1.0.tar.gz |
|---|---|
| Size | 14.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0177d5df14c8ac82907a7fa943930c08e09f1d70dccb3e99cc4c8f8e3b0f9e83
|
|
BLAKE2b-256 checksum How to use checksums |
efcbb730f325c88455f8f99694e7b593eb38d2015c9c6bec529444bbc0b9afda
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / larzagent-0.1.0-py3-none-any.whl
| Download URL | larzagent-0.1.0-py3-none-any.whl |
|---|---|
| Size | 11.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f4a6dd2c60e9723ef503ee54cd767f0708dc55f5d102881f2308a1a548c9ec23
|
|
BLAKE2b-256 checksum How to use checksums |
43ee2dc10e605447b08ca4912b4f51b88e153b97817a2396cedcf99d888e992a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|