惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
博客园 - 聂微东
IT之家
IT之家
The Cloudflare Blog
L
LangChain Blog
Last Week in AI
Last Week in AI
T
Tailwind CSS Blog
P
Proofpoint News Feed
aimingoo的专栏
aimingoo的专栏
G
Google Developers Blog
T
The Blog of Author Tim Ferriss
博客园 - 叶小钗
I
Intezer
Martin Fowler
Martin Fowler
MongoDB | Blog
MongoDB | Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
ThreatConnect
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
小众软件
小众软件
T
The Exploit Database - CXSecurity.com
H
Help Net Security
T
Tenable Blog
WordPress大学
WordPress大学
F
Future of Privacy Forum
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
NISL@THU
NISL@THU
The Register - Security
The Register - Security
A
About on SuperTechFans
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
MyScale Blog
MyScale Blog
Malwarebytes
Malwarebytes
博客园_首页
T
Threatpost
C
CERT Recently Published Vulnerability Notes
Know Your Adversary
Know Your Adversary
T
Threat Research - Cisco Blogs
V
Vulnerabilities – Threatpost
C
CXSECURITY Database RSS Feed - CXSecurity.com
Blog — PlanetScale
Blog — PlanetScale
Recorded Future
Recorded Future
大猫的无限游戏
大猫的无限游戏
K
Kaspersky official blog
月光博客
月光博客
Jina AI
Jina AI
S
Securelist
Hugging Face - Blog
Hugging Face - Blog
G
GRAHAM CLULEY
腾讯CDC
S
Secure Thoughts
V
V2EX - 技术

暗无天日

