A lightweight AI Agent framework built on LiteLLM with ReAct reasoning, tool calling, and smart memory.
Project description
EasyAgent
English | 简体中文
A lightweight AI Agent framework built on LiteLLM, featuring multi-model support, tool calling, and intelligent memory management.
~809 lines of code, production-ready Agent capabilities — Multi-model adapters, tool calling, smart memory, ReAct reasoning, DAG pipelines, debug tracing. Core code refined to the extreme.
Features
- Multi-Model Support - Unified interface via LiteLLM for OpenAI, Anthropic, Gemini, and more
- Tool Calling - Protocol-based tool definition with
@register_tooldecorator - Memory - Sliding window + auto-summarization strategies for context management
- ReAct Loop - Standard think → act → observe reasoning cycle
- DAG Pipeline - Directed Acyclic Graph workflow orchestration with parallel execution
- Debug Friendly - Colored logging, token usage and cost tracking
- Minimal Footprint - Only ~809 lines of core code, no bloat, easy to read/modify/extend
Installation
From PyPI:
pip install easy-agent-sdk
From source (development mode):
git clone https://github.com/SNHuan/EasyAgent.git
cd EasyAgent
pip install -e ".[dev]"
Core dependencies:
litellm>=1.80.0pydantic>=2.12.5
Architecture
┌──────────────────────────────────────────────────────────────┐
│ User Layer │
│ (Input / Output) │
└─────────────────────────────┬────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Agent Layer │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ ReactAgent (ReAct Loop: think -> act -> observe) │ │
│ │ ↓ extends │ │
│ │ ToolAgent (Tool Registration & Execution) │ │
│ │ ↓ extends │ │
│ │ BaseAgent (Model + Memory + History Management) │ │
│ └────────────────────────────────────────────────────────┘ │
└───────────┬──────────────────┬──────────────────┬────────────┘
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Model │ │ Memory │ │ Tool │
│ │ │ │ │ │
│ BaseLLM │ │ BaseMemory │ │ Tool Protocol │
│ ↓ │ │ ↓ │ │ ↓ │
│ LiteLLMModel │ │ SlidingWindow │ │ ToolManager │
│ (OpenAI/Claude) │ │ SummaryMemory │ │ @register_tool │
└────────┬─────────┘ └──────────────────┘ └──────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Schema Layer │
│ Message | ToolCall | LLMResponse │
└──────────────────────────────────────────────────────────────┘
Layer Overview:
| Layer | Responsibility | Module |
|---|---|---|
| User Layer | User interaction entry point | - |
| Agent Layer | Core control, ReAct loop | agent/ |
| Infrastructure | Independent modules | model/ memory/ tool/ |
| Schema Layer | Pydantic data structures | model/schema.py |
Project Structure
EasyAgent/
├── agent/ # Agent layer
│ ├── base.py # BaseAgent abstract class
│ ├── tool_agent.py # ToolAgent (tool calling support)
│ └── react_agent.py # ReactAgent (ReAct loop)
├── model/ # Model layer
│ ├── base.py # BaseLLM abstract class
│ ├── litellm_model.py # LiteLLM implementation
│ └── schema.py # Message, ToolCall, LLMResponse
├── memory/ # Memory layer
│ ├── base.py # BaseMemory abstract class
│ ├── sliding_window.py # Sliding window strategy
│ └── summary.py # Auto-summarization strategy
├── tool/ # Tool layer
│ ├── base.py # Tool Protocol
│ └── manager.py # ToolManager + @register_tool
├── pipeline/ # DAG Pipeline
│ └── base.py # BaseNode, BasePipeline, NodeContext
├── prompt/ # Prompt templates
├── config/ # Configuration management
├── debug/ # Debug utilities (colored logs)
└── test/ # Tests
Quick Start
1. Configuration
Option 1: Environment Variable (Recommended)
Copy .example_env to .env and set your custom config path:
cp .example_env .env
# .env
EA_DEFAULT_CONFIG=/path/to/your/config.yaml
Option 2: Edit Package Config
cp config/config_example.yaml config/config.yaml
Config File Format
debug: true
summary_model: gpt-4o-mini
models:
gpt-4o-mini:
api_type: openai
base_url: https://api.openai.com/v1
api_key: sk-xxx
# Custom models with cost configuration
gemini-2.5-flash:
api_type: openai
base_url: https://your-proxy.com/v1
api_key: your-key
cost:
input_cost_per_token: 0.0000003
output_cost_per_token: 0.00000252
max_tokens: 8192
max_input_tokens: 1048576
Config Loading Priority:
- Path specified by
EA_DEFAULT_CONFIGenvironment variable - Default
config/config.yamlin package
3. Create Agent
import asyncio
from agent.react_agent import ReactAgent
from config.base import ModelConfig
from model.litellm_model import LiteLLMModel
config = ModelConfig.load()
model = LiteLLMModel(**config.get_model("gpt-4o-mini"))
agent = ReactAgent(
model=model,
tools=["get_weather"],
system_prompt="You are a helpful assistant.",
max_iterations=10,
)
result = asyncio.run(agent.run("What's the weather in Beijing?"))
print(result)
Core Components
Agent Layer
| Class | Description |
|---|---|
BaseAgent |
Abstract base class with model, memory, and history management |
ToolAgent |
Extends BaseAgent with tool registration and execution |
ReactAgent |
ReAct loop implementation: think → act → observe |
Model Layer
| Class | Description |
|---|---|
BaseLLM |
Abstract interface defining call() and call_with_history() |
LiteLLMModel |
LiteLLM implementation supporting all LiteLLM-compatible models |
Message |
Pydantic message model (system/user/assistant/tool) |
ToolCall |
Tool call structure |
LLMResponse |
Unified response format with content, tool_calls, usage |
Memory Layer
| Strategy | Use Case | Features |
|---|---|---|
SlidingWindowMemory |
Short conversations | Truncate by message/token count, keep recent messages |
SummaryMemory |
Long conversations | Auto-summarize and persist, respects max_tokens |
from memory import SlidingWindowMemory, SummaryMemory
# Sliding window: limit by message and token count
memory = SlidingWindowMemory(max_messages=20, max_tokens=4000)
# Auto-summary: for long tasks, max_tokens fetched from litellm
memory = SummaryMemory(
task_id="task_001",
reserve_ratio=0.3,
workspace="workspace",
)
Tool Layer
Tools must implement the Tool Protocol:
from typing import Protocol
class Tool(Protocol):
name: str
type: str
description: str
def init(self) -> None: ...
def execute(self, **kwargs) -> str: ...
Pipeline
DAG-based workflow orchestration with parallel node execution:
import asyncio
from pipeline.base import BaseNode, BasePipeline, NodeContext
# Define nodes
class FetchData(BaseNode):
async def execute(self, ctx: NodeContext) -> None:
ctx.data = "raw_data"
class ProcessA(BaseNode):
async def execute(self, ctx: NodeContext) -> None:
ctx.result_a = f"{ctx.data}_processed_A"
class ProcessB(BaseNode):
async def execute(self, ctx: NodeContext) -> None:
ctx.result_b = f"{ctx.data}_processed_B"
class Merge(BaseNode):
async def execute(self, ctx: NodeContext) -> None:
ctx.final = f"{ctx.result_a} + {ctx.result_b}"
# Build DAG using >> syntax
fetch = FetchData()
process_a = ProcessA()
process_b = ProcessB()
merge = Merge()
fetch >> [process_a, process_b] # Parallel branches
process_a >> merge
process_b >> merge
# Execute
pipeline = BasePipeline(root=fetch)
ctx = asyncio.run(pipeline.run())
print(ctx.final) # "raw_data_processed_A + raw_data_processed_B"
# Visualize (Mermaid format)
print(pipeline.visualize())
Core Components:
| Component | Description |
|---|---|
BaseNode |
Abstract node class, implement execute(ctx) |
BasePipeline |
Pipeline executor with level-based parallel execution |
NodeContext |
Shared context for inter-node data passing |
>> operator |
Syntactic sugar for node.add(successor) |
Debugging
Enable debug mode for colored logs:
# config/config.yaml
debug: true
Log output example:
14:30:15 DEBUG [ReactAgent] User: What's the weather?
14:30:15 DEBUG [ReactAgent] Iteration 1/10
14:30:16 INFO [LiteLLM] Response: in=150, out=45, cost=$0.000195
14:30:16 INFO [ReactAgent] Tool call: get_weather({"city": "Beijing"})
14:30:16 INFO [ReactAgent] Tool result: The weather in Beijing is sunny, 25°C.
14:30:17 INFO [ReactAgent] Final: The weather in Beijing is sunny with 25°C.
Use LogCollector to capture logs:
from debug.log import LogCollector, Logger
log = Logger("MyApp")
with LogCollector() as collector:
log.info("Step 1")
log.info("Step 2")
print(collector.to_text()) # "Step 1\nStep 2"
Running Tests
python -m test.test_agent
python -m test.test_model
Acknowledgements
Thanks to litellm and OpenManus for inspiration and guidance.
License
MIT License © 2025 Yiran Peng
2. Define Tools
Use the @register_tool decorator:
from tool import register_tool
@register_tool
class GetWeather:
name = "get_weather"
type = "function"
description = "Get the weather for a city."
parameters = {
"type": "object",
"properties": {"city": {"type": "string", "description": "City name"}},
"required": ["city"],
}
def init(self) -> None:
"""Called when tool is initialized"""
pass
def execute(self, city: str) -> str:
"""Execute tool logic"""
return f"The weather in {city} is sunny, 25°C."
Project details
Release history Release notifications | RSS feed
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 easy_agent_sdk-0.1.0.tar.gz.
File metadata
- Download URL: easy_agent_sdk-0.1.0.tar.gz
- Upload date:
- Size: 27.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb5351b47333d239fe5ca863bb5ceed20ccd819988241af6338ba083d9fca30f
|
|
| MD5 |
8a15cb12984bba8a894421ccfe156db8
|
|
| BLAKE2b-256 |
7bc58db756ad89894f72ec0b1c4b634d6b7b6756f69a6b183089c6da80b40f49
|
Provenance
The following attestation bundles were made for easy_agent_sdk-0.1.0.tar.gz:
Publisher:
publish.yml on SNHuan/EasyAgent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
easy_agent_sdk-0.1.0.tar.gz -
Subject digest:
eb5351b47333d239fe5ca863bb5ceed20ccd819988241af6338ba083d9fca30f - Sigstore transparency entry: 828361014
- Sigstore integration time:
-
Permalink:
SNHuan/EasyAgent@4335bf238fd4e5256f0d8b2a60709ff8cf1c7feb -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/SNHuan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4335bf238fd4e5256f0d8b2a60709ff8cf1c7feb -
Trigger Event:
release
-
Statement type:
File details
Details for the file easy_agent_sdk-0.1.0-py3-none-any.whl.
File metadata
- Download URL: easy_agent_sdk-0.1.0-py3-none-any.whl
- Upload date:
- Size: 28.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ba7a0d38ebe299a22c7ea4e1ce4418b1262b0bf1a1c974231860261a5da52eb
|
|
| MD5 |
53b4d8ff93e850f14a899c5f42a76971
|
|
| BLAKE2b-256 |
4f44cafe35c4d66b3e7a2df9a8e8709b0bd3865d02e5d76755076cc780d0a5d1
|
Provenance
The following attestation bundles were made for easy_agent_sdk-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on SNHuan/EasyAgent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
easy_agent_sdk-0.1.0-py3-none-any.whl -
Subject digest:
0ba7a0d38ebe299a22c7ea4e1ce4418b1262b0bf1a1c974231860261a5da52eb - Sigstore transparency entry: 828361019
- Sigstore integration time:
-
Permalink:
SNHuan/EasyAgent@4335bf238fd4e5256f0d8b2a60709ff8cf1c7feb -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/SNHuan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4335bf238fd4e5256f0d8b2a60709ff8cf1c7feb -
Trigger Event:
release
-
Statement type: