AI Agent实战:从零搭建带工具调用的服务

AI Agent实战:从零搭建带工具调用的服务 最近在团队里聊到一个话题一边是 AI 编程工具已经成了日常写代码的标配另一边却总有声音觉得“AI 是不是被吹过头了”。其实这两种感受并不矛盾。真正被高估的是单点模型的聊天能力真正被低估的是模型能力之上的工程化落地——也就是 AI Agent、工具调用、私有化部署和业务系统集成这些看起来很“工程”的环节。这篇文章会从一个经常被转发的观点切入AI 市场被低估了。但我不会去讨论估值逻辑而是从开发者的视角把它翻译成一组可以动手验证的技术实践。文章会拆解 AI 应用开发的核心概念然后带你从零搭建一个带工具调用能力的 AI Agent 服务包含完整代码、运行命令、常见报错和工程化建议。如果你正在做 AI 应用开发、AI Agent 相关项目或者只是想知道“除了聊天大模型还能怎么接入业务系统”这篇文章应该能给你一个比较完整的闭环思路。1. 从“AI 市场被低估”说起1.1 投资观点背后的技术线索Eric Vishria 认为 AI 市场被低估了。这类观点在被传播的过程中往往被简化成“买不买 AI 股票”的判断但对于技术从业者来说它真正值得关注的点在于为什么资本市场会觉得 AI 有增量空间答案不太可能只靠聊天机器人。聊天机器人解决的是“人问模型答”的问题价值场景有限。真正让人兴奋的是把大模型嵌进一个完整的业务流程里让模型去判断任务、选择工具、调用系统、汇总结果最终替人完成一个多步骤的工作。这个方向就是 AI Agent。换句话说模型本身的能力已经足够强了缺的是把它变成生产力的工程链路。1.2 被低估的四个方向结合日常开发场景我认为被低估的技术方向主要有四个。第一个是 AI 编程工具。很多团队对 AI 编程的理解还停留在“自动补全”上但实际使用中它最大的价值在于批量重构、测试生成、解释历史代码、跨文件修改。真正用好的人效率提升不是线性的。第二个是 AI Agent 和工作流自动化。从“回答一个问题”到“完成一整个任务”这个跨越被严重低估了。比如让 Agent 自动查天气、算价格、生成报表再把它接入内部系统能替代大量重复劳动。第三个是本地部署与私有化模型。很多企业对数据安全有硬性要求外部 API 不能碰核心数据。于是开源模型的本地部署、微调、蒸馏变得非常关键。这个领域很“工程”门槛也更高但市场价值很大。第四个是垂直行业场景应用。金融、医疗、客服、运维、电商每个行业都有自己的一套数据结构和业务逻辑。通用模型解决不了差异化问题行业知识和工具链才是壁垒。2. AI 应用开发的价值链2.1 模型层、编排层、应用层从技术视角看AI 应用开发可以分成三层。模型层是底座包括大模型 API 和开源权重模型。这一层技术密集但不是大多数开发者的主战场。底座模型的能力通过 API 暴露出来开发者只需要知道如何调用即可。编排层是 AI Agent 的核心。它负责理解用户意图、拆解任务、调用工具、管理上下文、处理多轮对话。工具调用、RAG 检索增强生成、记忆机制都属于这一层。应用层是最终交付物可以是 Web 服务、内部系统、命令行工具也可以是 IM 机器人。这一层要解决的是真实业务问题比如客服工单自动分派、数据分析报告生成、代码审查辅助。2.2 为什么说机会在编排层和应用层模型层有很高的资金和算力门槛基本上是头部玩家的战场。但编排层不同它需要的是工程能力怎么写好工具描述、怎么管理对话历史、怎么处理工具返回的结构化数据、怎么降本增效。这些恰恰是普通开发者的优势区间。你不需要训练一个大模型你只需要把现有模型可靠地接入业务系统。可以说AI 真正被低估的部分不在“智能”本身而在“工程化交付”这件事上。3. 环境准备与 API 选型3.1 本地环境说明本文的示例代码以 Python 为例。建议使用 Python 3.10 或更高版本但 3.9 也不会有太大问题具体看依赖包的兼容情况。需要准备的工具有Python 3.10pip 包管理工具一个可用的代码编辑器或 IDE一个可以访问的 OpenAI 兼容 API 服务如果你使用的是国内各家大模型厂商的 API也不用担心。绝大多数厂商都提供了 OpenAI 兼容接口只需修改base_url和model两个参数即可。下面代码示例中会采用这种兼容性较好的接入方式。3.2 大模型 API 与开源模型怎么选开发 AI Agent 应用前第一件事是选型。调用现成大模型 API 的成本更低部署简单适合快速验证业务。缺点是数据会经过第三方服务且调用成本会随着业务量增长。另一个问题是如果你所在的环境无法访问海外服务就需要使用国内可访问的模型 API 或自建网关。开源模型本地部署则更适合数据敏感、需要私有化交付的场景。它的优点是数据不出内网可以针对业务微调缺点是需要 GPU 资源和模型运维能力。对刚起步的团队来说建议先用 API 快速验证业务闭环确认 ROI 之后再决定是否投入资源做私有化部署。3.3 credits 与计费基础概念调用模型 API 时你常会看到 credits额度这个概念。它的本质是预付费套餐额度。每次请求会根据输入和输出的 token 数量扣除额度不同模型的扣费比例不同。上下文越长、模型越大单次调用消耗的 credits 就越多。这就引出了一个重要思路AI 应用的花费是可优化的。常见做法包括使用小模型处理简单任务、限制输出长度、缓存重复问题、对历史消息做截断。后面最佳实践章节我再展开讲。3.4 项目结构规划为了不把代码堆在一个文件里我们先把项目结构设计好。ai-agent-demo/ ├── .env ├── requirements.txt └── app ├── __init__.py ├── config.py ├── tools.py ├── agent.py └── main.pyconfig.py负责读取环境变量tools.py放工具实现agent.py是 Agent 核心逻辑main.py提供 HTTP 服务入口。这样的结构方便后续扩展工具和路由。4. 核心原理什么是工具调用Function Calling4.1 上下文、系统提示词与工具调用在开始写代码前有必要把几个核心概念讲清楚。上下文Context是模型可以读取的消息序列。它通常由 system 消息、user 消息、assistant 消息和 tool 消息组成。模型每次生成回复时都会基于整个上下文来做推断。系统提示词System Prompt用于设定模型的行为和边界。你可以告诉它“你是一个助手可以使用工具获取天气信息”也可以告诉它“不要回答与业务无关的问题”。工具调用Function Calling / Tool Calling则是模型的一项关键能力。它允许模型在需要时输出一个结构化的“调用请求”而不是直接输出最终答案。这个请求包含工具名称和参数由我们的代码去真正执行再把执行结果返回给模型让模型组织最终回复。一定要区分清楚模型不会自己执行任何外部操作它只会“请求调用”。真正执行工具的是你的代码。4.2 一次工具调用的完整流程为了方便理解我把一次带工具调用的 Agent 流程拆成六步。用户发送消息。Agent 把消息追加到上下文并携带工具列表请求模型。模型判断需要调用工具时返回工具调用指令例如“调用 get_weather参数 city北京”。Agent 在代码中执行对应工具得到结构化或文本结果。Agent 把工具结果作为 tool 消息追加到上下文再次请求模型。模型根据工具结果生成最终自然语言回复返回给用户。这整个过程中模型负责判断和表达代码负责执行和传递。理解了这个闭环你就理解了 AI Agent 的最基本骨架。4.3 工具描述的 JSON Schema模型是怎么知道有哪些工具、各自接受什么参数的靠的是工具描述通常使用 JSON Schema 格式。下面是一个示例描述了一个“查询天气”的工具。{ type: function, function: { name: get_weather, description: 查询指定城市的实时天气, parameters: { type: object, properties: { city: { type: string, description: 城市名称 } }, required: [city] } } }这里的description非常关键。模型靠它来判断什么时候该调用这个工具以及需要填什么参数。工具描述写得越清晰模型的调用准确率越高。4.4 容易踩的坑结合我看到的很多新手问题工具调用最容易踩的坑有这几个。一个是把工具调用和模型回答混为一谈。模型返回tool_calls时content往往是空的正确做法是先把 assistant 消息写入历史再执行工具而不是直接把它当成最终答案返回用户。另一个是忽略工具结果的校验。模型生成的参数不一定是合法 JSON也可能是空字符串、错误类型。代码里要做好异常捕获和类型校验。还有一个是上下文越堆越长。多轮工具调用会让消息数量快速增长最终超出模型上下文窗口。后面必须在生产环境中做消息截断和压缩。5. 实战搭建带工具调用的 AI Agent 服务5.1 创建项目与依赖首先创建项目目录并准备好依赖文件。mkdir ai-agent-demo cd ai-agent-demo mkdir app touch app/__init__.pyrequirements.txt内容如下。这里以 OpenAI 官方 Python SDK 为基础它兼容大多数支持 OpenAI 协议的大模型 API。openai1.30.0 python-dotenv1.0.0 fastapi0.111.0 uvicorn[standard]0.30.0 pydantic2.5.0安装依赖pip install -r requirements.txt5.2 编写环境配置在项目根目录创建.env文件。这里需要填入你实际可用的 API Key、接口地址和模型名称。# 文件路径ai-agent-demo/.env AI_API_KEYsk-xxxx AI_BASE_URLhttps://api.example.com/v1 AI_MODELgpt-4o-mini注意AI_BASE_URL和AI_MODEL需要根据你实际使用的服务商修改。比如你使用的国内模型服务支持 OpenAI 兼容接口把AI_BASE_URL改成对应地址AI_MODEL改成你的模型名即可。接着编写配置读取模块app/config.py。# 文件路径ai-agent-demo/app/config.py import os from dotenv import load_dotenv load_dotenv() class Settings: API_KEY os.getenv(AI_API_KEY, ) BASE_URL os.getenv(AI_BASE_URL, https://api.example.com/v1) MODEL os.getenv(AI_MODEL, gpt-4o-mini) settings Settings()5.3 编写工具实现tools.py里放两个工具一个是计算器一个是天气查询。天气查询这里用 mock 数据代替真实 HTTP 请求方便你本地跑通。真实项目中你可以替换为气象服务 API、数据库查询或内部系统接口。# 文件路径ai-agent-demo/app/tools.py def calculate(expression: str) - str: 计算数学表达式例如 1 2 * 3。 注意这里对表达式字符做了白名单校验并清空了内置函数 避免任意代码执行风险。 allowed set(0123456789-*/(). ) if not set(expression).issubset(allowed): return 表达式包含非法字符。 try: result eval(expression, {__builtins__: {}}, {}) return str(result) except Exception as e: return f计算失败{e} def get_weather(city: str) - str: 查询城市天气。演示环境使用本地模拟数据 真实项目请替换为气象服务 HTTP 请求。 weather_map { 北京: 晴20℃, 上海: 小雨22℃, 广州: 多云26℃, } return weather_map.get(city, f暂未收录 {city} 的天气数据)这里要特别说明一下calculate的安全性。eval在 Python 中是危险函数如果直接对外部输入求值可能导致代码注入。所以在示例里做了两件事只允许数学表达式常用字符并传入空的__builtins__。即便如此生产环境也不建议直接用这种方式更推荐使用ast.literal_eval或专门的计算库。5.4 编写 Agent 核心逻辑接下来是重点。agent.py实现了 Agent 类核心逻辑包含三个部分构造工具描述、处理第一轮模型响应、执行工具并生成最终答案。# 文件路径ai-agent-demo/app/agent.py import json from openai import OpenAI from tools import calculate, get_weather def build_tool_schemas(): 构造工具列表。每个工具都包含名称、描述和参数 Schema。 描述越清晰模型越容易正确调用。 return [ { type: function, function: { name: calculate, description: 计算数学表达式例如 1 2 * 3, parameters: { type: object, properties: { expression: { type: string, description: 数学表达式 } }, required: [expression], }, }, }, { type: function, function: { name: get_weather, description: 查询指定城市的实时天气, parameters: { type: object, properties: { city: { type: string, description: 城市名称 } }, required: [city], }, }, }, ] class Agent: def __init__(self, api_key: str, base_url: str, model: str): self.client OpenAI(api_keyapi_key, base_urlbase_url) self.model model self.tools build_tool_schemas() self.messages [ { role: system, content: 你是一个乐于助人的 AI 助手。 当用户需要计算或天气信息时请调用对应工具。, } ] def run(self, user_message: str) - str: # 把用户消息追加到上下文 self.messages.append({role: user, content: user_message}) # 第一轮请求模型可能返回文本也可能返回工具调用请求 response self.client.chat.completions.create( modelself.model, messagesself.messages, toolsself.tools, ) message response.choices[0].message # 如果没有工具调用直接返回模型文本 if not message.tool_calls: self.messages.append(message) return message.content # 模型决定调用工具先把 assistant 消息写入历史 self.messages.append(message) # 逐个执行工具 for tool_call in message.tool_calls: fn_name tool_call.function.name fn_args json.loads(tool_call.function.arguments) if fn_name calculate: result calculate(**fn_args) elif fn_name get_weather: result get_weather(**fn_args) else: result f未知工具: {fn_name} # 工具结果以 roletool 的消息返回给模型 self.messages.append( { role: tool, tool_call_id: tool_call.id, content: result, } ) # 第二轮请求模型结合工具结果生成最终回答 second_response self.client.chat.completions.create( modelself.model, messagesself.messages, toolsself.tools, ) final_message second_response.choices[0].message self.messages.append(final_message) return final_message.content上面这段代码比较长但逻辑很清晰。核心就一句话模型说“我要调用工具”代码就去执行工具然后把结果还给模型再让模型总结。需要注意的是不同版本的 OpenAI SDK 对message.tool_calls的字段结构可能略有差异。如果你在使用时发现属性名对不上可以先打印message对象确认字段结构再按实际结构调整。5.5 编写 FastAPI 服务入口main.py提供 HTTP 接口方便我们用浏览器或 curl 调用。这里使用 FastAPI 和 Pydantic 做请求参数校验。# 文件路径ai-agent-demo/app/main.py from fastapi import FastAPI from pydantic import BaseModel from agent import Agent from config import settings app FastAPI(titleAI Agent Demo) agent Agent( api_keysettings.API_KEY, base_urlsettings.BASE_URL, modelsettings.MODEL, ) class ChatRequest(BaseModel): message: str app.post(/chat) def chat(req: ChatRequest): answer agent.run(req.message) return {answer: answer}这样我们就有了一个最简单的 AI Agent 服务用户发送消息服务返回模型最终回答。5.6 启动与调用启动开发服务器uvicorn app.main:app --reload --port 8000看到类似Uvicorn running on http://127.0.0.1:8000的日志说明服务启动成功。然后用 curl 测试curl -X POST http://127.0.0.1:8000/chat \ -H Content-Type: application/json \ -d {message:帮我查一下北京今天天气怎么样}预期返回是模型生成的自然语言回答例如{answer:北京今天晴20℃适合出行。}再测试一个计算场景curl -X POST http://127.0.0.1:8000/chat \ -H Content-Type: application/json \ -d {message:计算 (12 8) * 3 的结果}如果模型正确调用了calculate工具返回结果里会包含计算结果。5.7 观察运行流程为了看到模型到底是怎么调用工具的可以在agent.py里临时加一段打印逻辑。if message.tool_calls: print(模型请求调用工具:) for tool_call in message.tool_calls: print(f 工具名: {tool_call.function.name}) print(f 参数: {tool_call.function.arguments})再次运行请求时你会在服务端日志里看到类似这样的输出模型请求调用工具: 工具名: get_weather 参数: {city:北京}这说明整个链路是通的模型判断需要查询天气并输出了结构化参数代码执行了工具拿到了 mock 天气数据最后模型基于这个结果生成了回答。6. 常见问题与排查思路6.1 高频报错排查表问题现象常见原因解决思路401 认证失败API Key 错误或环境变量未加载检查.env内容和config.py读取逻辑404 模型不存在model 名称与渠道不匹配确认服务商支持的模型名称按官方文档修改请求超时网络不通、防火墙限制或 DNS 异常检查网络连通性在客户端设置 timeouttool_calls解析失败模型返回参数不是合法 JSON用 try-except 包裹 JSON 解析做参数校验上下文超长多轮对话 token 累积超限对历史消息做截断或摘要压缩credits 消耗过快消息重复调用、无缓存缓存工具结果限制输出长度选择性价比更高的模型6.2 工具调用不稳定怎么办如果你发现模型时而调用工具、时而不调用大概率是工具描述不够清晰或者系统提示词没有给出足够的引导。可以尝试这么优化在工具描述里增加典型例子例如“当用户问价格时调用 calculate 计算总价”。在系统提示词中明确工具使用边界比如“当用户要求查询天气时必须调用 get_weather”。如果某个场景总是判断错误可以把该场景从工具调用中拆出来直接用规则匹配处理。工具调用不是百分百可靠的工程上要做容错设计而不能假设模型每次都完美工作。7. 最佳实践与工程建议7.1 成本控制AI 应用的成本大头在模型 API 调用。这里有几个实打实的优化点。第一对工具结果做缓存。例如天气查询结果短时间内基本不变可以把城市和结果缓存几分钟避免同一问题反复请求模型。第二裁剪历史消息。不是所有历史消息都有用超过一定轮数后可以用模型做摘要保留摘要代替完整历史。第三分层使用模型。简单任务用小参数模型复杂推理用大模型。很多请求其实不需要最贵的模型。第四设置max_tokens限制输出长度防止模型生成过长的回答导致费用暴涨。7.2 安全边界围绕 Agent 的安全有几点必须强调。不要把 API Key 写在代码仓库里。应该通过环境变量或配置中心管理密钥并设置密钥的访问权限和最小权限范围。对 Agent 可执行的工具做白名单。不允许 Agent 调用未注册的工具尤其涉及文件删除、数据库写入、发布操作这类高风险能力时必须走人工审批。工具参数的校验要放在执行前。模型生成的参数不一定可信要有类型和取值范围校验。对于涉及 SQL、Shell 命令的场景强烈建议做成只读操作或使用沙箱环境。警惕提示词注入。当 Agent 读取到外部文本如网页内容、邮件内容时这些文本里可能包含恶意指令。要注意将外部内容与系统指令隔离不要让外部内容覆盖系统提示词。7.3 可观测性与日志Agent 应用比传统接口复杂得多因为它涉及多轮模型调用和工具执行。生产环境必须把每一轮对话链路记录下来。建议至少记录以下几类信息每次请求的用户输入和模型最终输出。模型是否发起了工具调用调用的是哪一个工具参数是什么。工具执行的耗时和结果。每轮调用消耗的 token 数量。有了这些日志你才能在线上问题发生时还原完整链路而不是靠猜。7.4 生产部署建议这个 demo 使用 FastAPI 是为了演示方便生产环境还需要考虑几个问题。长耗时任务不要使用同步请求。如果 Agent 要连续调用多个工具单次响应可能超过几秒甚至几十秒这时候应该使用异步任务队列返回任务 ID由前端轮询获取结果。要对接口做限流。Agent 的调用成本远高于普通接口如果不加限制一个异常调用循环就可能耗尽 credits。幂等性设计也要考虑。如果服务端重试一次请求Agent 可能会重复执行工具可能造成重复扣费或重复操作。可以在请求层引入去重机制。8. 接下来可以继续深入的方向到这里你已经实现了一个最简单但完整的 AI Agent 服务用户提问、模型判断、工具执行、结果汇总。这套骨架就是很多 AI 应用的最小闭环。如果你想继续深入有这几个方向比较值得研究。第一个是 RAG 检索增强生成。给 Agent 外挂一个知识库让它可以基于私有文档回答问题。这样 Agent 就不只是会调用工具还能理解企业内部的业务资料。第二个是多 Agent 协作。把一个大任务拆成多个子任务由不同的 Agent 分头执行再由主 Agent 汇总结果。这种模式适合更复杂的业务流程。第三个是模型评估和效果调优。Agent 的效果不是靠感觉而是靠测试集和评估指标。你可以把常见问题整理成测试集每次改动工具描述或提示词后跑一遍回归。第四个是工程化基础设施。包括日志追踪、成本监控、消息队列、模型网关等。这些内容不性感但决定了 AI 应用能不能真正稳定跑在生产环境。AI 市场是不是被低估了最终可能不取决于模型评测榜单而取决于我们能把多少真实工作交给 Agent。与其等着别人告诉你答案不如先把这个 demo 跑起来看看它在你的业务里能替你做多少事。