欢迎光临
我们一直在努力

别只会调用大模型 API:用 Python 实现一个能干活的 AI Agent

大模型负责生成文字,AI Agent 负责完成任务。本文不使用复杂框架,从零实现一个能够自主选择工具、读取文件、查询时间并连续执行任务的最小 AI Agent。

前言:为什么 2026 年大家都在谈 Agent?

过去两年,我们写过太多这样的代码:向大模型发送一段提示词,然后打印回答。

response = client.chat.completions.create(
model="your-model",
messages=[{"role": "user", "content": "帮我分析这个项目"}],
)
print(response.choices[0].message.content)

这段代码能聊天,却不能真正分析项目,因为模型既看不到项目文件,也不能执行任何操作。它只能根据训练数据和提示词猜测。

AI Agent 的关键变化,是给大模型增加一个可控的“行动层”:模型可以判断当前需要什么信息,选择合适的工具,读取执行结果,再决定下一步做什么。

例如,当用户提出:

阅读 README.md,总结项目用途,并告诉我当前时间。

一个 Agent 可能按下面的顺序工作:

  • 判断需要读取文件;
  • 调用 read_file;
  • 获取文件内容并进行总结;
  • 判断还需要当前时间;
  • 调用 get_current_time;
  • 综合两次工具结果,生成最终回答。
  • 这就是 Agent 最核心的闭环:

    用户目标 -> 模型决策 -> 调用工具 -> 观察结果 -> 再次决策 -> 最终回答

    一、先设计两个安全工具

    为了让示例容易运行,我们只提供两个工具:读取指定目录内的文本文件,以及获取当前时间。

    项目结构如下:

    ```text
    mini-agent/
    ├── agent.py
    └── workspace/
    └── README.md

    先实现工具函数:

    from datetime import datetime
    from pathlib import Path
    from zoneinfo import ZoneInfo

    WORKSPACE = Path(__file__).parent.joinpath("workspace").resolve()

    def read_file(path: str) > str:
    """读取工作目录内的 UTF-8 文本文件。"""
    target = WORKSPACE.joinpath(path).resolve()

    # 防止 ../../ 等路径穿越访问工作目录之外的文件
    if target != WORKSPACE and WORKSPACE not in target.parents:
    return "错误:只能读取 workspace 目录内的文件"
    if not target.is_file():
    return f"错误:文件不存在:{path}"
    if target.stat().st_size > 100_000:
    return "错误:文件超过 100 KB,拒绝读取"

    try:
    return target.read_text(encoding="utf-8")
    except UnicodeDecodeError:
    return "错误:当前示例只支持 UTF-8 文本文件"

    def get_current_time(timezone: str = "Asia/Shanghai") > str:
    """返回指定 IANA 时区的当前时间。"""
    try:
    now = datetime.now(ZoneInfo(timezone))
    except Exception:
    return f"错误:无效时区:{timezone}"
    return now.isoformat(timespec="seconds")

    这里有一个很重要的细节:不要把整个文件系统直接开放给 Agent。

    工具参数来自模型,而模型可能受到错误提示词或 Prompt Injection 的影响。因此,工具本身必须检查路径、文件大小和数据类型。安全边界应该写在工具代码里,不能只靠一句“请勿读取敏感文件”的提示词。

    二、把工具描述交给大模型

    大模型不会自动知道 Python 函数的存在,我们需要用 JSON Schema 描述工具名称、用途和参数。

    TOOLS = [
    {
    "type": "function",
    "function": {
    "name": "read_file",
    "description": "读取 workspace 目录内的 UTF-8 文本文件",
    "parameters": {
    "type": "object",
    "properties": {
    "path": {
    "type": "string",
    "description": "相对于 workspace 的文件路径",
    }
    },
    "required": ["path"],
    "additionalProperties": False,
    },
    },
    },
    {
    "type": "function",
    "function": {
    "name": "get_current_time",
    "description": "获取指定 IANA 时区的当前时间",
    "parameters": {
    "type": "object",
    "properties": {
    "timezone": {
    "type": "string",
    "description": "例如 Asia/Shanghai 或 UTC",
    }
    },
    "required": [],
    "additionalProperties": False,
    },
    },
    },
    ]

    描述应当短而准确。如果多个工具的描述含糊或相互重叠,模型就更容易选错工具。

    三、实现 Agent 的核心循环

    下面使用兼容 Chat Completions 与 Function Calling 的 HTTP 接口。通过环境变量可以更换模型服务地址,不需要把密钥写进代码。

    先安装依赖:

    pip install requests

    配置环境变量:

    # Linux / macOS
    export LLM_API_KEY="你的密钥"
    export LLM_BASE_URL="https://你的服务地址/v1"
    export LLM_MODEL="支持工具调用的模型名称"

    Windows PowerShell:

    $env:LLM_API_KEY="你的密钥"
    $env:LLM_BASE_URL="https://你的服务地址/v1"
    $env:LLM_MODEL="支持工具调用的模型名称"

    然后在 agent.py 中加入 Agent 循环:

    import json
    import os
    from typing import Any

    import requests

    API_KEY = os.environ["LLM_API_KEY"]
    BASE_URL = os.environ["LLM_BASE_URL"].rstrip("/")
    MODEL = os.environ["LLM_MODEL"]

    TOOL_HANDLERS = {
    "read_file": read_file,
    "get_current_time": get_current_time,
    }

    def call_model(messages: list[dict[str, Any]]) > dict[str, Any]:
    response = requests.post(
    f"{BASE_URL}/chat/completions",
    headers={
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
    },
    json={
    "model": MODEL,
    "messages": messages,
    "tools": TOOLS,
    "tool_choice": "auto",
    "temperature": 0,
    },
    timeout=60,
    )
    response.raise_for_status()
    return response.json()["choices"][0]["message"]

    def execute_tool(name: str, arguments: str) > str:
    handler = TOOL_HANDLERS.get(name)
    if handler is None:
    return f"错误:未知工具 {name}"

    try:
    kwargs = json.loads(arguments or "{}")
    if not isinstance(kwargs, dict):
    return "错误:工具参数必须是 JSON 对象"
    return str(handler(**kwargs))
    except json.JSONDecodeError:
    return "错误:工具参数不是合法 JSON"
    except TypeError as exc:
    return f"错误:工具参数不正确:{exc}"
    except Exception as exc:
    return f"错误:工具执行失败:{exc}"

    def run_agent(user_input: str, max_steps: int = 8) > str:
    messages: list[dict[str, Any]] = [
    {
    "role": "system",
    "content": (
    "你是一个谨慎的开发助手。根据任务选择工具;"
    "不要猜测工具结果;完成目标后直接给出结论。"
    ),
    },
    {"role": "user", "content": user_input},
    ]

    for step in range(1, max_steps + 1):
    message = call_model(messages)
    messages.append(message)
    tool_calls = message.get("tool_calls") or []

    if not tool_calls:
    return message.get("content") or "模型没有返回内容"

    for tool_call in tool_calls:
    function = tool_call["function"]
    result = execute_tool(function["name"], function.get("arguments", "{}"))
    print(f"[步骤 {step}] 调用 {function['name']} -> {result[:100]}")

    messages.append(
    {
    "role": "tool",
    "tool_call_id": tool_call["id"],
    "content": result,
    }
    )

    return f"任务超过最大执行步数 {max_steps},已停止"

    if __name__ == "__main__":
    question = input("请输入任务:")
    print("\\nAgent 回答:")
    print(run_agent(question))

    运行程序:

    python agent.py

    输入任务:

    读取 README.md,总结这个项目的用途,然后告诉我上海当前时间。

    一次典型的执行日志可能是:

    [步骤 1] 调用 read_file -> 这是一个用于演示工具调用的最小 AI Agent 项目……
    [步骤 2] 调用 get_current_time -> 2026-07-27T15:30:18+08:00

    模型最后会基于真实文件内容和工具返回值组织答案,而不是凭空猜测。这也是 Agent 与普通对话接口最本质的区别。

    四、这段代码为什么已经算 Agent?

    判断一个程序是不是 Agent,不在于它使用了多少框架,而在于它是否形成了自主决策闭环。

    在这个示例中:

    • 目标:来自用户输入;
    • 决策者:大模型判断是否需要工具以及调用哪个工具;
    • 行动:Python 函数访问外部环境;
    • 观察:工具结果以 tool 消息返回给模型;
    • 循环:模型根据新信息继续决策,直到给出答案。

    许多 Agent 框架所做的事情,本质上也是管理这套循环,并在此基础上加入状态持久化、任务规划、重试、并发和可观测性。
    在这里插入图片描述

    图 1:决策、编排和执行三层分离,工具层负责守住真正的权限边界。

    五、真实项目中最容易踩的坑

    1. Agent 陷入死循环

    模型可能反复调用同一个工具。因此必须设置 max_steps,生产环境还应记录相同工具和参数的重复次数。

    2. 把模型当成安全边界

    系统提示词不是权限系统。文件访问范围、数据库权限、命令白名单和网络域名限制,都必须由程序强制执行。

    3. 工具返回内容太多

    如果直接把几十万行日志塞回上下文,不仅成本高,还会稀释真正有用的信息。应该在工具层分页、过滤或截断。

    4. 允许 Agent 直接执行任意 Shell 命令

    这是很多演示项目最危险的设计。删除文件、安装软件、发送消息等高风险操作,至少需要白名单、沙箱以及人工确认。

    5. 只看最终答案,不看执行轨迹

    Agent 的错误可能来自模型选错工具、参数错误、工具异常或上下文污染。生产系统需要保存每一步调用耗时、参数摘要、结果状态和 Token 消耗。

    六、MCP 在这里扮演什么角色?

    本文把工具直接写在 Python 程序里,优点是容易理解,缺点是工具和 Agent 紧密耦合。

    MCP(Model Context Protocol)试图为模型连接外部工具和数据源提供统一协议。你可以把文件系统、数据库、浏览器或内部平台封装成 MCP Server,让不同的 Agent 客户端用较一致的方式发现和调用它们。

    可以把两者简单理解为:

    Function Calling:模型如何表达“我要调用这个工具”
    MCP:客户端如何发现、连接和使用外部工具服务

    在这里插入图片描述

    图 2:Function Calling 描述调用意图,MCP 解决外部工具服务的发现与连接。

    MCP 不会自动解决权限、安全和结果可信度问题。即使接入 MCP,服务端仍然需要进行参数验证和权限控制。

    七、下一步可以怎样升级?

    这个最小 Agent 还可以沿着四个方向继续扩展:

  • 接入 RAG:让 Agent 检索企业文档或项目知识库;
  • 增加记忆:保存跨会话的用户偏好和任务状态;
  • 接入 MCP:把本地函数改造成可复用的工具服务;
  • 加入评测:准备固定任务集,统计成功率、调用次数和成本;
  • 人工审批:执行写文件、发消息等操作前请求确认;
  • 多 Agent 协作:让规划、编码和审查角色各自负责不同阶段。
  • 不过,多 Agent 并不一定比单 Agent 更好。角色越多,调用成本、状态同步和故障定位也越复杂。对多数业务来说,先把单 Agent 的工具、权限和评测做好,通常比急着搭建“AI 团队”更重要。

    总结

    一个最小可用的 AI Agent,只需要三个核心组件:

    • 一个支持工具调用的大模型;
    • 一组边界清晰、经过验证的工具;
    • 一个不断执行“决策—行动—观察”的循环。

    真正困难的部分不是让模型调用函数,而是确保它调用正确的函数、只能访问被授权的数据,并且出错时能够停止和追踪。

    当我们开始讨论权限、沙箱、评测、可观测性和人工审批时,AI Agent 才真正从有趣的 Demo 走向可以交付的软件系统。


    大语言模型MCP

    赞(0)
    未经允许不得转载:171主机测评 » 别只会调用大模型 API:用 Python 实现一个能干活的 AI Agent
    分享到: 更多 (0)

    评论 抢沙发

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