手搓 Agent:权限护栏与 Hook 系统学习笔记

这是我手搓 coding agent 系列的第 3、4 节笔记(前两节是最小 agent loop 和多工具 dispatch)。 前两节做出来的 agent 有个吓人的特点:模型说调什么工具,循环就直接执行,包括 rm -rf /。这节就是给它装刹车,并且把刹车做成一个可扩展的系统。 一句话总结这两节的本质: s03:在工具执行前插一道检查,决定放行 / 询问 / 拒绝。 s04:把这道检查从「写死在循环里」抽象成「往事件上挂回调」,以后加功能不用再改循环。 一、s03 权限护栏:三道闸门 之前的循环是"信任模型"——handler(**args) 直接执行。s03 只做一件事:在执行之前加一段检查。整个循环、工具、dispatch 表都没动,只在执行前多调了一个 check_permission()。 核心是三道闸门,从严到松: 工具要执行了 │ 闸门1 DENY_LIST ──命中──→ ⛔ 直接拒,连问都不问 │ 没命中 闸门2 RULES ──命中──→ 闸门3 ask_user ──你按 N──→ 拒绝 │ 没命中 └──你按 y──→ 放行 │ ✅ 执行 闸门 1:硬拒绝表(deny list) 一张黑名单,命中直接拒: DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if=", "> /dev/sda"] def check_deny_list(command: str) -> str | None: for pattern in DENY_LIST: if pattern in command: # 字符串包含就算命中 return f"Blocked: '{pattern}' is on the deny list" return None # 没命中 → None 这里有个贯穿全节的约定:命中返回理由字符串(真值),没命中返回 None(假值)。这样上层一句 if reason: 就能判断。 ...

4 min · 639 words · Jiaju

给 Agent 加工具:Tool Dispatch 学习笔记

这篇是我学「给 Agent 加工具」(s02 Tool Use)时整理的笔记。接着上一篇手搓最小 Agent Loop往下走。 一句话总结这一节的核心: 给 agent 加一个工具,只需要"加一行"——循环代码完全不动,新工具注册进一张**派发表(dispatch map)**就行。 我用的还是通义千问 qwen-plus(OpenAI 兼容接口)。 一、为什么要给它多个工具 s01 里 agent 只有一个 bash。读文件要 cat,写文件要 echo "..." > file,找文件要 find。问题是:模型脑子里想的是"读这个文件",却要拼出一条 cat path/to/file 的命令——多了一层翻译,浪费 token,还容易拼错。 s02 的做法:给它 5 个专用工具,让它直接表达意图。 工具 干什么 read_file 读文件 write_file 写文件 edit_file 把文件里一段旧文字换成新文字 glob 按模式找文件,如 *.py bash 兜底,其它都不合适时才用 system prompt 里写一句"能用专用工具就别用 bash",模型就会优先挑合适的工具。 二、新概念:工具派发(dispatch) s01 只有 bash,模型一说要工具,代码闭眼调 run_bash 就行。现在有 5 个工具,模型说"我要 read_file",代码得按名字找到对应的那个 Python 函数去执行——这就是 dispatch。 实现靠一张派发表:一个把"工具名字符串"映射到"真正函数"的字典。 TOOL_HANDLERS = { "bash": run_bash, "read_file": run_read, "write_file": run_write, "edit_file": run_edit, "glob": run_glob, } ⚠️ 易错点:值是 run_bash,不要加括号。 run_bash(不加括号)= 把函数本身存进表,需要时再调 run_bash()(加括号)= 立刻执行它,存进表的是执行结果,错了 ...

3 min · 429 words · Jiaju

手搓最小 Agent Loop 学习笔记

这篇是我手搓最小 Agent Loop(s01)时整理的笔记。一句话总结它的本质: 大模型本身只会"说话"。把它的话真的执行掉、再把结果喂回去,它就从"会聊天"变成了"会干活"——这就是 agent。 我用的是通义千问 qwen-plus(走 OpenAI 兼容接口),但下面的道理换任何模型都一样。 一、Agent 到底是什么 一开始我有个困惑:大模型不是只会问答吗?它怎么还能帮我创建文件? 答案是:文件不是模型创建的,是我的代码创建的。 模型自始至终只做了一件事——输出文字。 拆开看,一个 agent 其实是三个角色凑在一起: 角色 是谁 干什么 🧠 动嘴的 大模型 只会输出文字,包括"我想用某个工具"(tool_calls) 📞 传话的 agent loop 代码 读模型的话 → 转给工具 → 把结果再转回给模型 🔧 动手的 run_bash(subprocess) 真的在电脑上执行命令 所以当我说"创建一个 hello.py",发生的是: 我说"建 hello.py" ↓ [模型] 动嘴:我想执行 echo ... > hello.py ← 只是输出文字,文件还不存在 ↓ [代码 run_bash] 动手:真的执行命令 ← ✨ 文件在这一步才诞生 ↓ [把结果喂回] → [模型] 看到成功 → 说"搞定了" ← 不再要工具,结束 关键认知:模型不会"主动调用"工具,它连发消息的能力都没有。 是我写的 for call in msg.tool_calls: 那段循环,替模型把话送到 bash 那儿。没有这段代码,模型说一万句"我想执行 ls"都没用。 ...

3 min · 446 words · Jiaju

FastAPI 进阶:搭一个能跑 Agent 的后端

这是我学 FastAPI 进阶部分整理的笔记。和入门那篇(查询参数、请求体、response_model)不同,这次的目标很明确:搭一个能真正跑 agent 的后端——能调 LLM、能存对话记忆、能扛并发、能鉴权。 我现在是 vibe coding 的方式在学:不追求闭卷默写,而是「读得懂 AI 写的代码 + 能指出哪里不对 + 能说清自己要什么」。所以下面每个知识点我都标了两件事——它对写 agent 有什么用,以及大厂面试会怎么考。 先一句话串起全篇: 一个 agent 后端的骨架 = 接口(处理请求)+ 错误处理(诚实报错)+ 异步(扛并发)+ 数据库(存记忆)+ 依赖注入(管连接和鉴权)。 一、HTTPException:出错了要「诚实地报错」 痛点:硬返回 None 是在「撒谎」 查一只不存在的宠物,如果这么写: @app.get("/pets/{pet_id}") def get_pet(pet_id: int): pet = pets_db.get(pet_id) # 没找到,pet = None return pet # 直接返回 None 后果是:状态码还是 200 OK,body 是 null。这等于明明失败了却告诉前端「成功了」。前端通常这么判断: if (response.ok) { // 200 → ok 是 true,以为成功了 showPet(data.name); // data 是 null → 💥 报错 "Cannot read 'name' of null" } 更糟的是用中括号取值 pets_db[999]——直接 KeyError,FastAPI 兜底返回 500,把「用户查了个不存在的 id」(用户的锅,4xx)甩锅成「服务器坏了」(服务器的锅,5xx)。 ...

5 min · 952 words · Jiaju

LLM Agents: How ReAct, Tool Use, and Planning Actually Work

“Agent” is one of the most overloaded words in AI right now. Everything from a simple function-calling wrapper to AutoGPT gets called an agent. This post cuts through the hype and explains the actual mechanics: how agents think, act, and plan, with the math to back it up. What Is an Agent? A bare LLM is a function: given a prompt, output a completion. An agent wraps that function in a loop: ...

6 min · 1219 words · Jiaju