Observed since 20 Oct, 21:58 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
A tiny, provider-agnostic framework for building tool-using AI agents.
A tiny, provider-agnostic framework for building tool-using AI agents in Python.
No SDK lock-in, no heavy dependencies (just requests), no magic. One small
Agent class, a @tool decorator, and a clean reason-act loop you can actually
read in one sitting. Works with any OpenAI-compatible endpoint: OpenAI,
OpenRouter, Groq, Together, Mistral, or a local llama.cpp / Ollama server.
from agentforge import Agent, tool
@tool
def add(a: int, b: int) -> int:
"Add two numbers."
return a + b
agent = Agent(model="gpt-4o-mini", tools=[add])
print(agent.run("What is 21 + 21?"))
# -> "21 + 21 equals 42."
Most agent frameworks are huge. If all you want is "call my Python functions from an LLM and loop until done," you shouldn't need 40 dependencies and a graph DSL. AgentForge is ~200 lines of readable code that does exactly that.
base_url at any OpenAI-compatible API.await
the agent from async code.pip install agentforge-mini
Or from source:
git clone https://github.com/kangbaxso/agent-forge
cd agent-forge
pip install -e .
Any function with a docstring becomes a tool. Parameter types come from annotations:
from agentforge import tool
@tool
def get_weather(city: str) -> dict:
"Get the current weather for a city."
...
from agentforge import Agent
agent = Agent(
model="gpt-4o-mini",
tools=[get_weather],
system="You are a concise assistant.",
verbose=True, # log each tool call
max_steps=8, # safety cap on the loop
)
answer = agent.run("What's the weather in Tokyo?")
for token in agent.stream("Explain async IO in one paragraph."):
print(token, end="", flush=True)
Tool-calling rounds run first (non-streamed); once the model is ready to answer in plain text, that answer streams token-by-token.
answer = await agent.arun("What's 21 + 21?")
The provider retries automatically on 429 and 5xx responses and on network
errors, with exponential backoff. Tune it:
agent = Agent(model="gpt-4o-mini", max_retries=5)
After exhausting retries it raises agentforge.ProviderError.
Set two environment variables — that's the whole config:
# OpenAI (default)
export OPENAI_API_KEY=sk-...
# OpenRouter
export OPENAI_BASE_URL=https://openrouter.ai/api/v1
export OPENAI_API_KEY=sk-or-...
# Groq
export OPENAI_BASE_URL=https://api.groq.com/openai/v1
export OPENAI_API_KEY=gsk_...
# Local llama.cpp / Ollama
export OPENAI_BASE_URL=http://localhost:8080/v1
export OPENAI_API_KEY=not-needed
…or pass base_url= / api_key= directly to Agent(...).
export OPENAI_API_KEY=sk-...
python examples/quickstart.py
No API key needed — the provider is mocked:
python tests/test_agent.py
# or
python -m pytest tests/ -q
role: tool messages.max_steps is reached.That's it. The entire loop is in agentforge/agent.py.
MIT — see LICENSE.
pypistats · 38 observations shown. Missing days are unknown. Source category: overall: without mirrors.
| Date / source period | Value | Observed at |
|---|---|---|
| 2026-10-20 – 2026-10-20 | 3 | 2026-10-21T09:52:36.6417321Z |
| 2026-10-18 – 2026-10-18 | 3 | 2026-10-21T09:52:36.6417321Z |
| 2026-10-17 – 2026-10-17 | 7 | 2026-10-21T09:52:36.6417321Z |
| 2026-10-16 – 2026-10-16 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-10-15 – 2026-10-15 | 4 | 2026-10-21T09:52:36.6417321Z |
| 2026-10-14 – 2026-10-14 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-10-13 – 2026-10-13 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-10-12 – 2026-10-12 | 3 | 2026-10-21T09:52:36.6417321Z |
| 2026-10-09 – 2026-10-09 | 2 | 2026-10-21T09:52:36.6417321Z |
| 2026-10-06 – 2026-10-06 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-10-02 – 2026-10-02 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-30 – 2026-09-30 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-27 – 2026-09-27 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-26 – 2026-09-26 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-23 – 2026-09-23 | 2 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-22 – 2026-09-22 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-20 – 2026-09-20 | 3 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-19 – 2026-09-19 | 2 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-16 – 2026-09-16 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-14 – 2026-09-14 | 2 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-13 – 2026-09-13 | 2 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-12 – 2026-09-12 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-10 – 2026-09-10 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-08 – 2026-09-08 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-07 – 2026-09-07 | 3 | 2026-10-21T09:52:36.6417321Z |
| 2026-09-06 – 2026-09-06 | 2 | 2026-10-21T09:52:36.6417321Z |
| 2026-08-31 – 2026-08-31 | 2 | 2026-10-21T09:52:36.6417321Z |
| 2026-08-28 – 2026-08-28 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-08-24 – 2026-08-24 | 6 | 2026-10-21T09:52:36.6417321Z |
| 2026-08-23 – 2026-08-23 | 3 | 2026-10-21T09:52:36.6417321Z |
| 2026-08-17 – 2026-08-17 | 2 | 2026-10-21T09:52:36.6417321Z |
| 2026-08-14 – 2026-08-14 | 4 | 2026-10-21T09:52:36.6417321Z |
| 2026-08-09 – 2026-08-09 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-08-06 – 2026-08-06 | 2 | 2026-10-21T09:52:36.6417321Z |
| 2026-08-04 – 2026-08-04 | 3 | 2026-10-21T09:52:36.6417321Z |
| 2026-08-02 – 2026-08-02 | 1 | 2026-10-21T09:52:36.6417321Z |
| 2026-07-27 – 2026-07-27 | 3 | 2026-10-21T09:52:36.6417321Z |
| 2026-07-23 – 2026-07-23 | 4 | 2026-10-21T09:52:36.6417321Z |
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.2.0 | 7 Jun 2026 | 1 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 21:58 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:27d9594ead490d725eff2c00ab8e4f974b9e96e1f83dcbc51f4a562aea4c1d17