用最小化代码重现 LangGraph 和 OpenAI Agents SDK 的共同设计模式。高效帮助开发者理解 agent 框架本质而非黑盒使用。
我从头重建了它们。一个没有依赖的小框架,小到可以一次坐着读完,真正理解。
我使用过 LangGraph 和 OpenAI Agents SDK 这样的 agent 框架。它们很不错。但当你在学习时,它们会感觉像魔法。你把几样东西连在一起,一个 agent 就出现了,但你并不完全确定中间发生了什么。
我不想要更多魔法。我想看到机器是如何运作的。
所以我做了对我学习神经网络非常有效的事情。如果你看过 Andrej Karpathy 构建 micrograd,你就知道那种感觉。他用大约 150 行代码重建了 PyTorch 的核心概念,不是为了替代它,而是为了让它不可能被误解。之后,PyTorch 就不再是魔法了。
我想对 agent 做同样的事情。所以我构建了 microagent。
理解 agent 最快的方法是想象一个小办公室。
一个新助理在他们的第一天开始工作。他们有一个笔记本,几个可以求助的同事,和一个简单的习惯。坚持工作直到任务完成。
这就是整个想法。下面的一切只是这个办公室用代码写出来。归结为五个小部分。
办公室里最小的东西是一张便条。某人提出一个问题。助理写下一个决定。一个同事递回一个答案。这些都是一张便条。
@dataclass(frozen=True)
class Message:
role: str # "user", "assistant", or "tool"
content: str
name: str | None = None
简单来说,一张便条只需要三样东西。谁写的(role)、说了什么(content),以及一个可选的标签标注它来自哪个同事(name)。
它是 frozen 的,这是个花哨的词,意思是用笔写的。一旦一张便条存在,它就永远不会改变。这在接下来会很重要。
一张便条本身是没有用的。助理需要整个运行历史。这个历史就是 Context。把它想象为办公室笔记本。
这里有一条规则使一切都很简单。你永远不会擦掉一页。你只会在末尾添加新便条。
@dataclass(frozen=True)
class Context:
messages: tuple = ()
def add(self, *msgs):
# We do not change this notebook. We hand back a new one.
return Context(self.messages + tuple(msgs))
def last(self):
return self.messages[-1] if self.messages else None
两个小方法完成工作。add 接收你的新便条,交给一个全新的笔记本,它被添加到末尾,所以旧的永远不会被碰。last 只是偷看一下最新的便条。
因为笔记本只会增长,发生了什么的完整故事总是就在那里。你可以读回去,重放它,调试它。没有什么是隐藏的。内存不是我们后来添加的功能。它只是我们从不擦掉的笔记本。
助理不能单独做一切。需要算数字?叫有计算器的人。需要取文件?叫认识档案柜的人。
Tool 正是这样。你交给它一个请求,它交给你一个结果。
@dataclass
class Tool:
name: str
description: str
fn: object # the function to run
terminal: bool = False # does calling this end the job?
def __call__(self, **kwargs):
return str(self.fn(**kwargs))
简单来说,一个工具有一个名字让大脑可以请求它,一个描述让大脑知道它是做什么的,实际运行的函数,和一个标志说明调用它是否结束任务。
一个工具很特殊。它是那个同事,他的工作是说我们完成了,答案是 X。我们把那个标记为 terminal。
现在有趣的部分来了。谁决定下一步做什么?看笔记本,选择下一步行动。那个决策者就是 Policy。它是大脑。
一个决定有两部分,调用哪个工具和用什么参数调用它。
@dataclass(frozen=True)
class Action:
tool: str
args: dict = field(default_factory=dict)
reasoning: str = ""
理解为大脑指着一个同事说调用这一个,用这些细节。reasoning 是关于为什么的一个简短说明,所以故事以后容易跟踪。
这是整个项目中最重要的想法。大脑是可交换的。今天你可以用几行纯 Python 写它,完全没有 AI,只是为了看办公室运作。明天你可以换成真实的语言模型,甚至是你自己训练的小神经网络。办公室不会改变。只有大脑改变。
交换大脑,保持身体。
最后我们把它们绑在一起。Agent 持有大脑,认识它的同事,并在一个循环中遵循一个习惯。
class Agent:
def __init__(self, brain, tools, max_steps=6):
self.brain = brain
self.tools = {t.name: t for t in tools}
self.max_steps = max_steps
def run(self, user_input):
ctx = Context().add(Message("user", user_input))
for _ in range(self.max_steps):
action = self.brain(ctx, self.tools) # decide
tool = self.tools[action.tool]
result = tool(**action.args) # act
ctx = ctx.add( # write it down
Message("assistant", action.reasoning),
Message("tool", result, name=action.tool),
)
if tool.terminal: # are we done?
break
return ctx
把循环理解为绕一圈,四个小步骤。
问大脑接下来做什么。它查看笔记本并选择一个同事。
调用那个同事并得到结果。
把决定和结果都写进笔记本。
如果那个同事是结束任务的那个,停止。如果不是,再绕一圈。
那个循环是你听过的每个 AI agent 的整个引擎。决定、行动、写下来、重复。大框架把它装饰一下,但在底下,就是这样。
在我们运行它之前,这是所有五个汇聚在一个地方。
Message,一张单独的便条。
Context,你从不擦掉的笔记本。
Tool,一个你可以叫的同事。
Policy,拿出下一步的大脑。
Agent,运行循环直到任务完成的助理。
这是一个处理退款的支持 agent。它查找订单,检查政策,在人签署后发放退款,并回复客户。相同的五个部分,指向真实工具。
从上到下像读故事一样读它。每条思考线是大脑决定,每条箭头线是同事递回结果。
you asked: "Can I get a refund for order A1234? It arrived broken."
support thought: let me pull up the order first
-> look_up_order returned: A1234, Wireless Headphones, $79, delivered, reported defective
support thought: now check if it qualifies
-> check_policy returned: eligible, within the 30 day window and reported defective
support thought: it qualifies, and a refund needs a human to sign off
-> issue_refund returned: approved by a human, $79 refunded to the original card
support thought: all set, time to reply to the customer
-> final_answer returned: Good news, A1234 qualifies. I refunded $79 to your card, here in 3 to 5 days.
注意第三步。一个风险行动等待了人类批准。这不是一个特殊功能。它是一个包装另一个工具的工具。
这是我没有预料到的部分,也是构建小型项目的最好理由。
当你尝试添加困难的真实世界功能时,它们都不需要新的构建块。
Multi-agent 只是被用作同事的 agent。一个 agent 可以作为工具交给另一个 agent。这就是多 agent 系统的全部。
安全性只是一个有门的同事。用一个首先请求批准的工具包装风险工具。
不会爆炸的内存是一个函数。一个长笔记本进来,一个较短的总结笔记本出来。
从错误中恢复基本上是免费的。把错误写进笔记本,大脑在下一次看到自己的错误并修复它。
这些都是相同的五个部分,排列方式略有不同。当困难问题不需要新的基础时,这是一个强有力的迹象,说明五个部分是正确的。
microagent 不是生产框架,它也不尝试成为。如果你需要交付,使用 LangGraph 或 OpenAI Agents SDK。它们非常出色,解决了真实世界的混乱问题,一个教学项目不应该假装解决这些问题。
microagent 的全部要点是不同的。读每一行,理解每一个部分,然后去构建真正的东西,准确知道它下面是什么。
仓库在这里:github.com/rahul1368/microagent。
最好的入门方式是从头开始的笔记本。它一次一个地重建所有五个部分,用办公室故事,并在每一步运行。之后,代码会感觉像你已经知道的东西,因为你会自己写过它。
如果这有帮助,仓库上的一颗星意义重大,我很想听听哪个部分终于让你理解了 agent。