Metadata-Version: 2.4
Name: scope-oaf
Version: 0.2.2
Summary: OpenAgentFramework — minimal, fast, transparent AI agent framework
Project-URL: Homepage, https://github.com/devincii-io/scope-oaf
Project-URL: Repository, https://github.com/devincii-io/scope-oaf
Project-URL: Issues, https://github.com/devincii-io/scope-oaf/issues
Author: devin
License-Expression: MIT
License-File: LICENSE
Keywords: agent,ai,anthropic,llm,openai,tool-calling
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: anthropic>=0.88
Requires-Dist: openai>=2.30
Requires-Dist: tiktoken>=0.7
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: python-dotenv>=1.0; extra == 'dev'
Provides-Extra: web
Requires-Dist: aiohttp>=3.9; extra == 'web'
Requires-Dist: python-dotenv>=1.0; extra == 'web'
Description-Content-Type: text/markdown

<p align="center">
  <strong>OAF — OpenAgentFramework</strong><br>
  <em>Minimal, fast, transparent AI agent framework for Python</em>
</p>

<p align="center">
  <a href="https://pypi.org/project/scope-oaf/"><img src="https://img.shields.io/pypi/v/scope-oaf?color=blue" alt="PyPI"></a>
  <a href="https://pypi.org/project/scope-oaf/"><img src="https://img.shields.io/pypi/pyversions/scope-oaf" alt="Python"></a>
  <a href="https://github.com/devincii-io/scope-oaf/blob/master/LICENSE"><img src="https://img.shields.io/github/license/devincii-io/scope-oaf" alt="License"></a>
  <a href="https://github.com/devincii-io/scope-oaf/actions"><img src="https://img.shields.io/github/actions/workflow/status/devincii-io/scope-oaf/publish.yml?branch=master&label=build" alt="Build"></a>
</p>

---

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`](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

```bash
pip install scope-oaf
```

> Requires Python 3.11+

## Quick Start

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

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

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

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

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

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

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

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

```python
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`](chat.py) | Interactive terminal REPL with tool-calling agent |
| [`web.py`](web.py) + [`web_ui.html`](web_ui.html) | Browser-based chat UI with streaming |

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

```bash
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 `dev` → `master`
3. GitHub Actions publishes to PyPI automatically (trusted publishing via OIDC)

## Documentation

| Document | Contents |
|----------|----------|
| **[`OAF.md`](OAF.md)** | Full project docs — architecture, API reference, design decisions, task list |
| **[`README.md`](README.md)** | This file — quick start, features, contributing |
| **[`LICENSE`](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](LICENSE) — use it however you want.

---

<p align="center">
  <sub>Built by <a href="https://github.com/devincii-io">devincii-io</a></sub>
</p>