读:把会议当系统来设计 - 暗无天日 读:把 JSON 当编程语言执行——一个迷你解释器的构造过程 - 暗无天日 读:tetris-sql——用一条SQL查询实现俄罗斯方块 - 暗无天日 TIL: dired 里按时间标记文件——dired-mark-if 与夏令时陷阱 - 暗无天日 读:从端点到行动——面向 AI 代理的后端设计 - 暗无天日 TIL DDD战术模式:用Clojure让代码说人话 - 暗无天日 读:Amin Bandali 与 Protesilaos 谈 Emacs 内置功能的深度定制 读:Clojure 世界的 AI 代理调教术——四个改变行为的 Skill TIL: minibuffer 激活时也能操作其他窗口 - 暗无天日 读:Tramp改了配置怎么不生效 - 暗无天日 读:为 project.el 写一个自定义后端 - 暗无天日 读:AI 时代的敏捷开发 - 暗无天日 TIL: 给 dired 异步命令加 nohup,让外部程序活过 Emacs 退出 TIL: elisp-fontify-semantically——让 Emacs 看懂你的 Elisp 代码 TIL-etags扫描外部库头文件 - 暗无天日 读:The Many Faces of flet——Elisp 局部函数的三种写法 读:df 与 du——为什么两个磁盘用量命令数字对不上 - 暗无天日 TIL-可观测性工具的成本盲区 - 暗无天日 TIL: 把 Emacs Buffer 打印成图片 读:gamegrid.el——Emacs 内置游戏是怎么写出来的 - 暗无天日 TIL: MCP 服务器不到 20 行 Python 就能写出来 TIL-AI 工具普及后的组织观察 - 暗无天日 读:当 Agent 开始写数据库——六个防御模式 - 暗无天日 读:右键菜单——Elisp 开发的隐藏利器 - 暗无天日 读:Git 仓库里的隐藏配置文件 - 暗无天日 读:20条软件工程定律 - 暗无天日 读:编译高性能 Emacs - 暗无天日 读:为什么我在终端里待了十年——一个 Emacs 用户的 GUI 观察 读:Event Sourcing——让你的数据库记住每一次变更 - 暗无天日 读:数据管道中Schema变更的四种形状 - 暗无天日 读:SES——Emacs内置的简易电子表格 - 暗无天日 TIL: 用 parallel 加速 rsync 迁移海量小文件 TIL:给 AI 一个更小的世界——技术选型的上下文窗口约束 - 暗无天日 读:emacs chat 技巧拾遗——从 bandali 的配置里捡到的那些技巧 读:当 Agent 成为生产调用者——四个被打破的运维假设 - 暗无天日 TIL: describe-personal-keybindings 查看你的自定义按键 - 暗无天日 读:Linux 创建指定大小文件的三种方式——dd、fallocate 与 truncate - 暗无天日 读:Yazi——在终端里管理文件的新选择 - 暗无天日 读:软件测试的反馈视角——CLEAR 原则从测试到运维 - 暗无天日 MobileOrg Android:从 API 17 迁移到 API 34 的实战记录 TIL:微服务与复杂度守恒——从单体到分布式的代价转移 - 暗无天日 读:MCP 时代的安全威胁——幻觉权限与三道防线 - 暗无天日 读:超越对话——用 Skills 和 Agents 工程化上下文 TIL:用 :box 给 mode-line 加内边距 org-mobile-push 卡顿排查实战:从黑盒到字节码反编译 - 暗无天日 读:用 LLM 重构遗留代码——三个陷阱与一套方法 - 暗无天日 读:AI 辅助编程的三种错误用法 - 暗无天日 读:Before GitHub - 暗无天日 读:AI in Software Architecture - 暗无天日 读:把成本当作 SLI - 暗无天日 TIL: 用进程树展开定位被脚本包装的 JVM 进程 - 暗无天日 读:Linux 删文件的真相——用 /proc 恢复被进程持有的已删除文件 - 暗无天日 读:Choosing a Python Logging Library in 2026 Emacs buffer 导出:五种方案对比 - 暗无天日 TIL: flymake 错误跳转加入 Evil 跳转列表 TIL: 用 Org-mode 列表管理选择题题库 - 暗无天日 读:sysstat 诊断链——从 sar 到 pidstat 的排查路径 读:理论靠谱,生产翻车的六个集成模式 - 暗无天日 读:双写问题——@Transactional 给不了的跨系统一致性 - 暗无天日 读:PostgreSQL 随机测试数据生成——从快速造数到自动化填充 - 暗无天日 读:逆萨丕尔-沃夫假说与编程语言 - 暗无天日 读:理解 MCP 架构——LLM 直接调 API 与 MCP 协议的对比 读:Emacs 连接数据库时密码放哪里 - 暗无天日 TIL:watch 命令的几个遗漏技巧 - 暗无天日 TIL:Python 3.15 的 sentinel() 内置函数 读:7 Techniques That Supercharged My Claude-Assisted Development 读:AI 编码代理的四种工作流 - 暗无天日 读:Agent 的瓶颈不在模型,在基础设施 - 暗无天日 读:EvoForge——用群体进化优化 AI Agent - 暗无天日 TIL:Google Stitch 的 DESIGN.md,给 AI 读的设计系统说明书 Emacs 批量搜索替换:从场景到命令 - 暗无天日 TIL: image-mode 的 header-line 中显示图片尺寸 dotfile仓库大扫除:清理过时的配置 - 暗无天日 读:The Art of Logging——日志规范清单 - 暗无天日 从CSS选择器到自然语言:网页自动化的两种范式与取舍框架 - 暗无天日 TIL-用 curl + w3m + awk 从 HTML 表格提取数据 读:Shell脚本安全编码的五条铁律 - 暗无天日 读:Emacs newcomers-presets theme —— 30+ 项新手预设一览 读:Protesilaos 的 Emacs 合理默认配置 —— 兼与 newcomers-presets 对比 控制 Bash 历史记录的 6 个场景 读:AI Agent 安全日志——从可见性与隐私的两难说起 - 暗无天日 读:AI Agent 生产化——一份从原型到上线的速查清单 - 暗无天日 读:LLM 生产环境六种失败原型——基准测试无法预测的那些故障 - 暗无天日 读:Prompt Injection 五层纵深防御——从输入过滤到审计追踪 - 暗无天日 读:为什么所有 Prompt Injection 防御都会被攻破——以及架构上该怎么办 - 暗无天日 读:JVM 后端性能调优备忘——从一次生产事故中学到的优化要点 - 暗无天日 读:Java 容器化——从 Fat JAR 到高效 Docker 镜像 读:整洁代码的几个通用原则——从 Go 生态看起 - 暗无天日 读:规则引擎——从 if-else 到业务规则管理 - 暗无天日 AI写作的语言指纹——如何让文字不那么像机器 - 暗无天日 读:50 条 Claude Code 技巧——一个工程经理的六个月使用心得 读:AI 辅助开发为什么让 E2E 测试更有价值 - 暗无天日 读:在Emacs中使用Claude Code(Spacemacs适配版) - 暗无天日 Claude Code 背后的工程哲学——读 Agent Harness Engineering 读:Agent Harness Engineering——AI 智能体不只是模型,还有套件 - 暗无天日 browser-harness:让 AI 直接接管你的浏览器 - 暗无天日 读:Security-First CI/CD —— DevSecOps 自动化实践指南 TIL: 数字小键盘的小数点陷阱与行内算术求值 - 暗无天日 读:Immutability 不是万能药,它是一种权衡 - 暗无天日 Conducty:给 Claude Code 加上项目记忆和并行执行能力 - 暗无天日
读:从API调用到Agent循环——构建 Agent 的七个阶段 - 暗无天日
2026-05-25 · via 暗无天日

