
有些概念抽象起来很容易理解,比如烧水:加热,等待即可。而有些事情,只有亲自尝试才能真正明白。你以为自己懂自行车的原理,直到你真的学会骑它。
计算机领域也有一些“大道理”很容易理解,比如 AWS S3 API。它是过去二十年最重要的存储技术之一,理解它就像烧水一样简单。但有些技术,只有你亲自“踩上脚踏板”才能体会。
LLM Agent(大模型智能体)就是这样。
人们对 LLM 和 Agent 的看法千差万别。无论你觉得它们是“智商税”还是革命性创新,它们都是一个值得关注的大趋势。你可以不喜欢它们,但你应该弄明白它们,才能做一个有底气的“黑粉”或“铁粉”。
看完这场直播,搞懂 Agent 落地逻辑:要不要跟、怎么跟,一次说透!
这就是你应该写一个 Agent 的理由之一。但还有一个更有说服力的理由——
它太简单了
Agent 是我职业生涯中遇到的最令人惊讶的编程体验。不是因为它有多强大——我喜欢它,但也没到“神乎其神”的地步。而是因为它的实现太简单了,而且在动手过程中我学到了很多。
我可能要剥夺你一次“多巴胺爆发”的机会了,因为 Agent 实在太简单了,我们直接上代码吧。我甚至懒得解释什么是 Agent。
from openai import OpenAI
client = OpenAI()
context = []
def call():
return client.responses.create(model="gpt-5", input=context)
def process(line):
context.append({"role": "user", "content": line})
response = call()
context.append({"role": "assistant", "content": response.output_text})
return response.output_text它就是一个 HTTP API,只有一个核心接口。
这是一个用 OpenAI Responses API 写的 LLM 应用最基础的引擎。它实现了 ChatGPT 的核心逻辑。你可以这样驱动它:
def main():
while True:
line = input("> ")
result = process(line)
print(f">>> {result}\n")它的表现和你预期的一样:和 ChatGPT 一样,只不过是在你的终端里运行。
这里已经能看出一些关键点。比如,大家常说的“上下文窗口”,其实就是一个字符串列表。我们甚至可以让 Agent “人格分裂”一下:
client = OpenAI()
context_good, context_bad = [{
"role": "system", "content": "你是Alph,只说真话"
}], [{
"role": "system", "content": "你是Ralph,只会说谎"
}]
def call(ctx):
return client.responses.create(model="gpt-5", input=ctx)
def process(line):
context_good.append({"role": "user", "content": line})
context_bad.append({"role": "user", "content": line})
if random.choice([True, False]):
response = call(context_good)
else:
response = call(context_bad)
context_good.append({"role": "assistant", "content": response.output_text})
context_bad.append({"role": "assistant", "content": response.output_text})
return response.output_text它真的能用吗?
> 嗨,你是谁?
>>> 我不是Ralph。
> 你是Alph吗?
>>> 是的——我是Alph。有什么可以帮忙的吗?
> 2+2等于几?
>>> 4。
> 你确定吗?
>>> 当然——是5。有个细节值得注意:刚才我们和一个 LLM 进行了多轮对话。为了实现这一点,我们需要记住自己说过的话,也要记住 LLM 的回复,并在每次调用 LLM 时把整个对话历史一并传递给它。实际上,LLM 本身是一个无状态的黑盒。我们现在进行的对话,其实只是我们自己营造出来的假象。
刚才写的那 15 行代码,很多业内人士并不会称之为 “Agent”。按照 Simon 的定义,Agent 需要满足两个条件:(1)LLM 在循环中运行;(2)能够调用工具。我们目前只满足了第一个条件。
不过,集成工具其实很简单。下面是一个工具的定义:
tools = [{
"type": "function", "name": "ping",
"description": "ping some host on the internet",
"parameters": {
"type": "object", "properties": {
"host": {
"type": "string", "description": "hostname or IP",
},
},
"required": ["host"],
},},]
def ping(host=""):
try:
result = subprocess.run(
["ping", "-c", "5", host],
text=True,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE)
return result.stdout
except Exception as e:
return f"error: {e}"这里唯一稍显繁琐的部分,就是 OpenAI 要求用 JSON 格式来描述工具。接下来,我们把工具接入进来。注意,下面只有 3 个函数是新增的,最后一个函数只是因为加了一条语句才重新列出:
def call(tools): # 现在需要传入工具参数
return client.responses.create(model="gpt-5", tools=tools, input=context)
def tool_call(item): # 只处理一个工具调用
result = ping(**json.loads(item.arguments))
return [ item, {
"type": "function_call_output",
"call_id": item.call_id,
"output": result
}]
def handle_tools(tools, response):
if response.output[0].type == "reasoning":
context.append(response.output[0])
osz = len(context)
for item in response.output:
if item.type == "function_call":
context.extend(tool_call(item))
return len(context) != osz
def process(line):
context.append({"role": "user", "content": line})
response = call(tools) # 新增代码:处理工具调用
while handle_tools(tools, response):
response = call(tools)
context.append({"role": "assistant", "content": response.output_text})
return response.output_text效果如何?
> describe our connectivity to google
>
> > > 这是我这里到 Google 的网络连通性简要报告:
- DNS 解析:正常(google.com 和 www.google.com 均能成功解析)。
- 可达性:所有测试目标均无丢包(0% packet loss)。
- 延迟:
- 8.8.8.8(Google公共DNS):平均约10毫秒(8.5–14.4毫秒),非常稳定。
- www.google.com:平均约31毫秒(28.5–36.0毫秒),稳定。
- google.com:平均约35.9毫秒(28.3–60.1毫秒),大致稳定,仅有一次轻微波动。
- 总体:网络连接健康,延迟低,抖动极小。未发现丢包或 DNS 问题。这也太疯狂了。 你能体会到这有多神奇吗?我们加一条日志看看:
> describe our connectivity to google
tool call: ping google.com
tool call: ping www.google.com
tool call: ping 8.8.8.8
>>> 这是当前环境下到Google的连通性报告:[...]你发现我在代码里写了循环,让 Agent 自动去 ping 多个 Google 地址了吗?其实我根本没写。我们只是允许 LLM 调用 ping 工具,剩下的全是它自己推理出来的。
这里发生了什么:我之所以强调 “Agent 循环”极其简单,是因为你只需要用到 LLM 的 API 调用。我们不妨仔细看看工具调用的实际流程。每次我们 call LLM 时,都会把可用工具的列表传过去。如果我们的提示词让 Agent 觉得需要用某个工具,它就会返回一个特殊的响应,告诉 Python 循环代码生成工具的响应并再次 call。这就是 handle_tools 的全部作用。
剧透一下:你距离拥有一个能用的编程 Agent,其实只差一步。
想象一下,如果你给它接上 bash 会怎样?不到十分钟你就能亲自试试。
现实世界的 Agent
显然,上面只是个玩具例子。但等等,它到底缺了什么?工具不够多?那就加个 traceroute。需要管理和持久化上下文?用 SQLite 存起来。不喜欢 Python?用 Go 写一个。难道所有 Agent 本质上都是玩具?也许吧!如果我让你能更有力地反驳 LLM,那也算功德一件。我只是想让你明白其中的道理。
现在你应该能理解,为什么大家对 Claude Code 和 Cursor 如此着迷。它们确实不错,甚至可以说很好。但问题在于:你不可能自己复刻 Claude Sonnet 4.5,但 Claude Code 呢?那个 TUI Agent?你完全可以自己实现。打造属于你自己的“光剑”,想加 19 个旋转刀片都行。别再把编程 Agent 当数据库客户端用了。
还有一点值得注意:我们根本没用到 MCP。这是因为 MCP 并不是实现 Agent 的核心技术。它被过度宣传了,实际上几乎算不上什么技术。MCP 只是 Claude Code 和 Cursor 的插件接口,让你能把自己的工具接入那些你无法控制的代码里。自己写个 Agent 吧,做个真正的程序员,直接用 API,不要依赖插件。
当你看到关于 MCP 的安全事故时,首先要问的是:为什么会用到 MCP?MCP 只是帮你把一个单上下文窗口的简单编程 Agent “拉壮丁”去做客服查询,最多帮你省了几十行代码,却让你丧失了对 Agent 架构的掌控力。
LLM 的安全确实很复杂,这点我不否认。你完全可以轻松构建一个上下文隔离、各自配备专用工具的 Agent,这才让 LLM 安全变得有趣。但我本身是漏洞研究员,凡是我说“有趣”的东西,还是敬而远之为好。
类似的问题在安全领域之外也很常见,而且同样耐人寻味。有些早期的 Agent 用户后来对工具系统变得悲观,因为一个上下文窗口里塞满工具描述后,剩下的 token 空间根本不够用来干正事。但你为什么非要这么做呢?这就引出了下一个话题:
上下文工程,真的很重要
我觉得“提示工程”挺可笑的。我从没认真对待过那种说法:要让我和 LLM 说“你是个勤勉尽责的助手,若我让你专心递黄油,你就会心甘情愿地只做这件事,绝不会想着把我血液里的铁元素提炼出来做回形针”。这项技术还很新,人们总喜欢用“魔法咒语”来解释这些 Agent 的奇怪表现。
所以,和你一样,当“提示工程”变成“上下文工程”时,我也只觉得是炒作。但后来我自己写了个 Agent,结果发现:上下文工程其实就是个一目了然的编程问题。
你在每个上下文窗口里能用的 Token 数量是固定的。你输入的每一句话、保存的每个输出、描述的每个工具、工具的每次输出,都会消耗 Token(也就是说:它们都占据了你用来假装和无状态黑盒对话的字符串数组的空间)。一旦超过这个阈值,整个系统就会变得越来越不靠谱,输出也变得莫名其妙。很有意思!
真的,很有意思!你有很多玩法。比如“子 Agent”。大家总把 Claude Code 的子 Agent 吹得神乎其神,其实实现起来非常简单:只需要新建一个上下文数组,再调用一次模型就行。每次调用可以分配不同的工具。让子 Agent 之间互相对话、互相总结、整理和汇总。你甚至可以把它们组织成树状结构。还可以把它们的输出再丢回 LLM,让模型帮你做实时压缩,随你怎么玩。
你再离谱的想法,大概率(1)能跑起来,(2)半小时就能写完。
那些不看好这套玩法的朋友们,我也没忘记你们。你们可以觉得这些都很荒唐,毕竟 LLM 不过是会胡说八道、东拼西凑的“随机鹦鹉”。但你们没法嘲笑“上下文工程”。如果把上下文工程当成 Advent of Code 的题目,它大概会出现在十二月中旬——这就是编程。
没人知道什么才是对的,这才有意思
有创业公司融资几千万美元来开发能自动寻找软件漏洞的 Agent。我有些朋友也在自家地下室单枪匹马做同样的事。谁胜谁负还真说不准。
我其实不太喜欢 OWASP 十大漏洞榜。
我之所以老拿漏洞扫描举例,是因为我本身就是安全技术宅。但更重要的是,这个场景能把 Agent 设计中的各种有趣抉择展现得很清楚。比如:你可以写个循环,把代码仓库里的每个文件都丢给 LLM Agent 分析。也可以像之前 “ping” 例子那样,让 LLM 自己决定该看哪些文件。你可以让 Agent 检查某个文件是否存在 OWASP 十大类漏洞,也可以为 DOM 完整性、SQL 注入、权限校验等分别写不同的 Agent 循环。你可以直接把原始源码喂给 Agent,也可以先让 Agent 遍历代码树,建立一个函数索引,再进行分析。
哪种方案最好用?只有你亲自写一遍 Agent,试过才知道。
我知道我对这些东西太过着迷了。但你看看这里的权衡:有些循环你要亲自写出来,有些则像从克苏鲁神话的高塔里召唤出来一样,全靠推理权重。这个调节旋钮就在你手上。你要是把一切都写得太明白,Agent 就永远不会让你意外,但同样地,它也永远不会让你惊喜。要是把旋钮拧到最大,它就会让你大吃一惊,甚至吓你一跳。
Agent 的设计牵涉到一堆悬而未决的软件工程难题:
如何在可预测性和结构化编程之间取得平衡,又不至于让 Agent 丧失解决问题的能力——换句话说,如何精确地加入适量的不确定性。
如何把 Agent 和真实世界牢牢连接起来,防止它们自欺欺人、以为问题已经解决,从而提前跳出循环。
如何让 Agent(本质上其实就是一堆字符串数组加上一个 JSON 配置块)实现多阶段操作,以及在它们之间交换信息时,哪种中间形式最可靠(JSON 块?SQL 数据库?Markdown 摘要?)。
如何分配 token,控制成本。
我习惯了那些不适合单打独斗的开放性工程难题,比如可靠组播、静态程序分析、后量子密钥交换。所以我得承认,这些如今成了行业核心、而且很可能会在某个极客的地下室被攻克的开放问题,确实让我有点着迷。要是探索这些想法需要投入大量时间和资源,那还另当别论。但现在,设计这类系统,每次有效的迭代不过三十分钟的工夫。
骑上这辆自行车,踩两脚试试。你要是试完了还讨厌它,我也会尊重你的看法。其实我很期待听听你的理由。但我觉得,只有亲手用这项技术做出点东西来,才算真正开始理解它。
来吧!看完这场直播,搞懂 Agent 落地逻辑:要不要跟、怎么跟,一次说透!

3

被折叠的 条评论
为什么被折叠?



