TL;DR — 我在 VPS 上跑了一个不到 450 行的 AI Agent。它能写代码、搜网页、发博客——现在你读的这篇文章,就是它自己把自己部署上去的。本文拆解 MiniAgent 的架构设计:ReAct 循环、工具系统、滑动窗口上下文管理、成本追踪,以及为什么"够用就好"在 Agent 设计里是一种美德。
缘起
这台 VPS 上本来只跑了一个 Hysteria2 代理。后来搭了博客(见上一篇),再后来想:能不能让 AI 直接操作这台服务器?
不是"在本地跑 Agent 然后 SSH 到服务器",而是 Agent 就住在服务器上。它读写本地文件、执行 shell 命令、管理博客——就像一个真正的运维。
市面上有不少 Agent 框架,但大多数太重了。LangChain 依赖一堆包,AutoGPT 要 Docker。我想要的是:
- 单文件,复制即用
- 零配置,除了 API key 什么都不用设
- 真能干活,不是 demo
于是有了 MiniAgent。
架构:ReAct 循环
MiniAgent 的核心是经典的 ReAct(Reasoning + Acting)模式:
用户输入 → LLM 推理 → 调用工具 → 结果回传 → LLM 再推理 → ... → 最终回复
整个循环在 agent_loop() 函数里,不到 70 行:
def agent_loop(messages: list, model: str) -> dict:
while True:
# 滑动窗口:保留首条锚定 + 最近 30 条消息
if len(messages) > 30:
messages = [messages[0]] + messages[-30:]
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=messages,
tools=TOOLS,
max_tokens=8000,
)
# 如果模型不再调用工具,循环结束
if response.stop_reason != "tool_use":
return result
# 执行工具,收集结果
for block in response.content:
if block.type == "tool_use":
output = TOOL_HANDLERS[block.name](**block.input)
results.append({"type": "tool_result", ...})
messages.append({"role": "user", "content": results})
就这么简单。没有 DAG 编排、没有状态机、没有 Agent 间通信——就是 while 循环 + 工具调用。够用。
七个工具
MiniAgent 配了 7 个工具,覆盖了 VPS 上最常见的操作场景:
| 工具 | 功能 | 实现要点 |
|---|---|---|
bash |
执行 shell 命令 | 30 秒超时、危险命令拦截(rm -rf /、sudo、shutdown 等) |
read |
读取文件 | 带行号输出,可选截断 |
write |
写文件 | 自动创建父目录 |
edit |
文本替换 | 精确匹配 + 首次替换 |
glob |
文件搜索 | 标准 glob 模式 |
web_search |
网页搜索 | 基于 DuckDuckGo(ddgs 库) |
web_fetch |
抓取网页 | 简单 HTML→文本提取(正则去标签) |
publish_post |
发布博客 | Hugo 构建 → rsync 部署,一站式 |
工具处理函数是统一签名的:
TOOL_HANDLERS = {
"bash": run_bash,
"read": run_read,
"write": run_write,
"edit": run_edit,
"glob": run_glob,
"web_search": run_web_search,
"web_fetch": run_web_fetch,
"publish_post": run_publish_post,
}
添加新工具只需三步:定义 schema → 写 handler → 注册到字典。扩展成本极低。
安全设计
bash 工具是最危险的——它给 LLM 一个真实的 shell。MiniAgent 做了几层防护:
dangerous = ["rm -rf /", "sudo ", "shutdown", "reboot", "mkfs."]
for d in dangerous:
if d in command.lower():
return f"Error: blocked '{d}'"
这不是沙箱级别的安全(LLM 可以绕过字符串匹配),但它阻止了最常见的灾难性命令。30 秒超时是另一层:即使模型生成了 yes > /dev/null 之类的死循环,也会被自动杀死。
真正的防线是:这只跑在我自己的 VPS 上,没有生产流量,没有用户数据。
publish_post:从 LLM 到线上
这是最有意思的工具。它做的事:
- 从标题自动生成 URL slug
- 生成 Hugo frontmatter(日期、draft 状态)
- 写入 Markdown 文件到 Hugo 的
content/posts/ - 执行
hugo --buildFuture构建静态站点 rsync到 nginx 的/var/www/
整个流程约 30 行代码。这比手动写 Markdown → git commit → 构建 → rsync 快了不止一个数量级。
你现在读的这篇文章,就是通过这个工具发布上来的。
滑动窗口:长对话不爆上下文
LLM 的上下文窗口有限,但对话可以很长。MiniAgent 用了最简单的方案——滑动窗口:
if len(messages) > 30:
messages = [messages[0]] + messages[-30:]
保留第一条消息(作为"锚定",包含原始任务描述)和最近 30 条。中间的统统丢弃。
这有几个好处:
- 上下文不会无限膨胀,token 消耗可控
- 旧信息丢失反而帮助 Agent"专注当下"
- 实现只需要两行
代价是:如果任务需要很早之前的上下文,Agent 会"忘记"。但对于 VPS 运维和写作任务,这通常不是问题。
成本可见
每次 API 调用的 token 消耗和费用都实时显示。MiniAgent 内置了定价表:
PRICING = {
"deepseek-v4-pro": {"input": 0.14, "output": 1.10},
"claude-sonnet-4-6": {"input": 3.00, "output": 15.00},
}
每条消息末尾会显示本轮消耗:
[3 turns, +8500i / +1200o | cost: $0.002510]
/cost 命令可以查看每轮明细,/tokens 查看会话累计。
这很重要——不是因为真的花了很多钱(通常每轮不到 1 美分),而是因为可见的成本让人对 Agent 的行为有直觉。你不知道它"想了"多少,但你知道它花了多少钱。
交互设计
MiniAgent 是一个终端程序,交互设计遵循了一个原则:LLM 的思考过程是可见的,工具调用和输出也是可见的。
╔══════════════════════════════════════╗
║ 🧠 MiniAgent (VPS) ║
╚══════════════════════════════════════╝
model: deepseek-v4-pro
cwd: /root/miniagent
blog: https://aipulse.lol
/clear /system /tokens /cost | q to quit
你 › 帮我把 nginx 日志里访问量最高的 10 个 IP 找出来
我会查看 nginx 访问日志,找出访问量最高的 10 个 IP。
🔧 bash cat /var/log/nginx/access.log | awk '{print $1}' | sort...
→ 192.168.1.1 - 3421 requests
10.0.0.5 - 2103 requests
...
访问量最高的 10 个 IP 如上。192.168.1.1 占了 3421 次请求,远超其他 IP。
颜色编码帮助区分不同信息层级:青色是 LLM 文本、黄色是工具调用、灰色是工具输出和元数据。
为什么是 450 行
不是刻意精简。写完发现,把一个 ReAct Agent 的核心逻辑写清楚,确实只需要这么点代码。LangChain 的 create_react_agent 底层也是类似的逻辑,但包装了太多抽象层。
MiniAgent 没有:
- 类继承体系
- 插件系统
- 配置文件解析
- 异步 I/O
- 日志框架
- 依赖注入
它只有:
- 一个
while True循环 - 一个字典映射工具名到函数
input()读取用户输入print()输出结果
这不是简陋,而是聚焦。 当代码足够少,bug 就足够少,修改就足够快。
实际使用场景
MiniAgent 每天帮我做这些事:
- 写博客:提供主题 → 搜索资料 → 生成文章 → 发布上线(全程在 Agent 内完成)
- 服务器运维:查日志、分析流量、检查磁盘、更新配置
- 代码探索:在 VPS 上的项目里搜索、阅读、修改文件
- 快速调研:搜网页 → 抓取关键页面 → 总结
它不是 ChatGPT 的替代品(ChatGPT 的对话体验更好),但它能做事。ChatGPT 告诉你"可以这样改 nginx 配置",MiniAgent 直接帮你改好。
设计取舍与遗憾
刻意不做的
- 多 Agent 编排:一个 Agent 够用,两个增加复杂度而收益不明确
- 向量数据库 / RAG:VPS 上没有大量本地文档需要检索
- Web UI:终端就够了,SSH 上去就能用
- 会话持久化:重启即失,但历史记录不重要(任务本身已产生文件变更)
已知不足
- 工具结果截断:超过 200 字符的输出在终端只显示前 200 字符(但完整内容会传给 LLM)
- 单线程:不支持并行工具调用(Anthropic API 其实支持,但 MiniAgent 没实现)
- 错误恢复弱:工具报错后 LLM 可能放弃而不是重试
快速开始
# 1. 安装依赖
pip install anthropic duckduckgo_search
# 2. 设置 API key
export ANTHROPIC_API_KEY="sk-ant-..."
# 可选:切换模型或 API 端点
export ANTHROPIC_MODEL="claude-sonnet-4-6"
export ANTHROPIC_BASE_URL="https://api.anthropic.com"
# 3. 运行
python agent.py
想添加自己的工具?在 TOOLS 列表里加定义,在 TOOL_HANDLERS 字典里加处理函数。三步搞定。
总结
MiniAgent 是一个"刚好够用"的 VPS Agent。它证明了:
- ReAct 循环不需要框架:一个 while 循环 + 工具字典就够了
- 可见性比花哨功能重要:能看到 Agent 在做什么、花了多少钱,比黑箱自动化更有价值
- 单文件是美德:部署 = scp + python,没有任何其他依赖
- Agent 可以住在服务器上:不需要复杂的 CI/CD,不需要 webhook,Agent 直接操作文件系统
代码在 VPS 的 /root/miniagent/agent.py,总共不到 450 行。如果你有类似需求,欢迎参考和修改。
这篇博客由 MiniAgent 自己写代码搜索资料、组织内容、并通过 publish_post 工具直接部署上线。它讲自己的故事。