目录

原文:Learn Agentic AI in Python: A 10-Step Journey 把从"调一次 API"到"写一个完整 Agent 循环"拆成十个递进练习,再归纳成七个概念阶段。这篇文章是它的配套讲解,逐层解读每个阶段的设计决策,回答一个核心问题:从调一次 API 到写一个完整的 Agent,中间到底要补哪些东西?

Stage 1:最简调用

每个 LLM 应用的起点都一样:创建客户端,调用 API,读取返回文本。以 Anthropic Python SDK 为例:

import anthropic

client = anthropic.Anthropic()
msg = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=256,
    messages=[{"role": "user", "content": "Say hi"}],
)
print(msg.content[0].text)

注意 content[0].text 而不是 .textcontent 是一个列表,每个元素是一个"块"(block),比如文本块、工具调用块等。现在你拿到的是 [{"type": "text", "text": "Hello!"}] ,后面引入工具调用时就会变成 [{"type": "text", ...}, {"type": "tool_use", ...}] ,但外层还是同一个列表。早点建立这个心智模型,后面加功能才不会措手不及。

Stage 2:让输出可解析

Stage 1 拿到了模型的文本回复,但输出的文本没有定式,模型今天可能输出"餐饮",明天可能输出"类别:餐饮(食品)",你没法直接用它做后续处理。解决方案是通过提示词明确输出格式,同时用 Pydantic 模型在收到结果后验证 LLM 返回是否符合格式。

from pydantic import BaseModel

class ExpenseResult(BaseModel):
    category: str
    confidence: float

result = ExpenseResult.model_validate_json(msg.content[0].text)

系统提示词要像 API 契约一样严格:说清楚"只输出 JSON",给出具体的字段结构,并禁止任何多余内容。"禁止多余内容"这几个字很重要,没有这几个字LLM总喜欢在 JSON 后面加一句友好的总结。

评注:** 系统提示词就是契约,跟传统软件开发里的接口定义是一个道理。你不是在跟模型聊天,你是在给它写规格说明书。传统接口靠编译器检查,LLM 的"接口"靠提示词约束 + 输出验证来保证:提示词引导模型输出正确格式,Pydantic 在模型不配合时兜底报错,两道防线少了哪一道都会出问题。

Stage 3:让它记住对话

Stage 1 和 2 解决了单次调用的问题——你能拿到结构化的回复了。但实际应用中用户会连续提问,模型需要理解上下文。可问题在于:LLM 本身没有状态、没有记忆,当前调用之外的上下文它一概不知。我们感知到的"连续对话",其实是你每次调用时把完整的消息历史重新发给它。

history = []

def ask(user_msg):
    history.append({"role": "user", "content": user_msg})
    reply = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=512,
        messages=history,
    ).content[0].text
    history.append({"role": "assistant", "content": reply})
    return reply

操作很简单:调用前追加用户消息,调用后追加助手回复。角色必须交替(user → assistant → user → ...)。数据存在你的请求里,不存在模型里。理解这一点,大部分关于"上下文窗口"和"记忆"的困惑就自然消解了。

评注:** 理解 LLM 无状态,是搞清楚 LLM 应用设计的关键一步。很多人刚意识到这件事时会觉得"这怎么用",但换个角度想:正因为无状态,控制权全在你手里。对话历史由你管理,想截断就截断,想摘要就摘要,想让模型"忘掉"前面说的什么,直接不发给它就行。

Stage 4:工具调用

Stage 1-3 构建了一个能记住上下文的聊天机器人,但它只能聊天,不能做事。工具调用把聊天机器人变成能执行动作的程序。核心循环比很多人想象的更简单:

while True:
    response = client.messages.create(
        ..., tools=TOOLS, messages=messages
    )
    if response.stop_reason == "end_turn":
        return response.content[0].text
    

