欢迎光临
我们一直在努力

200行Python代码,让程序自己会干活——手写一个AI Agent

200行Python代码,让程序自己会干活——手写一个AI Agent

2026 年了,光会调 API 已经不够用。这篇文章带你手写一个真正能"自主决策、调用工具、完成任务"的 AI Agent,代码不超过 200 行。


一、什么是 AI Agent?

先说清楚概念。

你平时用大模型,大概率是这个流程:发一条消息 → 模型回一条消息 → 结束。这叫"单轮对话",模型就是个高级问答机。

但 Agent 不一样。Agent 的核心是 “自主循环”:模型不只是回答问题,而是自己判断接下来该干什么——要不要查资料?要不要算个数?要不要读写文件?——然后动手去干,干完再根据结果决定下一步。

打个比方:

  • 普通 LLM 像一个图书馆管理员,你问什么他答什么
  • Agent 像一个实习生,你跟他说"帮我整理这周的日报",他会自己去翻文件、读内容、写总结、保存结果

Agent 跟 RAG(检索增强生成)也不是一回事。RAG 只管"查了再说",Agent 是"边想边干"。


二、核心原理:三步循环

任何一个 Agent,底层都是同一个循环:

用户指令 → LLM 推理 → 需要工具? → 是 → 调用工具 → 拿到结果 → LLM 推理 → … → 不需要了 → 返回最终结果

