一个最小可运行的 AI Agent 框架,面向初学者的学习项目。 用 ~500 行 Python 代码,拆解清楚一个 Agent 到底是怎么跑起来的。
市面上的 Agent 框架(LangChain、LlamaIndex、AutoGen…)功能强大但抽象层太厚,初学者很难一眼看清**"一个 Agent 本质上是什么"**。
MiniAgent 的目标:不造轮子、不搞魔法,用最直白的代码把 Agent 的 5 个核心概念讲明白:
| 核心概念 | 对应文件 | 一句话解释 |
|---|---|---|
| Tool(工具) | tools/ |
Agent 能"动手做"的外部能力(计算、搜索、读写文件…) |
| Memory(记忆) | agent/memory.py |
保存对话历史,让 Agent 有"上下文" |
| Planner(规划) | agent/planner.py |
把复杂目标拆成有序子任务 |
| Executor(执行) | agent/executor.py |
把 LLM 输出的 tool_calls 派发给真正的函数 |
| Loop(主循环) | agent/loop.py |
ReAct 循环:Reason → Act → Observe |
读完并亲手改一遍这 5 个文件,你就理解了 Agent 的本质。
跑起来后可以和它这样对话:
You> 帮我计算 (12+8)*3 然后写入 result.txt
[Step 1] 调用 LLM...
[Act] 调用工具 calculator({"expression":"(12+8)*3"})
[Observe] 60
[Step 2] 调用 LLM...
[Act] 调用工具 file_io({"action":"write","filename":"result.txt","content":"60"})
[Observe] 已写入 result.txt(2 字符)
[Step 3] 调用 LLM...
[Answer] 已将 (12+8)*3 的结果 60 写入 result.txt。
内置 3 个示例工具:
- calculator — 安全的数学计算(基于 Python AST,拒绝任意代码执行)
- search — DuckDuckGo 网页搜索
- file_io — 本地文件读写 / 追加 / 删除 / 建文件夹 / 移动(带路径穿越防护)
┌─────────────────────────────────────────────────────────┐
│ main.py │
│ (入口:交互模式 / 单次任务) │
└──────────────┬──────────────────────────────────────────┘
│
┌─────────▼──────────┐ ┌───────────────────┐
│ Planner (可选) │ 拆解 │ goal → steps │
│ planner.py │────────▶│ │
└─────────┬──────────┘ └───────────────────┘
│
┌─────────▼───────────────────────────────────────┐
│ Agent Loop (loop.py) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ Reason │──▶│ Act │──▶│ Observe │ │
│ │ (LLM) │ │(Executor)│ │ (result) │ │
│ └──────────┘ └────┬─────┘ └─────┬─────┘ │
│ ▲ │ │ │
│ └──────────────┴───────────────┘ │
│ 循环至完成 │
└──────────────────┬─────────────┬────────────────┘
│ │
┌────────▼────┐ ┌─────▼──────┐
│ Memory │ │ Tools │
│ (messages) │ │ registry │
└─────────────┘ └────────────┘
miniagent/
├── main.py # 入口:CLI 交互模式
├── config.py # 配置:LLM 客户端、模型、环境变量
├── agent/
│ ├── loop.py # ReAct 主循环
│ ├── planner.py # 任务拆解(可选)
│ ├── executor.py # 工具调度
│ └── memory.py # 对话历史 + JSON 持久化
├── tools/
│ ├── registry.py # 工具注册表
│ ├── calculator.py # 示例工具:安全计算
│ ├── search.py # 示例工具:DuckDuckGo
│ └── file_io.py # 示例工具:文件操作
├── requirements.txt
├── .env.example # 环境变量模板
├── PLAN.md # 原始设计文档
└── README.md # 本文件
需要 Python 3.11+ 和一个 OpenAI 兼容的 LLM 服务。有两种选择,任选其一即可:
不想折腾本地模型?直接用云端 API 最快。本项目兼容任何 OpenAI 格式的服务,常见选项:
| 服务商 | LLM_REMOTE_URL |
获取 API Key |
|---|---|---|
| OpenAI | https://api.openai.com/v1 |
https://platform.openai.com/api-keys |
| DeepSeek | https://api.deepseek.com/v1 |
https://platform.deepseek.com/api_keys |
| 阿里百炼(通义千问) | https://dashscope.aliyuncs.com/compatible-mode/v1 |
https://bailian.console.aliyun.com/ |
| 月之暗面(Kimi) | https://api.moonshot.cn/v1 |
https://platform.moonshot.cn/console/api-keys |
| 智谱(GLM) | https://open.bigmodel.cn/api/paas/v4/ |
https://open.bigmodel.cn/usercenter/apikeys |
| 硅基流动 | https://api.siliconflow.cn/v1 |
https://cloud.siliconflow.cn/account/ak |
申请到 key 后,跳到 步骤 3 按"云端 API 配置"填写 .env 即可。
想完全跑在自己机器上?用 Ollama 一条命令拉模型:
# 1. 安装 Ollama(按官网指引:https://ollama.com/download)
# 2. 拉取 Qwen3 模型(8B 约 5GB,需要 ≥16GB 内存;低配机器可换 qwen3:4b / qwen3:1.7b)
ollama pull qwen3:8b
# 3. 启动 Ollama(默认监听 http://localhost:11434)
ollama serve💡 小参数模型(≤7B)的 function calling 能力较弱,可能会忽略工具"硬答"。 如果发现 Agent 不调工具,优先换更大的模型或走方式 A。
git clone https://github.com/<your-username>/miniagent.git
cd miniagent
# 创建虚拟环境(推荐)
python -m venv .venv
# Windows: .venv\Scripts\activate
# Linux/Mac: source .venv/bin/activate
pip install -r requirements.txt# Linux / macOS / Git Bash
cp .env.example .env
# Windows cmd / PowerShell
copy .env.example .env然后按下方两种方式之一编辑 .env:
方式 A:云端 API(以 DeepSeek 为例,其他服务商同理)
LLM_MODE=remote
LLM_REMOTE_URL=https://api.deepseek.com/v1
LLM_API_KEY=sk-你的真实key
LLM_MODEL=deepseek-chat方式 B:本机 Ollama
LLM_MODE=local
LLM_LOCAL_URL=http://localhost:11434/v1
LLM_API_KEY=ollama
LLM_MODEL=qwen3:8b# 交互模式
python main.py
# 单次任务(执行完进入交互模式)
python main.py "帮我计算 (12+8)*3 然后写入 result.txt"交互模式中可用命令:
| 命令 | 作用 |
|---|---|
exit |
退出 |
clear |
清空对话历史 |
| 变量 | 默认值 | 说明 |
|---|---|---|
LLM_MODE |
local |
local(本机 Ollama)或 remote(远程服务器) |
LLM_LOCAL_URL |
http://localhost:11434/v1 |
本地 Ollama 地址 |
LLM_REMOTE_URL |
— | 远程 LLM 服务地址(LLM_MODE=remote 时生效) |
LLM_MODEL |
qwen3:8b |
模型名称 |
LLM_API_KEY |
ollama |
Ollama 不校验,占位即可;远程加了鉴权则填真实 key |
MAX_STEPS |
10 |
ReAct 最大轮数(防死循环),必须是正整数 |
MEMORY_PATH |
memory.json |
对话历史持久化路径,留空则不持久化 |
USE_PLANNER |
false |
是否启用 Planner 预先拆解任务 |
FILE_BASE_DIR |
项目根目录 | file_io 工具允许操作的根目录(防路径穿越) |
LLM_THINK_MODE |
auto |
Qwen Thinking Mode 开关:auto(按模型名判断)/on/off。非 Qwen 模型务必保持 auto 或 on |
ReAct = Reasoning + Acting。一个 Agent 每一轮都在做三件事:
- Reason — LLM 读取对话上下文,决定"下一步做什么"
- Act — 如果 LLM 决定用工具,就真的调用那个 Python 函数
- Observe — 把工具返回结果塞回对话,让 LLM "看到"
循环直到 LLM 说"我说完了"(finish_reason == "stop" 且没有 tool_calls),或达到 MAX_STEPS。
每个工具两部分:
- Schema(OpenAI function calling 格式)— 告诉 LLM 这个工具怎么用、参数是什么
- Handler(Python 函数)— 真正执行逻辑,接收
dict,返回str
注册工具只要一行:
register_tool(schema_dict, handler_function)然后 LLM 就能在对话中自动触发它。
Qwen3 默认开启 "Thinking Mode",输出里夹带 <think>...</think> 块(模型的内心独白)。
本项目在两处处理它:
- 请求时
extra_body={"think": False}关闭思考(省 token) - 响应时用正则清洗残留的
<think>块,避免污染上下文
比如加一个"当前时间"工具,新建 tools/datetime_tool.py:
from datetime import datetime
from tools.registry import register_tool
def _handler(args: dict) -> str:
fmt = args.get("format", "%Y-%m-%d %H:%M:%S")
return datetime.now().strftime(fmt)
register_tool(
{
"type": "function",
"function": {
"name": "current_time",
"description": "返回当前本地时间",
"parameters": {
"type": "object",
"properties": {
"format": {
"type": "string",
"description": "strftime 格式字符串,默认 %Y-%m-%d %H:%M:%S",
}
},
},
},
},
_handler,
)然后在 main.py 顶部加一行:
import tools.datetime_tool # noqa: F401重启后 Agent 就能用 current_time 工具了。就这么简单。
Q: 我没有 Ollama,可以用 OpenAI / DeepSeek / 其他云端 API 吗?
可以。只要是 OpenAI 兼容接口,改 .env 即可:
LLM_MODE=remote
LLM_REMOTE_URL=https://api.deepseek.com/v1
LLM_API_KEY=sk-你的真实key
LLM_MODEL=deepseek-chatQ: 为什么我的模型经常忽略工具、自己瞎答?
小参数模型(7B 以下)的 function calling 能力较弱。建议:
- 用 Qwen3:8b 及以上,或 GPT-4o-mini、DeepSeek-V3 等
- 加强
main.py里的SYSTEM_PROMPT,明确"必须使用工具" - 开启
USE_PLANNER=true,用 Planner 先把任务拆清楚
Q: Agent 陷入死循环怎么办?
已有 MAX_STEPS 保护(默认 10 轮)。可以在 .env 里调小。
Q: 对话历史会越来越长,怎么办?
Memory 有 max_messages=50 的窗口裁剪。需要更精细的裁剪策略(例如按 token 数、保留摘要),可在 agent/memory.py 的 _trim() 中扩展。
Q: file_io 工具操作会不会写坏我的系统?
不会。file_io 通过 FILE_BASE_DIR 限制在项目目录内,绝对路径和 ../ 越界都会被拒绝。
推荐按这个顺序读代码,由浅入深:
tools/calculator.py— 看懂"一个工具长什么样"tools/registry.py— 工具怎么被注册和查找agent/memory.py— 对话历史的数据结构agent/executor.py— LLM 输出是怎么被映射到 Python 函数的agent/loop.py— 核心:ReAct 循环的完整实现agent/planner.py— 锦上添花的任务拆解main.py— 把上面所有模块串起来
读完这一圈,你已经掌握了现代 Agent 框架的骨架。之后再看 LangChain / AutoGen 的源码,会发现它们本质上就是在这套骨架上加层、加特性。
- M1 — 单工具 ReAct(计算器能跑)
- M2 — 多步任务(Planner 拆解)
- M3 — 多工具(search + file_io)
- M4 — Memory 持久化(重启恢复上下文)
- M5 — Web UI(Gradio / Streamlit)
- M6 — 流式输出
- M7 — 工具并行执行
欢迎 PR!
openai>=1.30.0 # OpenAI 兼容 API 客户端
python-dotenv>=1.0.0 # .env 文件加载
ddgs>=0.1 # DuckDuckGo 搜索
MIT License.
- Ollama — 本地跑 LLM 的最简方案
- Qwen — 阿里开源的高质量中文模型
- ReAct 论文:ReAct: Synergizing Reasoning and Acting in Language Models