上面这段伪代码省略了工具调用的处理逻辑,有两个需要注意的细节:

  1. 助手的回复要完整追加 response.content (不是只取文本),因为里面包含模型发出的工具调用块,下一轮循环模型需要看到这些块才能继续
  2. 工具执行结果要包在 user 角色的消息里返回,不是 assistant 。这看起来反直觉,但角色交替的规则要求这样做

评注:** 工具调用的本质是让 LLM 当"决策者",你的代码当"执行者"。模型决定调什么工具、传什么参数,你的代码负责实际执行并把结果喂回去。这个分工把 LLM 的能力(理解意图、生成参数)和程序的可靠性(确定性执行、错误处理)结合了起来。循环本身是通用的,从后面后面 Stage 7 可以看出,不管工具是做加法还是查数据库,循环逻辑一模一样。

Stage 5:可替换、可测试

到这一步聊天机器人能跑了,但代码通常也缠成一团,业务逻辑直接依赖 anthropicsqlite3 等外部库,没法单独测试。常见的解耦模式有三个:

  1. Protocol 模式 给 LLM 提供者定义接口,测试时可以传入一个带 .calls 列表的 MockProvider 替代真实 API
  2. Repository 模式 给持久层定义接口,内存字典和数据库后端实现同一个接口
  3. Service 层 通过构造函数接受上述两个依赖,负责编排:调 LLM → 解析结果 → 存储 → 返回

三个模式组合起来就是一套可测试的 Agent 架构,每一层职责单一,可以独立替换和测试。

评注:** 这些模式都不是 AI 领域的新发明。Protocol 就是依赖倒置(DIP),Repository 就是数据访问抽象,Service 层就是业务编排。有后端开发经验的读者应该觉得很眼熟。原文的好处在于把这些经典模式落到了 LLM 应用的具体场景里。把"调 API"和"存数据"从业务逻辑中拔出来之后,你就可以给核心逻辑写单元测试了,而不用每次都真的去调一次 LLM。

Stage 6:人在回路中

Stage 2 用 Pydantic 模型从 LLM 输出中解析出了 confidence 字段,但一直没说这个分数拿来干什么。拿到置信度之后,可以用它来决定是否需要人工介入:

def process(result, threshold=0.8):
    if result.confidence >= threshold:
        return result.category
    answer = input(f"Accept '{result.category}'? (Enter to confirm): ").strip()
    return answer or result.category

设计要点:让"接受"路径的操作成本最低(直接回车就行),只有用户不认同模型的分类时才需要手动输入修正。原文认为,有没有这一步,是"可信助手"和"偷偷把事情搞错的 AI"的分界线,也是"演示项目"和"生产级工作流"的差距。

评注:** 置信度阈值是生产级 AI 应用中容易被忽视的设计决策。阈值设高了,模型几乎都要人工确认,自动化失去意义;设低了,错误静默溜过,用户失去信任。原文用的 0.8 只是个起点,实际项目中这个值需要根据业务场景的错误容忍度来调整。核心原则:让机器在它拿得准的时候自主行动,在它犹豫的时候及时交出控制权。

Stage 7:通用化循环

前面 Stage 4 的工具循环里,工具名和函数的对应关系是写死的。这最后一步只改了一个地方,即把硬编码的函数调用替换成一个字典查找:

TOOL_FUNCTIONS = {
    "add": lambda a, b: a + b,
    "multiply": lambda a, b: a * b,

}

content = str(TOOL_FUNCTIONS[block.name](**block.input))

新增一个工具只需要加一条 schema 定义 + 一个字典条目。把 add / multiply 换成 search_webquery_dbsend_email ,循环逻辑完全一样。原文指出,LangChain、OpenAI Assistants 等框架底层也是这个模式。

评注:** 这一阶段揭示了一个重要的认知:Agent 框架的核心逻辑并不复杂。本质上就是一个 while 循环 + 工具注册表 + 消息历史管理。框架真正值钱的是周边的工程化细节:错误重试、并发控制、日志追踪、安全沙箱。理解了这一点之后,选择用框架还是自己写循环,就变成了一个工程决策:你的场景复杂到需要框架的周边能力吗?如果不需要,自己写一个循环可能比引入框架更合适。

总结:这条学习路径教会了什么

原文的结论很明确:框架在你能够自己写出底层之后才有意义。跳过底层直接上框架,一旦框架的行为跟你预期不一致,你连问题出在哪都查不出来。七个阶段都很小,单个看没什么特别的,串起来才构成完整的学习路径。做完之后,"Agentic AI" 的底牌就清楚了:一个循环、一套 schema、加上几个常见的设计模式。