翻译成代码就是三个核心组件:

  • LLM 大脑:负责理解任务、决定用哪个工具、根据结果调整策略
  • 工具箱:一组可调用的函数(读文件、搜网页、执行命令……)
  • 调度循环:不停地"思考 → 行动 → 观察结果 → 再思考",直到任务完成
  • 下面我们一步步搭。


    三、环境准备

    先确认 Python 版本(3.10+ 就行):

    python –version

    需要装两个核心库:

    pip install openai httpx

    这里用 OpenAI 兼容接口(DeepSeek 也能用,接口完全兼容,换 key 和 base_url 就行),方便你随时切换模型。

    目录结构:

    agent_demo/
    ├── agent.py # 主程序
    ├── tools.py # 工具定义
    └── .env # API Key(不要提交到 git)

    .env 文件内容:

    OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
    OPENAI_BASE_URL=https://api.deepseek.com/v1
    MODEL_NAME=deepseek-chat


    四、第一步:定义工具箱

    Agent 能干什么,取决于你给它什么工具。我们先定义三个实用的:

    # tools.py
    import json
    import os
    from datetime import datetime

    def get_current_time() > str:
    """获取当前日期和时间"""
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    def read_file(filepath: str) > str:
    """读取文件内容,传入文件路径"""
    if not os.path.exists(filepath):
    return f"文件不存在: {filepath}"
    try:
    with open(filepath, "r", encoding="utf-8") as f:
    content = f.read()
    # 太长就截断,避免撑爆上下文
    if len(content) > 5000:
    return content[:5000] + "\\n…(内容过长,已截断)"
    return content
    except Exception as e:
    return f"读取失败: {str(e)}"

    def write_file(filepath: str, content: str) > str:
    """写入文件,传入文件路径和内容"""
    try:
    os.makedirs(os.path.dirname(filepath) or ".", exist_ok=True)
    with open(filepath, "w", encoding="utf-8") as f:
    f.write(content)
    return f"写入成功: {filepath}{len(content)} 字符)"
    except Exception as e:
    return f"写入失败: {str(e)}"

    def run_command(command: str) > str:
    """执行系统命令并返回结果(仅限安全命令)"""
    import subprocess
    # 安全白名单:只允许这些前缀的命令
    allowed = ["ls", "cat", "echo", "wc", "du", "find", "grep", "head", "tail", "date"]
    cmd_parts = command.strip().split()
    if not cmd_parts or cmd_parts[0] not in allowed:
    return f"不允许执行该命令: {cmd_parts[0] if cmd_parts else '空命令'}(白名单: {allowed})"
    try:
    result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)
    output = result.stdout or result.stderr
    if len(output) > 3000:
    output = output[:3000] + "\\n…(输出过长,已截断)"
    return output
    except subprocess.TimeoutExpired:
    return "命令执行超时"
    except Exception as e:
    return f"命令执行错误: {str(e)}"

    # 工具注册表:把函数名映射到函数对象
    TOOLS = {
    "get_current_time": get_current_time,
    "read_file": read_file,
    "write_file": write_file,
    "run_command": run_command,
    }

    关键设计点:

    • 每个函数都写了 docstring,这很重要——Agent 就是靠读函数描述来决定用哪个工具的
    • run_command 有安全白名单,别让 Agent 在你机器上 rm -rf /
    • 文件读写都加了长度限制,防止单次工具调用撑爆 LLM 的上下文窗口

    五、第二步:定义工具描述(Function Calling 格式)

    LLM 不认识 Python 函数,你得用标准的 Function Calling 格式告诉它"我能用哪些工具、每个工具有什么参数":

    # agent.py(接上面)

    TOOL_DEFINITIONS = [
    {
    "type": "function",
    "function": {
    "name": "get_current_time",
    "description": "获取当前日期和时间,不需要参数",
    "parameters": {
    "type": "object",
    "properties": {},
    "required": []
    }
    }
    },
    {
    "type": "function",
    "function": {
    "name": "read_file",
    "description": "读取指定文件的内容,用于查看文件、分析数据",
    "parameters": {
    "type": "object",
    "properties": {
    "filepath": {
    "type": "string",
    "description": "要读取的文件路径"
    }
    },
    "required": ["filepath"]
    }
    }
    },
    {
    "type": "function",
    "function": {
    "name": "write_file",
    "description": "将内容写入文件,用于保存结果、生成报告",
    "parameters": {
    "type": "object",
    "properties": {
    "filepath": {
    "type": "string",
    "description": "目标文件路径"
    },
    "content": {
    "type": "string",
    "description": "要写入的内容"
    }
    },
    "required": ["filepath", "content"]
    }
    }
    },
    {
    "type": "function",
    "function": {
    "name": "run_command",
    "description": "执行安全的系统命令(仅限 ls/cat/wc/du/find/grep/head/tail/date/echo),用于获取系统信息",
    "parameters": {
    "type": "object",
    "properties": {
    "command": {
    "type": "string",
    "description": "要执行的命令"
    }
    },
    "required": ["command"]
    }
    }
    }
    ]


    六、第三步:调度循环——Agent 的心脏

    这是整个 Agent 最核心的部分。循环逻辑很简单:

    # agent.py(核心循环)

    import json
    import os
    from openai import OpenAI
    from dotenv import load_dotenv
    from tools import TOOLS, TOOL_DEFINITIONS

    load_dotenv()

    client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url=os.getenv("OPENAI_BASE_URL")
    )

    MODEL = os.getenv("MODEL_NAME", "deepseek-chat")

    # 系统提示词——定义 Agent 的行为模式
    SYSTEM_PROMPT = """你是一个实用的 AI 助手,可以调用工具完成任务。

    工作原则:
    1. 接到任务后,先分析需要哪些步骤
    2. 每次只执行一步——调用一个工具,观察结果,再决定下一步
    3. 工具返回结果后,根据结果调整计划
    4. 任务完成时,用自然语言总结你的工作成果
    5. 如果工具调用失败,尝试其他方法,不要放弃

    可用工具:get_current_time(获取时间)、read_file(读文件)、write_file(写文件)、run_command(执行安全命令)
    """

    def run_agent(task: str, max_steps: int = 10) > str:
    """运行 Agent,最多执行 max_steps 步"""
    messages = [
    {"role": "system", "content": SYSTEM_PROMPT},
    {"role": "user", "content": task}
    ]

    print(f"\\n{'='*60}")
    print(f"📋 任务: {task}")
    print(f"{'='*60}\\n")

    for step in range(1, max_steps + 1):
    print(f"— 第 {step} 步 —")

    # 调用 LLM,附上工具定义
    response = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    tools=TOOL_DEFINITIONS,
    tool_choice="auto", # 让模型自己决定要不要调工具
    temperature=0.1 # 低温度,保证决策稳定
    )

    msg = response.choices[0].message

    # 情况1:模型觉得任务完成了,直接返回文本
    if msg.content and not msg.tool_calls:
    print(f"💬 Agent: {msg.content}")
    messages.append({"role": "assistant", "content": msg.content})
    return msg.content

    # 情况2:模型要调用工具
    if msg.tool_calls:
    tool_call = msg.tool_calls[0]
    func_name = tool_call.function.name
    func_args = json.loads(tool_call.function.arguments)

    print(f"🔧 调用工具: {func_name}({json.dumps(func_args, ensure_ascii=False)})")

    # 执行工具
    tool_func = TOOLS.get(func_name)
    if not tool_func:
    result = f"工具 {func_name} 不存在"
    else:
    try:
    result = tool_func(**func_args)
    except Exception as e:
    result = f"工具执行出错: {str(e)}"

    # 截断过长的结果
    if len(str(result)) > 4000:
    result = str(result)[:4000] + "\\n…(已截断)"

    print(f"📤 结果: {str(result)[:200]}{'…' if len(str(result)) > 200 else ''}")

    # 把工具调用和结果加入对话历史
    messages.append({
    "role": "assistant",
    "content": None,
    "tool_calls": [{
    "id": tool_call.id,
    "type": "function",
    "function": {
    "name": func_name,
    "arguments": tool_call.function.arguments
    }
    }]
    })
    messages.append({
    "role": "tool",
    "tool_call_id": tool_call.id,
    "content": str(result)
    })

    print(f"\\n⚠️ 达到最大步数限制 ({max_steps}),Agent 停止")
    return "任务未完成:达到最大步数限制"

    if __name__ == "__main__":
    task = input("请输入任务: ")
    result = run_agent(task)
    print(f"\\n{'='*60}")
    print(f"✅ 最终结果:\\n{result}")

    代码本身不复杂,但有几个值得展开的设计决策:

    1. 为什么 tool_choice="auto"?

    设成 auto 之后,LLM 自己判断"这轮我是该说话还是该调工具"。如果设成 required,模型每轮必调工具,处理简单问题时反而会"为了调工具而调工具"——比如你说"你好",它可能去读一个不存在的文件再回来,多浪费一轮 token。

    2. 温度设 0.1 的必要性

    Agent 的每一步决策是"链式"的——这一步的错会影响后面每一步。温度高了模型会"瞎猜",选了不该选的工具或者传了错的参数,整条执行链就歪了。0.1 是实测下来兼顾稳定性和灵活性的值。

    3. 为什么结果要截断?

    如果不截断,Agent 读了一个 10 万行的日志文件,会把整个上下文窗口撑满。之后的推理全部失效。4000 字符是经验值——够模型判断结果有没有用,又不会炸窗口。


    七、实战:让 Agent 分析一个真实项目

    光说不练不行。假设你的工作目录 /home/user/projects/ 里有一堆 Python 文件,你想让 Agent 自动统计代码质量。

    先造一个接近真实项目的测试环境:

    mkdir -p /tmp/demo_project/src /tmp/demo_project/tests /tmp/demo_project/docs
    cat > /tmp/demo_project/src/main.py << 'EOF'
    import sys
    from utils import parse_args, load_config

    def main():
    args = parse_args(sys.argv[1:])
    config = load_config(args.config_path)
    result = process_data(config)
    print(f"处理完成,共 {result['count']} 条记录")

    if __name__ == "__main__":
    main()
    EOF

    cat > /tmp/demo_project/src/utils.py << 'EOF'
    import json, os

    def parse_args(args):
    return {"config_path": args[0] if args else "config.json"}

    def load_config(path):
    with open(path) as f:
    return json.load(f)

    def process_data(config):
    return {"count": len(config.get("items", [])), "status": "ok"}

    def format_output(data):
    return json.dumps(data, indent=2, ensure_ascii=False)
    EOF

    cat > /tmp/demo_project/tests/test_utils.py << 'EOF'
    from src.utils import parse_args, load_config, format_output

    def test_parse_args():
    assert parse_args(["test.json"]) == {"config_path": "test.json"}

    def test_parse_args_default():
    assert parse_args([]) == {"config_path": "config.json"}

    def test_format_output():
    result = format_output({"count": 5})
    assert "count" in result
    assert "5" in result
    EOF

    echo "# Demo Project – 数据处理工具" > /tmp/demo_project/README.md
    echo "python-dotenv>=1.0.0" > /tmp/demo_project/requirements.txt

    现在运行 Agent,输入一个描述完整但不过于具体的任务:

    python agent.py

    请分析 /tmp/demo_project 项目:查看目录结构、统计 Python 代码行数、
    列出所有函数、生成一份代码质量简报保存到 /tmp/demo_project/REPORT.md

    Agent 的实际执行过程:

    第1步 — run_command("find /tmp/demo_project -name '*.py' | xargs wc -l")
    → 统计每个文件的代码行数

    第2步 — run_command("grep -n '^def ' /tmp/demo_project/src/*.py")
    → 列出所有函数定义

    第3步 — read_file("/tmp/demo_project/README.md")
    → 了解项目用途

    第4步 — write_file("/tmp/demo_project/REPORT.md", "项目代码质量简报…")
    → 根据前三步的信息生成报告

    第5步 — 输出自然语言总结:"项目包含 2 个源文件共 N 行代码,
    定义了 X 个函数,测试覆盖…"

    你不需要逐条告诉它"先 ls、再 wc、再 grep"——你只需要说想要什么结果,它自己拆解步骤。

    这才是 Agent 和普通脚本的本质区别。


    八、进阶:给你的 Agent 加更多工具

    上面只是个最小原型。实际用起来你会想加更多能力。下面给几个我实际在用、效果不错的工具:

    8.1 网页搜索

    def web_search(query: str) > str:
    """搜索网页,传入搜索关键词,返回前5条结果的标题和链接"""
    import httpx
    # 使用 Tavily Search API(免费额度1000次/月)
    # 也可以用 SearXNG 自建、SerpAPI、Bing API 等
    try:
    resp = httpx.post(
    "https://api.tavily.com/search",
    json={"query": query, "api_key": "YOUR_TAVILY_KEY", "max_results": 5},
    timeout=15
    )
    data = resp.json()
    results = []
    for r in data.get("results", []):
    results.append(f"- {r['title']}\\n {r['url']}\\n {r['content'][:200]}")
    return "\\n".join(results) if results else "未找到结果"
    except Exception as e:
    return f"搜索失败: {e}"

    8.2 HTTP 请求

    def http_request(url: str, method: str = "GET") > str:
    """发送 HTTP 请求,传入 URL 和请求方法,返回响应文本(前3000字符)"""
    import httpx
    try:
    resp = httpx.request(method.upper(), url, timeout=15, follow_redirects=True)
    content = resp.text[:3000]
    return f"状态码: {resp.status_code}\\n内容: {content}"
    except Exception as e:
    return f"HTTP 请求失败: {e}"

    8.3 代码执行(沙箱)

    def execute_python(code: str) > str:
    """在隔离环境中执行 Python 代码并返回结果,适合做数学计算或数据处理"""
    import io, sys, traceback
    old_stdout = sys.stdout
    sys.stdout = buffer = io.StringIO()
    try:
    # 限制执行环境
    exec(code, {"__builtins__": {
    "print": print, "len": len, "range": range,
    "int": int, "float": float, "str": str, "list": list,
    "dict": dict, "set": set, "tuple": tuple, "sum": sum,
    "max": max, "min": min, "sorted": sorted, "abs": abs,
    "round": round, "enumerate": enumerate, "zip": zip,
    "map": map, "filter": filter, "json": __import__("json")
    }})
    except Exception:
    traceback.print_exc(file=buffer)
    finally:
    sys.stdout = old_stdout
    output = buffer.getvalue()
    return output[:3000] if output else "(无输出)"

    加上这些工具之后,你的 Agent 就能搜资料、调 API、跑代码了——基本上能覆盖日常开发中 80% 的自动化场景。


    九、踩坑记录

    从头写 Agent 的过程中,遇到几个坑,记录一下免得你重蹈覆辙:

    坑1:工具结果太长,炸了上下文

    我最早没做截断,Agent 拿 cat 读了一个 5 万行的 JSON 文件,后面的推理全部抽搐。解法:每个工具返回值硬截断到 3000-4000 字符。

    坑2:模型陷入"工具循环"

    有一次 Agent 读文件失败了(路径写错),它没有意识到应该换个思路,而是不断换参数重试同一个工具,10 步全用完还没完成任务。解法:在系统提示词里加一句"同一工具重试 2 次还失败就换方法"。

    坑3:Function Calling 格式不兼容

    OpenAI 和 DeepSeek 的 Function Calling 格式 99% 兼容,但参数类型声明有细微差别。如果遇到 tool_choice 报错,换成 "auto" 就行。

    坑4:温度设太高导致决策不稳定

    温度 0.7 的时候,Agent 有时候会"灵机一动"跳过关键步骤直接写结论。降到 0.1 就好了。Agent 场景追求的不是创意,是可靠。


    总结

    200 行代码,核心就是一个循环:LLM 想 → 调工具 → 看结果 → 再想。搞懂了这个,LangChain、AutoGen、MCP 协议,都是一层皮。

    别光看。打开编辑器,把上面三段的代码敲一遍。Agent 这东西,跑通一次比读十篇文章都管用。

    下一篇讲怎么把这个 Agent 挂到飞书上,手机发消息就能让它干活——关注不走丢。


    📌 作者:Aliaoo 🚀 专注 AI 工具实战。每篇都是亲测可跑的教程。

    📬 觉得有用就点个赞,想追更就点个关注。有问题评论区见。


    📌 作者:Aliaoo 🚀 专注 AI 工具实战、云部署、自动化脚本。每篇都是亲测可跑的教程。

    CSDN开发云

    🖥️ 需要云服务器跑项目? 👉 CSDN 开发云常年折扣,新用户首单特惠

    📬 觉得有用就点个赞,想追更就点个关注——下次搜到我不靠缘分。

    赞(0)
    未经允许不得转载:171主机测评 » 200行Python代码,让程序自己会干活——手写一个AI Agent
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址