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
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 larzagent-0.1.0.tar.gz.
File metadata
- Download URL: larzagent-0.1.0.tar.gz
- Upload date:
- Size: 14.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0177d5df14c8ac82907a7fa943930c08e09f1d70dccb3e99cc4c8f8e3b0f9e83
|
|
| MD5 |
2740324aaacbb7c91da073c55d5e1f5d
|
|
| BLAKE2b-256 |
efcbb730f325c88455f8f99694e7b593eb38d2015c9c6bec529444bbc0b9afda
|
File details
Details for the file larzagent-0.1.0-py3-none-any.whl.
File metadata
- Download URL: larzagent-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f4a6dd2c60e9723ef503ee54cd767f0708dc55f5d102881f2308a1a548c9ec23
|
|
| MD5 |
f6061e9d37e88984080cc5bc0f2822c9
|
|
| BLAKE2b-256 |
43ee2dc10e605447b08ca4912b4f51b88e153b97817a2396cedcf99d888e992a
|