Skip to main content

OpenAgentFramework — minimal, fast, transparent AI agent framework

Project description

OAF — OpenAgentFramework
Minimal, fast, transparent AI agent framework for Python

PyPI Python License Build


Build AI agents that call tools, manage conversation context, and stream responses — with zero magic. Every prompt, tool call, and LLM decision is fully inspectable. Works with OpenAI and Anthropic out of the box.

📖 Full project documentation: OAF.md — architecture deep-dive, API reference, design decisions, and roadmap.

Why OAF?

OAF Typical frameworks
Abstraction Flat — one agent loop, one tool decorator Deep chains, hidden prompt wrangling
Debuggability Full prompt/response inspection via hooks Opaque internal state
Surface area ~10 top-level exports Hundreds of classes
Tool definition Decorate any async function Special base classes, schemas, descriptors
Context engineering Single subclass point — you own the prompt Scattered across prompt templates, chains, memory
Multi-agent Shared context, isolated internal tools Tight coupling, global state
Providers OpenAI + Anthropic, same API Often single-provider or heavy adapter layer

Install

pip install scope-oaf

Requires Python 3.11+

Quick Start

import asyncio
from oaf import Agent, ToolRegistry

registry = ToolRegistry()

@registry.register
async def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"Weather in {city}: 72°F, sunny"

agent = Agent(model="gpt-4.1-nano", tools=registry)
response = asyncio.run(agent.message("What's the weather in Tokyo?", role="user"))
print(response.text)

A full agent with tool calling in 10 lines.

Features

Tool Calling

Decorate any async function — type hints and docstrings become JSON Schema automatically:

@registry.register
async def search(query: str, max_results: int = 5) -> str:
    """Search the web for a query."""
    return f"Results for {query}"

Supports: str, int, float, bool, list[T], dict[K,V], Optional[T], Union, Enum, nested generics.

Tool Groups

Organize related tools with auto-prefixed names:

from oaf import BaseToolGroup, tool_method

class MathTools(BaseToolGroup):
    name = "math"

    @tool_method
    async def add(self, a: int, b: int) -> str:
        """Add two numbers."""
        return str(a + b)

registry.register_group(MathTools())
# → "math.add"

Streaming

async for chunk in agent.message_stream("Tell me a story"):
    print(chunk.delta_text, end="")

Lifecycle Hooks

Tap into every stage — subclass or register ad-hoc:

from oaf import Hooks

class MyHooks(Hooks):
    async def before_message(self, message, role, messages):
        print(f"→ {message}")

    async def after_tool_call(self, tool_name, arguments, result):
        print(f"  {tool_name}({arguments}) = {result}")

Available events: before_message, after_message, before_tool_call, after_tool_call, on_stream_chunk, on_error, on_context_update, system_prompt_change.

Context Engineering

BaseContext is the single subclass point for custom prompt assembly. The default ConversationalInMemory handles message/token limits. Build your own for vector DB injection, RAG, dynamic prompt sections, and more:

from oaf import BaseContext

class MyRAGContext(BaseContext):
    def build_messages(self, **kwargs):
        # Inject retrieved documents, manage tool output retention,
        # add dynamic system prompt fields — you control everything.
        ...

Provider Agnostic

Swap models by changing a string — OpenAI and Anthropic use the same interface:

agent_openai = Agent(model="gpt-4.1-nano", ...)
agent_claude = Agent(model="claude-sonnet-4-20250514", ...)

LLM Client (standalone)

The built-in LLMClient works independently of the agent framework:

from oaf.llmclient import LLMClient, SyncLLMClient, Message

# Async
client = LLMClient()
response = await client.chat("gpt-4.1-nano", [Message(role="user", content="Hello")])

# Sync
client = SyncLLMClient()
response = client.chat("gpt-4.1-nano", [Message(role="user", content="Hello")])

# Embeddings
emb = await client.embed("text-embedding-3-small", "Hello world")

Model Registry

Built-in metadata for all current models — context windows, pricing, output limits:

from oaf.llmclient import Models, get_model, list_models

model = Models.GPT_4_1_NANO
print(model.context_window)        # 1_000_000
print(model.input_price_per_mtok)  # 0.10

all_openai = list_models("openai")

Architecture

┌──────────────────────────────────────────────────┐
│                  Your Application                │
├──────────────────────────────────────────────────┤
│              OpenAgentFramework (OAF)            │
│  ┌──────────┐ ┌──────────┐ ┌───────────────┐    │
│  │  Agent   │ │  Tools   │ │    Context     │    │
│  │          │ │          │ │  + Prompts     │    │
│  └──────────┘ └──────────┘ └───────────────┘    │
├──────────────────────────────────────────────────┤
│              llmclient (built-in)                │
│          OpenAI + Anthropic providers            │
└──────────────────────────────────────────────────┘
Component What it does
Agent Async LLM call + tool execution loop. Configurable max rounds, temperature, model.
Tools @tool decorator, ToolRegistry, BaseToolGroup. Python types → JSON Schema.
Context Owns message storage, tool awareness, and prompt assembly. Single subclass point.
Hooks 8 lifecycle events. Class-based or ad-hoc. before_* events support mutation.
LLMClient Unified async/sync client for OpenAI + Anthropic. Streaming, embeddings, tool calling.
Prompts build_tool_prompt() renders tool descriptions. Override in your context for full control.

