【学习笔记】不到 100 行,写一个最小的、能够自主进化的 agent

哎呀肚子疼 2026-07-23 16:59 1

这篇文章主要参考:Building a minimal AI agent from scratch。本文全程手打,没有 AI 修改/润色,主要是为了巩固一下学到的东西,顺便记录一下自己开发一个 agent 的过程。


我自己的开发环境是 Fedora 44 (Linux),所以用 Windows 运行我的代码可能会有些问题。


在这篇文章,你将会看到:



  • 古法编程

  • 已经快绝迹的 CV 工程师

  • agent 尝试搞懂自己

  • agent 尝试优化自己


最小 agent 构成


简单来说,agent 的执行流程可以概括成:



  1. 等待用户输入。

  2. AI 处理输入,决定要不要调用工具。如果不需要(任务已经完成或者需要用户提供更多信息),回到第一步。

  3. AI 调用工具,把工具执行结果作为输入返回给 AI,回到第二步。


伪代码如下:


messages = [] # 历史对话
while True:
# 1. 等待用户输入。
user_input = input()
messages.append({"role":"user","contnet":user_input}) # 将当前用户输入和历史对话合在一起,作为本轮对话的 AI 输入

# 2. AI 处理输入,决定要不要调用工具。如果不需要(任务已经完成或者需要用户提供更多信息),回到第一步。
while True:
response = get_ai_response(messages) # 获取 AI 输出
messages.append(response) # 将 AI 的回复追加到 messages

if response.tool_calls: # AI 回复包含工具调用
# 3. AI 调用工具,把工具执行结果作为输入返回给 AI,回到第二步。

# AI 可能会一次性返回多个工具调用请求,逐一处理
for tool in response.tool_calls:
result = execute_tool(tool.name)

messages.append(
{"role": "tool", "tool_call_id": tool.id, "content": result}
) # 将工具调用结果追加到 messages

continue # 回到第二步。

break # 不需要调用工具,回到第一步。

上面的代码为了简单,隐藏/简化了很多细节,但逻辑其实已经很完整了。


按照上面的伪代码的逻辑,完善一下细节,就能实现一个最小的 agent。话不多说,开干!



关于 messages
AI 要求的输入并不是一串用户输入的文本,而是一个 messages,messages 可以简单理解为包含了多轮对话的列表,每轮对话包含用户输入、ai回复、工具调用请求、工具调用结果。
按我的理解,设计 agent 也就是在设计这个 messages,也就是在设计 AI 的输入是什么样的,比如说,如果要设计一个包含记忆系统的 agent,那么在每次对话开始前,就会把记忆塞到 messages 里,然后再加本次用户输入,作为一个整体发给 AI 进行处理。


用 python 实现一个最小的 agent


作为学习/练手,这一次写一个最小的 agent,AI 模型就用 deepseek-v4-flash,价格很美丽,智商也凑合。


实现多轮对话


首先创建项目,因为我自己习惯用 uv 管理 python 项目,创建项目和添加依赖都用 uv 来做,所以我直接:


uv init learn_agent
cd learn_agent
source .venv/bin/activate # 激活虚拟环境

打开 Deepseek API 文档,先瞅一眼 api 是怎么调用的,把对话跑起来先。正好官方贴心的提供了 python 调用示例:


# Please install OpenAI SDK first: `pip3 install openai`
import os
from openai import OpenAI

client = OpenAI(
api_key=os.environ.get('DEEPSEEK_API_KEY'),
base_url="https://api.deepseek.com")

response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "Hello"},
],
stream=False,
reasoning_effort="high",
extra_body={"thinking": {"type": "enabled"}}
)

print(response.choices[0].message.content)

那还说啥了,直接复制粘贴(古法编程!)到 vscode 里。然后安装一下依赖、设置一下 DEEPSEEK_API_KEY:


uv add openai
export DEEPSEEK_API_KEY=my_deepseek_api

代码也稍微改改,把模型换成 deepseek-v4-flash,按照伪代码的结构,加入用户输入、加入循环(改成多轮对话)、打印思考过程:


# Please install OpenAI SDK first: `pip3 install openai`
import os
from openai import OpenAI

client = OpenAI(
api_key=os.environ.get('DEEPSEEK_API_KEY'),
base_url="https://api.deepseek.com")

messages = [
{"role": "system", "content": "You are a helpful assistant"}
]

while True:
user_input = input("> ")

messages.append({"role":"user","content":user_input})

response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=messages,
stream=False,
reasoning_effort="high",
extra_body={"thinking": {"type": "enabled"}}
)

messages.append(response.choices[0].message) # 将 AI 的回复追加到 messages

print(f"<think>\n{response.choices[0].message.reasoning_content}\n</think>")
print(response.choices[0].message.content)

运行程序,一切正常:



实现工具调用(run_bash)


接下来就是让 AI 能够调用工具。


因为是 agent,我们想让它能够执行命令,所以首先要实现的工具就是执行命令。因为我的开发环境是 linux,所以可以简单写个执行 bash 命令的函数,函数接收字符串,把字符串当作命令执行:


def run_bash(command: str, timeout: int = 30) -> str:
try:
result = subprocess.run(
command, # 执行的命令(字符串或列表)
shell=True, # 通过系统 Shell 执行
text=True, # 以文本(str)形式返回 stdout/stderr
env=os.environ, # 继承当前进程的环境变量
encoding="utf-8", # 指定文本编码
errors="replace", # 编码错误时替换为 � 等占位符
stdout=subprocess.PIPE, # 捕获标准输出
stderr=subprocess.STDOUT, # 将标准错误合并到标准输出
timeout=timeout, # 30 秒超时
)
return result.stdout
except Exception as e:
return str(e)

继续翻看官方文档,看看怎么实现工具调用,简单来说只需要三步:



  1. 添加工具描述


tools = [
{
"type": "function",
"function": {
"name": "run_bash",
"description": "run bash command in current dir",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "the command to be run",
},
"timeout": {
"type": "integer",
"description": "Max seconds to wait, default to 30",
}
},
"required": ["command"]
},
}
},
]


  1. 将工具描述加到 AI 输入里


response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=messages,
stream=False,
reasoning_effort="high",
tools=tools, # <- 添加这一行
extra_body={"thinking": {"type": "enabled"}}
)


  1. 检测 AI 是否返回工具,如果是run_bash就执行对应函数

    这里因为只有一个工具,只用了if做简单判断,如果有多个工具需要调用,这里的逻辑需要更改。


message = response.choices[0].message

if message.tool_calls:
for tool in message.tool_calls:
print(f"tool call:{tool.function.name}, args: {tool.function.arguments}")
if tool.function.name == "run_bash":
arguments = json.loads(tool.function.arguments)
result = run_bash(**arguments)
messages.append(
{"role": "tool", "tool_call_id": tool.id, "content": result}
) # 将工具调用结果追加到 messages

最后,完善一下逻辑,最终代码:


import os,subprocess,json
import readline # 用于终端输入时处理中文等多字节字符
from openai import OpenAI

def run_bash(command: str, timeout: int = 30) -> str:
try:
result = subprocess.run(
command, # 执行的命令(字符串或列表)
shell=True, # 通过系统 Shell 执行
text=True, # 以文本(str)形式返回 stdout/stderr
env=os.environ, # 继承当前进程的环境变量
encoding="utf-8", # 指定文本编码
errors="replace", # 编码错误时替换为 � 等占位符
stdout=subprocess.PIPE, # 捕获标准输出
stderr=subprocess.STDOUT, # 将标准错误合并到标准输出
timeout=timeout, # 30 秒超时
)
return result.stdout
except Exception as e:
return str(e)

client = OpenAI(
api_key=os.environ.get('DEEPSEEK_API_KEY'),
base_url="https://api.deepseek.com")

messages = [
{"role": "system", "content": "You are a helpful assistant"}
]

tools = [
{
"type": "function",
"function": {
"name": "run_bash",
"description": "run bash command in current dir",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "the command to be run",
},
"timeout": {
"type": "integer",
"description": "Max seconds to wait, default to 30",
}
},
"required": ["command"]
},
}
},
]

while True:
user_input = input("> ")

messages.append({"role":"user","content":user_input})

while True:
response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=messages,
stream=False,
reasoning_effort="high",
tools=tools,
extra_body={"thinking": {"type": "enabled"}}
)

message = response.choices[0].message
messages.append(message) # 将 AI 的回复追加到 messages

if message.tool_calls:
for tool in message.tool_calls:
print(f"tool call:{tool.function.name}, args: {tool.function.arguments}")
if tool.function.name == "run_bash":
arguments = json.loads(tool.function.arguments)
result = run_bash(**arguments)
messages.append(
{"role": "tool", "tool_call_id": tool.id, "content": result}
) # 将工具调用结果追加到 messages
continue

print(f"<think>\n{message.reasoning_content}\n</think>")
print(message.content)
break

测试一手:



我看我自己:



(但是脚本实际上并不支持流式思考,愚蠢的 deepseek)


完美!至此一个最小 agent 已经成型了。


让 agent 自主进化


有了命令行工具,实际上 AI 就可以做很多事情了,包括但不限于删盘(rm -rf */,^-^),也可以自主进化了,不过在进化之前,先提交一手,防止恢复不回来:


git init
git add .
git commit -m "init"

我们现在这个小脚本有个很严重的问题,那就是不支持流式输出,让这个小 agent尝试优化一下:



一通输出猛如虎,等了好一会,deepseek 说它改完了:



ctrl+D 关掉当前对话,重开一个试试:



完啦!把自己玩坏啦!

最新回复 (2)
  • 哎呀肚子疼 楼主 07-23 17:57
    1

    有点呆现在这个agent,等我改天优化优化,让它真的能自主进化 ^-^

  • jingyifun 07-23 23:44
    2

    占个佬的位置,学习一下gogogo

* 帖子来源Linux.do
返回