做 AI 应用开发的人,应该都有过这种体验:单次调用大模型 API 很顺,返回结果也像模像样,可一旦想把 AI 真接到业务里,问题就全冒出来了——提示词改了又改仍不稳定,Agent 跑到一半不按逻辑走,上下文一长就丢失关键信息,模型答案明明有错却查不到是哪一步引入的。很多人归因于"模型不够聪明",真实项目里真正卡住你的,往往是工程链路。
“Beetles AI”这个名字,看起来像某个产品的代号,但它更适合被理解为一类 AI 应用项目的缩影:你想让大模型自动去读文档、查数据、调用工具、最终给出结论。这类项目有一个共同点——它们不是"一句提示词"能搞定的,而是需要一套可编排、可观测、可评估的工程结构。模型能力只是底料,真正决定项目能不能上线、能不能长期稳定跑的,是围绕模型搭建的工程化能力。
这篇文章就把从零搭建一个 AI Agent 应用的完整链路拆开讲:需求怎么拆、模型怎么选、工具怎么设计、代码怎么写、服务怎么部署、问题怎么排查。重点不讲某个厂商的私有功能,而是给出一套在任何大模型 API 上都适用的工程方法。你可以把“Beetles AI”当作一个练习项目代号,跟着思路实现一个自己的 AI Agent。
1. Beetles AI 是什么:先理解 AI 应用项目的边界
在动手写代码之前,需要先界定清楚:一个 AI 应用项目到底包含什么。
从外部看,AI 应用的入口可能只是一个聊天窗口、一个 API 接口、一个自动任务。但你往里拆,会发现它的核心不是“模型”,而是“流程”。Beetles AI 这类项目的典型结构是:
- 接收用户输入,可能是问题、文档、自然语言指令;
- 对输入做拆解,判断该做什么;
- 调模型理解意图,或者直接调工具查数据、发请求;
- 把中间结果带回模型,由模型决定下一步动作;
- 循环直到任务完成,输出最终结果。
这里有个很容易被忽略的边界:传统软件是确定性的,同样输入一定得到同样输出;AI 应用是概率性的,同一个提示词,模型这次和上次的答案可能不完全一致。这个不确定性不是 bug,而是模型的天然属性。工程上要做的,不是消灭不确定性,而是用流程设计、结果校验、异常重试把它控制在一个可接受范围内。
所以,Beetles AI 这类项目的第一个判断是:它的复杂度来自“可能性管理”。你要处理的不只是正确路径,还要处理模型理解偏了、工具调用失败、上下文溢出、输出格式不合法等一堆意外。这决定了整个项目的架构取向:不要写那种“模型一次返回最终答案”的一次性脚本,要写可拆分、可重试、可追踪的流程代码。
另外一个边界是提示词不是代码。很多人习惯把业务规则全部塞进提示词,结果系统升级时提示词膨胀到几千字,效果越来越飘。正确做法是:把能确定的事情用代码确定下来,比如数据格式校验、权限判断、流程状态机;只把真正需要语义理解的部分交给模型。代码管逻辑,模型管语义,这个边界越清楚,项目越稳定。
从读者角度看,这篇文章更适合两类人。一类是刚接触 AI Agent 开发、想从“调用 API”走向“做完整应用”的开发者;另一类是在做 Python 后端或 Java 后端、考虑把 AI 能力接入现有系统的工程师。如果你只是想调一次模型接口试试效果,Web Playground 足够;如果你想做一个能长期跑、能交给别人用的 AI 应用,后面这些内容值得读完。
2. AI 应用的核心架构:模型层、编排层与应用层
为了不把概念讲成空中楼阁,先给出一个划分方式。一个 AI 应用项目可以拆成三层:模型层、编排层、应用层。
模型层是最靠近算法的部分,指大模型本身。它可以是远程 API,也可以是本地部署的开源模型。模型层负责两件事:理解自然语言,生成文本。对应用开发者来说,模型层通常是一个黑盒,你不需要知道参数怎么调的,只需要知道怎么调用、怎么传参数。
编排层是 AI 应用和传统 API 调用的关键区别。它负责决定“下一步做什么”:模型说需要查询天气,编排层就调用天气工具;工具返回结果后,再交给模型继续判断。编排层里装着提示词模板、工具注册表、上下文记忆、Agent 循环逻辑。这层做得好不好,直接决定项目是“一个聊天 Demo”还是“一个可靠应用”。
应用层是用户接触的部分,包括 HTTP 接口、前端界面、后台定时任务、数据库读写、权限控制。应用层负责把 AI 能力封装成业务能力。用户不会直接对着模型说话,而是对着你的应用说话。
三个层之间的关系可以用一个比喻理解:模型层是员工,编排层是项目经理,应用层是公司前台。员工负责干活,项目经理决定干什么活、按什么顺序干、干错了怎么补救,前台负责接待外部请求。没有项目经理,员工只能被动回答零散问题;有了项目经理,才能完成复杂任务。
Beetles AI 这类项目的架构重心,不是模型层选哪个厂商,而是编排层怎么设计。常见实现方式有三种:直接用 LangChain 这类成熟框架、用 Spring AI 这类 Java 生态框架、或者自己写一套轻量编排代码。
| 维度 | LangChain | Spring AI | 自研轻量编排 |
|---|---|---|---|
| 语言生态 | Python | Java/Spring | 任意 |
| 上手速度 | 快,组件多 | 中,集成 Spring 生态方便 | 慢,但可控性最高 |
| 调试难度 | 中等,黑盒较多 | 中等 | 低,每个环节都自己控制 |
| 适用场景 | 快速原型、AI 应用开发 | 已有 Java 后端的企业项目 | 对稳定性、可控性要求高,逻辑简单 |
| 风险 | 依赖较重,版本变动快 | 新特性迭代快,文档不全 | 需要自己处理边界情况 |
这不是让你必须选某一个。更稳妥的判断是:项目刚起步时,框架能大幅提速;但不要盲信框架,框架只是帮你把工具调用、上下文管理、模型调用串起来,业务逻辑的可靠性还是得自己负责。Beetles AI 演示时,我建议先用自研方式理解核心循环,再用框架加速生产代码。接下来教程部分,就用自研方式实现一个最小可运行的 Agent,把原理讲透。
3. 环境准备与前置条件
写代码前先准备好环境。这里以 Python 为例,因为 Python 在 AI 生态里最通用,也能方便迁移到其他语言。如果你用的是 Java 技术栈,概念完全一致,只是换 Spring AI 或原生 HTTP 调用来实现。
基础环境
- 操作系统:Windows / macOS / Linux 均可,不影响核心逻辑;
- Python 3.10 或更高版本,推荐 3.11;
- 建议使用虚拟环境隔离项目依赖;
- 一个可调用的大模型接口,远程 API 或本地模型均可。
依赖安装
创建一个项目目录,并初始化虚拟环境。
mkdir beetless-ai-demo cd beetless-ai-demo python3 -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate pip install openai fastapi uvicorn python-dotenv说明一下依赖选择:
openai:官方 SDK,实际上很多模型平台提供兼容接口,可以直接复用;fastapi和uvicorn:用于把 Agent 封装成 HTTP 服务,方便测试和部署;python-dotenv:读取.env配置文件,管理 API 密钥。
环境变量配置
在项目根目录创建.env文件:
MODEL_API_KEY=your-api-key-here MODEL_BASE_URL=https://api.example.com/v1 MODEL_NAME=gpt-4o-mini MODEL_TEMPERATURE=0.2需要注意的是:MODEL_BASE_URL和MODEL_NAME要按你实际使用的模型平台填写。不同平台兼容 OpenAI 接口的程度不一样,但大部分厂商都提供了类似格式。这里重点不是推荐某一家,而是让你理解:所有平台调用方式大同小异,差异主要在模型能力、价格和速率限制。
本地模型可以把MODEL_BASE_URL指向本机服务。例如用 Ollama 启动本地模型后,可以通过http://localhost:11434/v1访问兼容接口,模型名填本地模型名称。本地部署的好处是数据不出内网、离线可用,但对机器配置有要求,且模型能力普遍弱于商业大模型。生产环境怎么选,要结合你的数据敏感度和预算,不能一概而论。
密钥管理有几个基本原则:
- 密钥只写在
.env里,不要提交到 Git 仓库; .gitignore中加入.env;- 生产环境使用密钥管理服务或环境变量注入;
- 遵循最小权限原则,给应用单独申请专用密钥,不使用管理员级别凭证。
这些看似基础,但是 AI 项目最容易在密钥管理上翻车。一个不小心把 API 密钥推到公开仓库,后果就是被别人盗刷额度。
4. 核心流程拆解:从需求到可运行的 Agent
环境准备好之后,不要急着写 Agent 代码。先把流程拆清楚,这一步决定了后续代码质量。
4.1 需求拆解:把任务变成“输入-处理-输出”
假设 Beetles AI 要实现这样一个功能:用户输入一句话,AI 自动判断是查询天气、做计算还是普通问答,并给出正确结果。这不是一个简单功能,里面至少包含两层:
- 意图识别:判断用户想问什么;
- 工具调用:如果是天气查询,调用天气 API;如果是计算,调用计算工具。
拆解时先写一句话描述:接收用户自然语言,判断意图,调用对应工具,返回结果。然后画成流程:
- 用户输入;
- 系统把输入交给模型,提示词中说明可用工具;
- 模型决定:直接回答,或调用工具;
- 如果是工具调用,执行工具函数,把结果带回模型;
- 模型组织最终答案,返回给用户。
这个流程看起来简单,但它是所有 AI Agent 的核心模式。后面加入再多的工具、再复杂的状态管理,底层循环都是这样。
4.2 模型选择:延迟、成本与能力的三角权衡
模型选型是需求拆解后马上要面对的问题。不同模型在推理能力、延迟、成本、上下文长度上差异很大。一个典型误区是:参数越大越好,模型越强越好。实际情况是,简单任务用大模型既慢又贵,复杂任务用小模型又容易出错。
选型维度可以归纳为:
- 任务复杂度:只有简单问答,用轻量模型;涉及多步推理、工具调用,用更强模型;
- 延迟要求:用户在线交互要求低延迟,离线批量任务可以接受更慢;
- 成本预算:token 费用要进入项目评估;
- 上下文长度:任务需要一次读入多长文档,决定模型上下文窗口;
- 数据合规:敏感数据场景考虑私有化部署。
建议的做法是:先按最复杂场景选定一个能力足够的模型跑通流程,再逐步尝试更小的模型,用评测集对比效果。模型不是越贵越好,适合任务才是关键。
4.3 工具设计:函数即工具,输入输出要可序列化
工具调用是 Agent 的关键能力。对模型来说,工具不是真正的函数,而是“一段 JSON 描述”。模型看到的是工具名称、参数说明、返回值格式,它决定“该调用什么工具、传什么参数”,真正执行是由你的代码完成的。
因此,工具设计有三个原则:
- 工具描述要详细。模型靠描述决定何时调用工具,描述写得含糊,模型就会乱调;
- 参数要明确。每个参数要说明类型、含义、是否必填;
- 返回值要可序列化。模型要读工具返回值做后续判断,所以返回值必须是 JSON 或纯文本,不能返回一个 Python 对象。
用天气工具举例,它的 JSON 描述至少要包含:工具名get_weather,参数city(城市名,字符串)、date(日期,字符串),返回值是包含温度和天气状况的文本。
4.4 提示词工程:把约束写清楚,而不是把知识塞进去
提示词是 Agent 的“使用手册”。很多人以为提示词写得越长越好,结果全塞进去之后模型反而更容易跑偏。提示词的核心作用不是教模型知识,而是给模型划边界。
一份面向 Agent 的系统提示词通常包含:
- 角色定位:你是一个智能助手,可以调用工具完成任务;
- 可用工具:列出工具清单和调用规则;
- 行为约束:不知道答案时不编造,工具调用失败时怎么处理;
- 输出格式:返回内容的结构要求。
举例来说:
你是一个自动化助手。你可以调用以下工具来回答用户问题: - get_weather(city: string): 查询指定城市的实时天气 - calculator(expression: string): 计算数学表达式 规则: 1. 如果用户问的是天气,必须调用 get_weather。 2. 如果用户给出数学算式,必须调用 calculator。 3. 如果工具调用失败,请如实告诉用户,不要编造结果。 4. 回答用简洁中文,不要输出额外解释。这段提示词的特点是:每条规则都对应一个具体行为,而不是空泛地要求“你要表现好”。实际项目里提示词会更多,但结构仍然如此。好提示词的标准是:一个没见过该系统的人(模型)读完就知道什么能做、什么不能做。
4.5 评测回环:没有评测的 Agent 都是碰运气
最后一步是设计评测。AI 应用和传统应用最大的差异就是“没有固定标准答案”。你不能等上线后发现用户反馈“这个回答不对”,才去修提示词。应该在开发阶段就建立一套最小评测集。
评测集可以是 20 到 50 条真实用户问题,每条标注期望结果。每次改动提示词、换模型、调工具逻辑后,把评测集跑一遍,对比结果变化。评测标准可以是:
- 工具调用是否正确(该调天气工具时有没有调对);
- 参数是否正确(城市名有没有传对);
- 最终答案是否满足用户需求;
- 是否有错误处理(工具失败时有没有合理回复)。
不一定非要自动化评分,手工跑一遍评测集,也比没有评测直接上线好得多。等业务稳定了,再考虑用另一层模型做自动评估,或者引入人工标注。这是长期主义,也是在 AI 项目里保证质量的核心手段。
5. 完整示例代码实现
进入代码阶段。下面用 Python 实现一个最小可运行的 Agent,包含模型客户端、工具定义、Agent 循环和服务化四部分。
5.1 模型客户端
先用 OpenAI SDK 封装一个统一的模型调用函数。之所以单独封装,是为了让后续所有代码都依赖这个接口,而不是散落各处直接调 SDK。
# 文件路径:beetles_ai/llm.py import os from openai import OpenAI client = OpenAI( api_key=os.getenv("MODEL_API_KEY"), base_url=os.getenv("MODEL_BASE_URL"), ) def chat(messages: list, tools: list | None = None, temperature: float = 0.2) -> str: """调用大模型。返回模型回复内容。""" response = client.chat.completions.create( model=os.getenv("MODEL_NAME"), messages=messages, tools=tools, temperature=temperature, ) return response.choices[0].message这段代码做了几件事:从环境变量读取模型配置,构造 OpenAI 客户端,定义chat函数接收消息列表和工具列表,返回模型回复。关键点是tools参数:当模型需要调用工具时,会在返回内容中带出工具调用信息,后面 Agent 循环会用到。
5.2 工具函数定义
定义两个工具:天气查询和计算器。天气查询这里返回模拟数据,重点演示流程。真实项目应该替换为真实 API 调用。
# 文件路径:beetles_ai/tools.py import json def get_weather(city: str, date: str = "今天") -> str: """查询城市天气。""" # 实际项目这里应该调用真实天气 API weather_data = { "北京": ("晴", 12), "上海": ("小雨", 18), "广州": ("多云", 24), } desc, temp = weather_data.get(city, ("未知", 0)) return json.dumps({"city": city, "date": date, "weather": desc, "temperature": temp}, ensure_ascii=False) def calculator(expression: str) -> str: """计算数学表达式。""" try: result = eval(expression, {"__builtins__": {}}, {}) return str(result) except Exception as e: return f"计算错误: {e}" TOOLS = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的天气情况", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名,如北京"}, "date": {"type": "string", "description": "日期,默认今天"}, }, "required": ["city"], }, }, }, { "type": "function", "function": { "name": "calculator", "description": "计算数学表达式,如 1+2", "parameters": { "type": "object", "properties": { "expression": {"type": "string", "description": "数学表达式"}, }, "required": ["expression"], }, }, }, ] TOOL_FUNCTIONS = { "get_weather": get_weather, "calculator": calculator, }这里有一个需要特别注意的安全问题:calculator里用了eval,虽然限制了__builtins__,但在生产环境仍然非常危险。这个工具仅用于演示 Agent 的工具调用机制,实际项目中如果要做计算,应使用ast.literal_eval或专门的表达式解析库,绝不能直接对用户输入执行eval。这个原则同样适用于所有工具函数:任何外部输入都不能被当作代码执行。
5.3 Agent 循环:模型决定,代码执行
这是整个项目最核心的部分。Agent 循环的逻辑是:把用户输入和系统提示词发给模型,模型返回“要么是最终答案,要么是工具调用请求”;如果是工具调用请求,代码执行对应工具,把结果追加回消息列表,再重新发给模型。循环直到模型返回最终答案,或者达到最大步数。
# 文件路径:beetles_ai/agent.py import os from dotenv import load_dotenv from llm import chat from tools import TOOLS, TOOL_FUNCTIONS load_dotenv() SYSTEM_PROMPT = """你是一个自动化助手。你可以调用以下工具来回答用户问题: - get_weather(city: string): 查询指定城市的实时天气 - calculator(expression: string): 计算数学表达式 规则: 1. 如果用户问的是天气,必须调用 get_weather。 2. 如果用户给出数学算式,必须调用 calculator。 3. 如果工具调用失败,请如实告诉用户,不要编造结果。 4. 回答用简洁中文。 """ MAX_ITERATIONS = 5 def run_agent(user_input: str) -> str: messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_input}, ] for step in range(MAX_ITERATIONS): response = chat(messages, tools=TOOLS) if not response.tool_calls: return response.content messages.append(response) for tool_call in response.tool_calls: function_name = tool_call.function.name function_args = tool_call.function.arguments print(f"[Step {step + 1}] 调用工具: {function_name}, 参数: {function_args}") if function_name in TOOL_FUNCTIONS: # 这里假设参数是 JSON 字符串,实际项目需要加异常处理 import json args = json.loads(function_args) result = TOOL_FUNCTIONS[function_name](**args) else: result = f"未知工具: {function_name}" messages.append( { "role": "tool", "tool_call_id": tool_call.id, "content": str(result), } ) return "已达到最大执行步数,请优化提示词或工具设计。"这段代码的关键逻辑是:response.tool_calls是否存在。如果为空,说明模型认为不需要调用工具,直接返回response.content;如果有工具调用,则遍历每个调用,执行对应函数,并把结果通过role: "tool"的消息回传。特别要注意tool_call_id必须一致,否则模型无法把工具结果和之前的调用请求对应起来。
这里有一个很多人第一次写 Agent 会踩的坑:忘记把模型返回的response加到messages里。如果不加,模型会丢失自己刚刚做出的“要调用工具”的决定,下一次调用就可能重复生成相同的工具调用,或者干脆不调工具。把response追加回消息列表是 Agent 循环的必备一步。
5.4 HTTP 服务化
跑通 Agent 循环后,把它封装成 HTTP 接口。这里用 FastAPI 实现一个/chat接口。
# 文件路径:beetles_ai/main.py from fastapi import FastAPI from pydantic import BaseModel from agent import run_agent app = FastAPI(title="Beetles AI Agent") class ChatRequest(BaseModel): message: str @app.post("/chat") def chat_endpoint(request: ChatRequest): result = run_agent(request.message) return {"reply": result} @app.get("/health") def health(): return {"status": "ok"}这个接口接收一个 JSON 请求体,字段为message,返回reply。/health接口用于健康检查,部署到生产环境后可以直接被负载均衡器或监控系统使用。
6. 运行结果与效果验证
代码写完,接下来验证效果。
6.1 本地运行 Agent
在项目根目录执行:
cd beetles_ai python agent.py为了单独测试 Agent,可以把agent.py改成支持命令行直接输入:
# 文件路径:beetles_ai/agent.py 底部追加 if __name__ == "__main__": while True: text = input("请输入问题: ") if text.lower() in ("exit", "quit"): break print("回答:", run_agent(text))然后运行:
python agent.py6.2 预期输出
输入“北京的天气怎么样”,预期输出类似:
[Step 1] 调用工具: get_weather, 参数: {"city": "北京", "date": "今天"} 回答: 今天北京晴,温度 12 度。再输入“12*7+5 等于多少”:
[Step 1] 调用工具: calculator, 参数: {"expression": "12*7+5"} 回答: 结果是 89。输入一个普通问题“你好”,模型应该不会调用工具,直接回复问候语。
6.3 如何判断成功
判断标准不是“回答对不对”这么模糊,而是看这几条:
- Agent 正确识别意图,调用了对应工具;
- 工具参数正确,没有把“北京”误传成其他值;
- 最终回复引用了工具返回值,而不是模型自己编造的结果;
- 整个流程在几步内结束,没有死循环。
如果失败,第一步不是改代码,而是看日志里的工具调用记录。日志里会显示每一步调用了哪个工具、传了什么参数。这能快速定位问题是出在意图识别、参数解析还是工具执行。
6.4 启动 HTTP 服务验证
单独跑通 Agent 后,再测试服务化接口:
uvicorn main:app --reload --port 8000然后新开一个终端,用 curl 测试:
curl -X POST http://localhost:8000/chat \ -H "Content-Type: application/json" \ -d '{"message": "上海天气如何"}'预期返回:
{"reply": "今天上海小雨,温度 18 度。"}如果 curl 返回了结果,说明整条链路已经打通:HTTP 入口 → Agent 编排 → 模型调用 → 工具执行 → 结果返回。
7. 常见问题与排查思路
AI 应用项目的问题排查方式与传统应用不太一样,因为很多错误不是“代码崩溃”,而是“模型行为不符合预期”。下面列几个实际项目里最高频的问题。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 模型不调用工具,直接编造答案 | 提示词没有明确说明“必须调用工具”;工具描述不清楚 | 查看模型原始回复,确认是否输出了工具调用信息 | 优化系统提示词,明确触发条件;把工具描述写得更具体 |
| 工具调用参数错误 | 工具的参数描述不规范,模型没有理解参数含义 | 查看日志中function_args的实际值 | 在工具 JSON 描述中增加参数类型、格式、示例 |
| Agent 陷入死循环 | 工具返回结果无法被模型理解,或提示词没有退出条件 | 观察日志中工具调用次数是否反复相同 | 增加最大循环次数;改进工具返回格式;优化提示词 |
| 上下文超限 | 对话历史过长或工具返回结果过大 | 查看模型报错信息 | 截断历史消息;压缩工具返回内容;切换更长上下文的模型 |
| 答案不稳定,同样输入不同输出 | 温度参数过高;提示词约束弱 | 对比多次输出的差异 | 降低 temperature;增加 Few-shot 示例 |
| 中文输出乱码或格式错乱 | 编码问题或模型输出包含 Markdown | 检查终端编码和原始返回 | 统一 UTF-8 编码;提示词约束输出格式 |
| API 调用超时 | 模型推理时间过长;网络不稳定 | 查看 SDK 报错信息和耗时 | 增加超时和重试;切换服务商或模型 |
这里想重点展开两个问题。
第一个是“模型不调用工具,直接编造答案”。这是新手最容易遇见的。根本原因往往是工具描述不够明确。比如你只写“查询天气”,模型可能不知道什么时候该调用它。解决办法是在描述中写清楚触发场景,并使用祈使句,比如“当用户询问某个城市的天气时,必须调用此工具”。另外,Few-shot 示例也很有效:在提示词里给出一两个“用户问题 → 工具调用”的示例。
第二个是“上下文超限”。Agent 循环里每轮都会把新的工具结果和模型回复追加进消息列表,几十轮之后上下文就可能被塞满。处理思路有几个:只保留最近 N 轮消息;把长文档做检索后再拼接;工具返回结果做摘要后再回传。不要试图把整个文档一次塞给模型。
排查 AI 应用问题时,要建立“日志优先”的习惯。像上面的 Agent 代码里已经加入了print输出工具调用信息,生产环境应该把这些信息写入结构化日志,记录每轮的调用链路、参数、耗时和 token 消耗。这样出了问题才能回溯。
8. 最佳实践与工程建议
文章最后一部分,讲一讲把 Beetles AI 这类项目从“能跑”推向“能上线”的一些工程建议。
8.1 提示词也做版本管理
代码有 Git 管理,提示词绝不能只躺在代码字符串里。建议把每个场景的系统提示词抽成独立文件,比如prompts/weather_agent.md,格式上支持变量替换。改动提示词时走和改代码一样的评审、提交、发布流程,方便回溯“这次效果变化是因为哪次提示词改动引起的”。实际经验里,AI 项目的效果滑坡,大多是提示词被某次改动悄悄影响的。
8.2 日志与链路追踪
Agent 是多步骤执行,你必须能看到“用户输入到底经历了哪些环节”。推荐把每次请求分配一个request_id,然后把模型响应、工具调用、token 消耗、耗时全部结构化输出。生产环境接入 Jaeger、SkyWalking 或云厂商的追踪服务,用链路 ID 串起一次完整请求。这样定位问题会比看散乱 print 高效得多。
8.3 成本控制
大模型 API 是持续消耗的。上线前要估算成本:平均一次请求消耗多少 token、日请求量是多少、成本上限是多少。在代码层做 token 计量,超过阈值熔断。另外要监控是否有异常调用——Agent 死循环不仅影响体验,还会烧钱。最大步数限制和 token 上限是每个项目都该有的安全护栏。
8.4 安全边界
AI 应用的安全问题比传统应用更隐蔽。
- 提示词注入:用户输入可能包含“忽略你的系统提示词”这类攻击,工具函数里绝不能直接执行用户提供的代码或命令;
- 权限控制:Agent 能调用的工具边界要和用户权限一致,不能让普通用户通过 Agent 触发管理员权限的功能;
- 敏感信息:不要把 API 密钥、用户隐私数据拼进提示词再发给第三方模型,必要数据先脱敏;
- 输出校验:模型返回结果不能直接信,关键业务路径要做规则校验。
任何涉及外部命令执行、数据库写操作、文件删除的工具,都要在代码层过滤输入并保留审计日志。生产环境遵循最小权限原则,宁可功能受限,不要暴露高风险操作。
8.5 建立评测集
这可能是最重要的一条建议。AI 应用项目最怕的不是写完没有用,而是上线后不知道什么时候变差了。每周跑一遍评测集,把通过率记录下来。效果下降时,对比最近的提示词改动、模型切换、工具变更,定位影响源。这个流程坚持下来,比任何“玄学调优”都有效。
8.6 灰度发布与回滚
模型能力在变,服务商接口也可能调整。发布新提示词或切换新模型时,不要全量切换。先让 10% 流量走新配置,对比用户反馈和效果指标,再逐步扩大。准备好一键回滚到旧配置的手段。不要把“模型效果”当成一次性的东西,它和业务代码一样需要持续维护。
9. 总结与后续学习方向
到这里,Beetles AI 从一个抽象的名词,变成了一条可以落地的工程路径:先拆需求,再选架构,然后设计工具、写提示词、实现 Agent 循环,最后部署、验证、监控、迭代。整套流程中,最关键的不是某个框架或某个模型,而是你对“模型 + 流程 + 工具 + 评测”的整体掌控能力。
基于上面的实践,有几件事适合作为下一步深入方向。
一是把框架用起来。理解自研循环之后,再去读 LangChain 或 Spring AI 的源码,你会更容易看懂它内部做了什么封装,也更能判断哪些抽象对你的项目有价值、哪些只是多余复杂度。
二是做 RAG(检索增强生成)。当 Agent 需要回答业务知识库问题时,可以引入向量检索,把和问题相关的文档片段取出来,再交给模型生成答案。这里会涉及 Embedding、向量数据库、切分策略等技术点,是 AI 应用项目里非常实用的扩展方向。
三是补全评测体系。手工评测集够用之后,可以尝试用模型自动评分,构建回归测试集,让每次效果变化都能量化。从“凭感觉调整”变成“看数据决策”,这是 AI 应用工程成熟的标志。
四是关注可观测性。AI 应用的延迟、token 成本、工具成功率、模型回答质量,都需要系统化监控。把这些指标接到现有的监控体系里,项目才能算真正进入生产状态。
如果你正在做自己的 AI 应用,建议从今天这个最小 Agent 开始,给它加上日志、评测集和失败处理。先让一个简单功能稳定可靠地跑起来,再逐步扩展工具和场景。这样积累下来的工程经验,比频繁更换模型和框架更能支撑一个长期可维护的 AI 项目。