Metadata-Version: 2.4
Name: larzagent
Version: 0.1.0
Summary: Tiny, zero-dependency AI agent framework: tool-calling loop, function-to-schema tools, and memory over any OpenAI-compatible endpoint.
Author: larz-scripter
License: MIT
Project-URL: Homepage, https://github.com/larz-scripter/larzagent
Project-URL: Repository, https://github.com/larz-scripter/larzagent
Project-URL: Documentation, https://github.com/larz-scripter/larzagent#readme
Project-URL: Issues, https://github.com/larz-scripter/larzagent/issues
Keywords: ai,agent,agents,llm,tool-calling,function-calling,openai,ollama,chatgpt,gpt,assistant,rag,zero-dependency,pure-python
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# 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.

```python
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/completions` shape, so point
  `base_url` at 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 `@tool` and 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

```bash
pip install larzagent
```

## Tools from plain functions

```python
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:

```json
{"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:

1. Send `system` + memory + the new user message to the model, with your tools.
2. If the model returns tool calls, run each one, append the results, and go
   back to step 1.
3. 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.

```python
# 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

```python
# 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

```bash
python -m unittest discover -s tests -v      # 26 tests, no network, zero deps
```

## The Larz stack

Pure-Python, zero-dependency building blocks:

- **[larz](https://github.com/larz-scripter/larz)** — money-native web framework
- **[larzchain](https://github.com/larz-scripter/larzchain)** — from-scratch PoW blockchain
- **[larzmoney](https://github.com/larz-scripter/larzmoney)** — exact, penny-perfect money
- **[larzcrypt](https://github.com/larz-scripter/larzcrypt)** — pure-Python cryptography toolkit
- **[larzdb](https://github.com/larz-scripter/larzdb)** — crash-safe embedded database
- **larzagent** — this framework

## License

MIT © larz-scripter