Examples

File Description
chat.py Interactive terminal REPL with tool-calling agent
web.py + web_ui.html Browser-based chat UI with streaming
# Terminal REPL
export OPENAI_API_KEY=sk-...    # or set ANTHROPIC_API_KEY
python chat.py

# Web UI
pip install -e ".[web]"
python web.py                   # → http://localhost:8080

Contributing

We use GitHub issues and labels to track work. See below for the label taxonomy.

Setup

git clone https://github.com/devincii-io/scope-oaf.git
cd scope-oaf
pip install -e ".[dev]"
pytest                          # 82 tests, no API keys needed

Branch Workflow

Branch Purpose
dev All development. Push after every logical change.
master Releases only. Push triggers PyPI publish via GitHub Actions.

GitHub Labels

Label Color Description
bug 🔴 #d73a4a Something isn't working
enhancement 🟢 #a2eeef New feature or improvement
breaking 🟠 #e99695 Breaking API change
agent 🔵 #1d76db Agent loop, tool execution, multi-agent
context 🔵 #1d76db Context system, prompt assembly
tools 🔵 #1d76db Tool decorator, registry, groups, type inference
llmclient 🔵 #1d76db LLM client, providers, streaming
hooks 🔵 #1d76db Lifecycle event system
docs 🟡 #fef2c0 Documentation only
tests 🟡 #fef2c0 Test coverage
good first issue 🟢 #7057ff Good for newcomers
help wanted 🟢 #008672 Looking for contributors
wontfix #ffffff Not planned

Release

  1. Bump version in pyproject.toml
  2. Merge devmaster
  3. GitHub Actions publishes to PyPI automatically (trusted publishing via OIDC)

Documentation

Document Contents
OAF.md Full project docs — architecture, API reference, design decisions, task list
README.md This file — quick start, features, contributing
LICENSE MIT License

Glossary

Term Definition
Agent The core orchestrator. Sends messages to an LLM, parses tool calls from responses, executes tools, and loops until the LLM gives a final text answer.
Tool An async Python function decorated with @tool or @registry.register. Its signature and docstring are converted to JSON Schema and injected into the system prompt for the LLM to call.
ToolRegistry A collection of tools. Passed to the agent or context. Supports registration via decorators and tool groups.
Tool Group A class extending BaseToolGroup with @tool_method methods. Methods are registered with a group.method naming convention.
Context An object implementing BaseContext that owns message storage, tool awareness, and prompt assembly. The single customization point for what the LLM sees.
ConversationalInMemory The default context implementation. In-memory message history with configurable message count and token budget limits.
Hooks Lifecycle event system. Fires callbacks at key points: before/after messages, before/after tool calls, on errors, on stream chunks.
LLMClient Unified async client for OpenAI and Anthropic APIs. Auto-detects provider from model name. Supports chat, streaming, and embeddings.
SyncLLMClient Thread-safe synchronous wrapper around LLMClient for non-async code.
System Prompt The initial instruction text sent to the LLM as a role="system" message. Tool descriptions are prepended to it by the context.
Tool Prompt The formatted text block describing available tools and the JSON response schema. Built by build_tool_prompt() in prompts/raw.py.
Internal Tools Agent-owned ToolRegistry (currently empty, reserved for built-in capabilities). Passed to build_messages() at call time — not stored on the context — so multiple agents can share a context safely.
JSON Tool Calling OAF's approach to tool use: tools are described in the system prompt as JSON Schema, and the LLM returns a JSON object with tool_calls. This is owned by OAF, not using native OpenAI/Anthropic function-calling APIs.
Provider An LLM backend (OpenAI or Anthropic). Auto-detected from the model name prefix (gpt-* → OpenAI, claude* → Anthropic).

License

MIT — use it however you want.


Built by devincii-io

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

scope_oaf-0.2.2.tar.gz (58.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

scope_oaf-0.2.2-py3-none-any.whl (41.2 kB view details)

Uploaded Python 3

File details

Details for the file scope_oaf-0.2.2.tar.gz.

File metadata

  • Download URL: scope_oaf-0.2.2.tar.gz
  • Upload date:
  • Size: 58.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for scope_oaf-0.2.2.tar.gz
Algorithm Hash digest
SHA256 21189d7490c32436a11acb81871902b2b7f8da2c99d9dad4fb3259450cade2d3
MD5 52d40b283c06aa7223b5af55d6449ed9
BLAKE2b-256 5998d1ce698a1ffe8d9b6a344046f13e2756ecafd0b28a76275bfe5924a06482

See more details on using hashes here.

Provenance

The following attestation bundles were made for scope_oaf-0.2.2.tar.gz:

Publisher: publish.yml on devincii-io/scope-oaf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file scope_oaf-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: scope_oaf-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 41.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for scope_oaf-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b5292902e5d8e6e144f3eff7747995b30f9fe8466e8ced51e01668e3a6908786
MD5 4585ca5d8c27a4eaa9d3508b429f2106
BLAKE2b-256 e9ef1af3bcabbd721834491aafae9f28e627d99748c3d0e26adb0aedd80ddfbd

See more details on using hashes here.

Provenance

The following attestation bundles were made for scope_oaf-0.2.2-py3-none-any.whl:

Publisher: publish.yml on devincii-io/scope-oaf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page