☰
AI Agent实战:从零构建大模型应用的信息助理完整指南
2026/10/19 7:34:55 网站建设 项目流程

这两年要是没听过AI Agent,都不好意思说自己在做技术。但真正上手的人都知道,概念文章满屏都是,框架装完又卸载,能把自己的Agent从零搭起来、跑通、还能稳定用上一段时间的,其实没那么多。这篇文章就围绕“AI Agent构建方法与大模型应用”这条主线,把我从零做一个信息助理Agent的全过程、思路和踩坑记录整理出来,尽量做到拿过去就能复现。

内容不打算做成罗列概念的百科,而是按我的实操顺序走:先想清楚Agent到底是什么、该解决什么问题,再做技术选型和组件规划,然后写代码跑通最小闭环,最后聊真实场景里的问题排查和成本控制。适合刚接触Agent、看了很多概念但没完整写过一遍的开发者,也适合需要给团队做技术预研的技术负责人。看完你能快速判断,一个Agent项目从想法到落地,到底要过几道坎。

1. 想清楚再动手:AI Agent到底是什么,和普通应用差在哪

1.1 从“会聊天”到“会办事”,Agent的核心能力到底强在哪

普通的大模型应用,用户问一句,模型回一句。中间没有目标拆解,没有外部动作,也没有结果验证。Agent不一样,它会把一句含糊的话拆成“我要做什么、需要哪些信息、用哪个工具、怎么确认做完了”,然后像人一样一步步执行,最后把结果回给你。

有个很形象的类比:普通问答应用像一个电话客服,你问一句它答一句,答不上来就只会在话术里绕;Agent像是一个手里拿着工具箱的办事员,你告诉它“帮我把会议纪要整理好,顺手排个待办清单”,它可以自己决定先看纪要、再提取事项、最后生成清单文件,中途如果某个工具失败,它还会换个办法重试。

所以Agent与普通应用最核心的差距,归结起来是三个能力:

  • 目标感:能理解一个多步骤任务,而不是只回答单轮问题。
  • 工具调用:能按照约定格式请求调用外部函数,比如查数据库、发消息、写文件。
  • 反馈闭环:拿到工具返回结果后再决定下一步,而不是一次性输出就完事。

这个“思考—行动—观察”的循环,就是Agent区别于聊天机器人的分水岭。

1.2 大模型、提示词、工具调用:一张图拆清Agent体系

很多人一上来就纠结“哪个框架最强”,实际上整个Agent体系拆开看,并不复杂:

  • 大脑:大模型,负责理解任务、拆解步骤、决定下一步动作。
  • 工作流:编排循环,把思考、行动、观察这个循环反复跑起来,直到得出最终结果。
  • 手:工具,就是预定义的函数,比如查天气、执行SQL、写文件、发邮件。
  • 记忆:上下文,包括短期对话历史和长期偏好、历史结论。
  • 约束:护栏,包括最大步数、工具白名单、输出格式校验、权限确认。

你可以想象一条流水线:用户说了一句话,大脑拿到这句话后开始推理,如果发现需要实时信息或外部操作,就生成一个结构化请求,交给手去执行。手执行完把结果塞回对话,大脑看到结果继续推理,直到认为自己已经把任务解决,才生成面向用户的最终回复。

下面是一次典型任务的全过程:

  1. 用户说:“帮我查一下本周三下午有没有空会议室,有的话预定一间。”
  2. 大脑判断需要查会议系统,于是调用工具query_meeting_rooms,参数是“本周三下午”。
  3. 工具返回结果:有三间空闲会议室。
  4. 大脑看到结果,继续规划:需要预定其中一间,于是调用工具book_meeting_room,参数是会议室编号。
  5. 工具返回预定成功。
  6. 大脑生成最终回复:“已帮你预定本周三下午某会议室。”

这六步里,模型被执行了多次,工具被调用了两次,每一步都依赖上一步的返回结果。这就是Agent的典型工作方式。

1.3 Agent不是万能药:哪些需求适合用Agent解决

了解Agent能做什么之后,更要知道什么场景不该硬上。我从实际项目里总结出几条判断标准。

适合用Agent解决的问题,一般具有这些特征:

  • 多步推理:比如“分析近30天的销售数据,找出异常原因并生成报告”,需要数据查询、异常检测、文本生成多个环节。
  • 实时信息:比如查天气、查库存、查订单状态,模型自身知识库覆盖不了。
  • 跨系统操作:比如在工单系统建单、在日历里排期、在文档库检索后归档。
  • 结果需要验证:比如先生成SQL再执行,执行结果再回填报告。

不适合用Agent的场景也很明显:

  • 高频低延迟接口:比如用户点了按钮就要立刻返回,Agent多轮循环的延迟容易超时。
  • 强确定性流程:比如资金结算、订单状态流转,这类场景要求步骤固定、结果可审计,交给规则引擎更稳,而不是让模型临时发挥。
  • 单次成本敏感:每多一轮调用都是在烧token,如果任务其实一句就能答完,没必要上Agent。
  • 数据安全约束极高:外部模型调用可能把数据带出内网,除非走私有化部署,否则要慎重。

我个人的判断方法是:先问自己,这个需求如果交给一个新人,他能不能按一套书面流程做下来?如果能,而且步骤里需要查外部系统、需要根据中间结果调整动作,那就适合Agent;如果答案只是“查一下资料回答我”,那做普通应用就够了。

2. 动手前的关键设计:目标拆解、模型选型与组件准备

2.1 把需求拆成“Agent能听懂”的步骤,是成败的关键

很多人一上来就写代码,结果写了一半发现模型不听指挥。问题往往不在模型,而在需求从来没被拆到模型能理解的程度。

我习惯先做一张流程拆解表,把口头需求变成可执行的步骤。拿我实际做的信息助理为例,用户的需求是“把今天收集到的资料整理成摘要,并生成一个待办清单”。看起来很自然的一句话,落到Agent层面需要拆成这样:

步骤具体动作成功标准失败兜底
1获取今天新增的资料文件拿到文件列表或内容告诉用户“今天没有新资料”
2对资料内容做摘要输出每条资料的核心结论单条资料过长时截断再摘要
3从摘要中提取待办输出结构化待办列表无明确事项时提示用户
4生成清单并写入本地文件返回文件路径写入失败时返回错误原因

拆完这个表,你才能真正定义工具边界:Agent需要哪些工具?每个工具接收什么参数?返回什么结构?哪些操作允许自动执行,哪些需要用户确认?

这里有个关键点:不要把完整指令塞给模型让它自由发挥,而是把流程的骨架固定住,让模型在骨架里做决策。比如“先检索、再摘要、再生成待办、最后确认写文件”,这个顺序是规则层面确定的,模型负责的只是每一步里的具体内容选择。这样既利用了模型的灵活性,又保证结果可预期。

2.2 大模型选型:怎么挑一个适合做Agent的底座

选模型是很多人最容易纠结的环节。我的原则很简单:做Agent,稳定性优先于单点聪明。一个对话能力很强但在函数调用场景下经常漏参数、格式不稳定的模型,会让你排查到崩溃;反过来,一个略笨但每次输出都能严格符合要求的模型,反而是好选择。

选型时我一般关注这几个维度:

维度说明考察方法
上下文长度决定能塞多少历史信息和工具返回结果压测多轮任务不丢早期内容
工具调用支持是否原生支持函数调用、参数校验是否严格写20个不同参数类型做回归
推理稳定性面对同一任务多次执行结果是否一致同一输入跑5次看差异
单位成本输入和输出token的单价用典型任务估算并发成本
限流与延迟并发上限、首字延迟压测脚本实测

有一个选型计算技巧,很多人忽略。我通常会把一个典型Agent任务跑一遍,统计平均轮数和token消耗,然后反推成本。比如平均每轮输入5000 token、输出500 token,一个任务平均需要5轮调用,那么这个任务单次消耗大约(5000 + 500)× 5 = 27500 token。如果一天要跑100个任务,就是约275万token的日消耗,再乘以服务商的单价,立刻能算出这个Agent上线后每月烧多少钱。这个账在选型阶段就要算清楚,而不是等月底看账单。

2.3 必备组件:记忆、工具接口、编排框架的真实含义

很多文章把记忆、工具、框架讲得很玄,其实就是三样东西。

记忆分两层。短期记忆就是对话上下文,直接在消息数组里保留历史,模型天然能看到。长期记忆则是把历史偏好、历史结论存起来,下次任务开始时检索相关片段回填到上下文。实现长期记忆最简单的方式就是选一个向量库,把历史信息向量化存储,新任务来时用相似度检索。别一开始就上复杂的记忆系统,先跑通短期记忆再说。

工具接口的本质是“模型的说明书”。模型并不知道你的系统里有哪些函数,你必须在请求里把函数名字、参数、用途告诉它。工具描述写得好不好,直接决定模型调用得对不对。比如一个保存笔记的工具,description里写“保存笔记”和“当用户明确要求记录一段文字时,保存为本地文件,文件名为用户指定的名称,内容为用户的原文”,后者明显更能让模型正确决策。所以,写工具接口的本质是写文档,不是写代码。

编排框架解决的是循环调度、上下文整理、工具注册这些重复劳动。我的建议是:第一次做Agent不要上重型框架,先手写一个循环,比如下一章那个50行的主循环。等你理解Agent的工作流之后,再引入框架也不迟。框架能帮你省事,但如果你不知道它背后循环在干吗,出了错根本无从排查。

3. 从0到1实现一个可用Agent:完整实操

3.1 场景定义:先做一个能跑通全流程的“信息助理”

这一章我们做一个最小可用的“信息助理Agent”。目标定得不高,能覆盖三类任务:

  1. 纯知识问答:不需要工具,模型直接回答。
  2. 实时信息查询:查当前日期、计算数学表达式,必须调用工具。
  3. 外部操作:把用户指定的内容保存为本地笔记文件,涉及文件系统操作。

为什么选这三类?因为它们分别覆盖了Agent的三种典型动作:纯推理、工具查询、外部动作。跑通这三类,你就能理解Agent循环的核心逻辑,后续往任意业务场景扩展都只是加工具的问题。

工具清单设计如下:

工具名功能参数返回
get_current_date返回当前日期和周几无字符串
calculate计算数学表达式expression字符串
save_note保存笔记到本地文件filename, content字符串

3.2 系统提示词的写法:把约束写进上下文

系统提示词是Agent的“岗位说明书”。我见过太多人随便写一句“你是一个聪明的助手”,结果模型行为全靠运气。可靠的写法是把规则分条列清楚,尤其要写明“什么时候用工具,什么时候不用”。

下面这段是我调过几版后稳定可用的模板:

你是信息助理Agent。你会收到用户的一句话任务,请把它拆解成执行计划,再按计划完成。 规则: 1. 当任务仅需要自身知识就能回答时,直接回答,不要调用任何工具。 2. 当任务涉及实时信息或外部操作时,必须调用对应工具,不要凭空编造结果。 3. 每次工具调用之后,必须根据工具返回的内容继续推理,不得虚构执行结果。 4. 工具执行失败时,如实告诉用户失败原因,并给出下一步建议。 5. 最终回答控制在200字以内,关键信息可以用简短列表呈现。 6. 仅使用本场景提供的工具,禁止自主想象其他操作。

这六条里,最容易被忽略也最关键的是规则3。很多Agent翻车就是因为模型在工具返回一堆报错后,完全无视错误,假装操作成功了。写明“基于返回结果继续推理”,实际上是在约束模型的幻觉倾向。规则1也重要,它防止模型为了“展示能力”而拿鸡毛蒜皮的问题也去调一次工具,白白浪费时间。

3.3 核心代码骨架:思考—行动—观察循环的实现

下面给出核心代码骨架。我刻意不依赖任何重型框架,只用一个Python主循环,方便你理解每一步发生了什么。

import json import requests from datetime import datetime API_KEY = "your-api-key" BASE_URL = "https://your-llm-api.example.com/v1/chat/completions" MODEL_NAME = "your-model-name" SYSTEM_PROMPT = ( "你是信息助理Agent。你会收到用户的一句话任务,请把它拆解成执行计划,再按计划完成。\n" "规则:\n" "1. 当任务仅需要自身知识就能回答时,直接回答,不要调用任何工具。\n" "2. 当任务涉及实时信息或外部操作时,必须调用对应工具,不要凭空编造结果。\n" "3. 每次工具调用之后,必须根据工具返回的内容继续推理,不得虚构执行结果。\n" "4. 工具执行失败时,如实告诉用户失败原因,并给出下一步建议。\n" "5. 最终回答控制在200字以内,关键信息可以用简短列表呈现。\n" "6. 仅使用本场景提供的工具,禁止自主想象其他操作。" ) def llm_call(messages, tools): payload = { "model": MODEL_NAME, "messages": messages, "tools": tools, "tool_choice": "auto", } resp = requests.post( BASE_URL, headers={"Authorization": f"Bearer {API_KEY}"}, json=payload, timeout=60, ) resp.raise_for_status() return resp.json()["choices"][0]["message"]

工具定义部分包括每个函数本身的实现和它的schema描述:

def get_current_date(): return {"ok": True, "date": datetime.now().strftime("%Y-%m-%d %A")} def calculate(expression: str): try: # 注意:生产环境校验表达式内容,防止恶意代码 result = eval(expression, {"__builtins__": {}}, {}) return {"ok": True, "result": str(result)} except Exception as e: return {"ok": False, "error": str(e)} def save_note(filename: str, content: str): try: with open(filename, "w", encoding="utf-8") as f: f.write(content) return {"ok": True, "path": filename} except Exception as e: return {"ok": False, "error": str(e)} TOOLS = [ { "type": "function", "function": { "name": "get_current_date", "description": "返回今天的日期和星期。当用户询问当前日期、今天几号等实时信息时调用。", "parameters": {"type": "object", "properties": {}}, }, }, { "type": "function", "function": { "name": "calculate", "description": "计算数学表达式,比如加减乘除。当用户要求计算时调用,表达式必须为纯数学公式。", "parameters": { "type": "object", "properties": { "expression": {"type": "string", "description": "数学表达式,如(12+34)*5"}, }, "required": ["expression"], }, }, }, { "type": "function", "function": { "name": "save_note", "description": "把一段文字保存为本地笔记文件。当用户明确表示要记录、保存笔记时调用。", "parameters": { "type": "object", "properties": { "filename": {"type": "string", "description": "文件名,需包含.txt后缀"}, "content": {"type": "string", "description": "要保存的正文内容"}, }, "required": ["filename", "content"], }, }, }, ]

核心主循环就是“思考—行动—观察”的翻译:

def execute_tool(name, arguments): if name == "get_current_date": return json.dumps(get_current_date(), ensure_ascii=False) if name == "calculate": return json.dumps(calculate(arguments["expression"]), ensure_ascii=False) if name == "save_note": return json.dumps(save_note(arguments["filename"], arguments["content"]), ensure_ascii=False) return json.dumps({"ok": False, "error": f"unknown tool: {name}"}) def run_agent(user_input, max_steps=5): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_input}, ] for step in range(max_steps): message = llm_call(messages, TOOLS) messages.append(message) if message.get("tool_calls"): for tc in message["tool_calls"]: fn = tc["function"] args = json.loads(fn["arguments"]) print(f"step {step}: invoke {fn['name']}, args={args}") result = execute_tool(fn["name"], args) messages.append({ "role": "tool", "tool_call_id": tc["id"], "content": result, }) else: return message["content"] return "已达最大步数,任务提前中止,请检查是否出现循环。"

这个循环做的事很简单:每次调模型,如果模型认为需要调用工具,就执行工具并把结果塞回消息列表;如果模型认为任务完成,就直接返回文本。整个过程循环进行,直到拿到最终答案或超过最大步数。

3.4 工具注册与调用:让Agent真正拥有“手”

工具注册看起来只是填参数,但这里面的坑非常深。最关键的是工具描述的质量。

我对比过两个版本的描述:

  • 版本一:description: "保存笔记"。模型经常在用户只是提到“记录一下”时就去调用它,但用户其实只是随口说。
  • 版本二:description: "把一段文字保存为本地笔记文件。当用户明确要求记录、保存笔记时调用,否则不要调用。文件名需包含.txt后缀,内容为原文。"。调用准确率明显提升。

原因在于,模型本质上是通过description来判断“什么时候用我”。你把触发条件写得越清晰,它的判断就越准。如果你的工具在“不满足条件时坚决不调用”的说明,模型反而会更容易学会克制。

参数描述同样重要。比如calculate的参数,如果只写“expression”,模型可能传入今天的天气是什么这种非表达式内容。补上“必须为纯数学公式,如(12+34)*5”之后,错误少了很多。所以,工具注册表写的不是JSON,是给模型的说明文档,偷懒不得。

另一个容易被忽略的点是返回值。模型需要从工具返回值里提取关键信息,所以返回值要尽量结构化。我统一用{"ok": true/false, ...}的形式,让模型一眼能看懂成功还是失败。失败时返回{"ok": false, "error": "..."},模型就会进入“报告失败并提供建议”的正确分支。

3.5 成本与安全:给Agent装上护栏

Agent跑起来之后,最怕的不是它不干活,而是它“胡干活”。我习惯在代码净化阶段装上几道护栏。

最大步数限制是必须的。模型在复杂任务里偶尔会陷入自我对话的死循环,没有步数上限,一次请求能把你的token烧穿。上面代码里的max_steps=5就是在做这件事。

工具白名单也很关键。模型只能调用你注册过、并且允许它在当前场景下使用的工具。如果业务系统里有删除、转账这类敏感操作,最好单独拆分出一个子Agent来执行,并且在调用前增加二次确认。

我实践过一种简单的确认机制:当Agent准备调用敏感工具时,不让它直接调用,而是先输出一句“准备执行XX操作,请回复确认”。用户说确认后,才在下一轮真正执行工具。这个机制多花一轮对话,但能避免大量误操作。

输出校验同样别省。在最终回复返回给用户前,检查是否包含必备字段、是否为空、格式是否可解析。我在一个报告生成Agent里加过最基础的“输出长度校验”,如果模型回复超过了设定阈值,就强制让模型重写,效果很明显。Agent的输出是程序化入口和用户入口的混合,校验是最后一道关。

日志记录是所有护栏里最基础也最重要的。每一步的系统提示词、用户输入、模型输出、工具返回,全部落盘记录。出了问题,先查日志,而不是先猜模型哪里“犯傻”。

4. 常见问题与排查技巧实录

4.1 Agent答非所问:意图识别跑偏的四个原因

我调试Agent时候最常遇到的,就是模型答非所问。表面看是模型“笨”,其实绝大多数情况是设计问题。

  • 原因一:系统提示词太“作文”化。写了一大段优美描述,但没有明确的规则,模型靠猜。对策:把提示词分条,明确“必须”“不得”。
  • 原因二:工具描述过于宽泛。比如“查询信息”这种描述,模型不知道查询什么、什么时候查。对策:写清触发条件和触发时机。
  • 原因三:历史信息干扰。对话轮次多了以后,模型分不清用户当前问的是什么问题和之前的问题有关还是无关。对策:在最后一次用户输入前面加一句“当前最新问题:”,或者必要时只保留最近几轮。
  • 原因四:没有固定回归用例。今天测两个问题觉得不错,过几天调了提示词又退化了,自己还没发现。对策:维护一个至少20条的测试集,每次改动后跑一遍。

4.2 工具调用连环出错:从超时、参数错到循环死锁

工具调用出错是最消耗排查精力的环节,常见问题有三种。

参数类型错误。模型传入的参数偶尔会多一个字段或类型不对。我的处理方式是在execute_tool里用try-except捕获异常,把错误结构化返回给模型。模型看到错误信息后,很多时候自己能纠正重试。

工具超时。如果一个工具内部请求外部系统超过3秒,Agent整个循环都在空转。我在工具内部都加了显式超时设置,并且把超时信息告知模型,比如返回{"ok": false, "error": "database timeout", "suggest": "retry later or use local cache"}。这个suggest字段很有用,模型会基于它给出合理的用户提示。

循环死锁。典型表现是模型反复调用同一个工具,每次返回同样错误,却不停重试。最常见的原因是没有步数上限,或者工具错误信息不够结构化让模型不知道换路。我给工具统一返回ok字段就是解决这个问题:模型看到ok: false,就会走“失败反馈”分支而不是继续莽撞重试。

排查工具循环问题时,我建议按这个顺序来:先打印每一步的完整请求和响应;再用固定的参数直接调工具函数,排除工具自身问题;最后检查系统提示词里是否把工具的触发条件写得足够明确。

4.3 Token预算失控:如何不让Agent烧钱

Agent比普通问答应用贵,这是必然的,但贵到什么程度可控,取决于你怎么设计。

照上面的例子算一笔账:一个典型工具型任务,平均每轮输入5000 token、输出500 token,跑5轮,单任务消耗约27500 token。一天100个任务,就是275万token。如果服务和上下文更长,这个数字还会涨。所以Agent上线前,一定要做成本估算。

我用的降本手段,按效果排序:

手段说明效果
精简系统提示词去掉废话,只保留规则性内容每轮能省几百到上千token
截断历史消息只保留最近N轮或按token阈值裁剪显著降低每轮输入长度
工具结果摘要长返回先摘要成关键信息再回填上下文长期任务收益极大
结果缓存相同或相似问题直接返回缓存,不走模型中高频任务明显
大小模型结合简单步骤用便宜小模型,复杂推理用大模型整体成本下降30%以上

我做过一个数据报表Agent,起初每轮都把完整日志原文塞进上下文,一次任务轻松烧掉几万token。后来改成工具结果先做“信息压缩”:只提取关键数值和结论再回填,同样的任务token消耗降到原来的三分之一。所以,上下文里不要什么都放,模型需要的只是能决策的那部分信息。

5. 我踩过的几个坑和后续扩展方向

5.1 三个印象深刻的翻车现场

第一个翻车现场,是我在系统提示词里写了“如果有必要,可以调用工具”。结果模型几乎每个问题都先调一次工具,哪怕用户只是问“你好”。后来改成规则1,明确“仅当任务需要实时信息或外部操作时才调用工具”,情况才正常。写提示词一定要把触发条件收窄,给模型留的“自由裁量权”越少越好。

第二个翻车现场,是工具报错时直接把Python异常堆栈返回给模型。模型看到一长串Traceback,开始一本正经地分析“这个错可能是环境变量问题,建议检查服务器配置”,实际上什么都不懂。后来我把所有异常都包装成{"ok": false, "error": "human-readable message"},模型才能给出正常人能听懂的建议。

第三个翻车现场,是跑一个长会话Agent时没限制上下文长度。到第15轮,历史消息已经逼近模型窗口上限,请求超时,整个任务失败。后来我加了“历史消息按token截断,只保留最近10轮+系统提词词”的逻辑,长会话才稳定下来。这个教训提醒我:Agent的上下文管理不是优化项,是必需品。

5.2 从“能跑”到“好用”:还能加的能力

跑通最小Agent之后,有几个方向值得继续扩展,按性价比排序。

长期记忆是第一个建议加的。把用户的历史偏好存进向量库,新任务开头先检索相关记忆。比如用户多次说过“报告要包含环比”,下次生成报告时模型就会自动带上,体验提升非常明显。

评估集是第二个建议。建一份固定测试集,包含正常任务、边界任务、误导性任务,每次调完提示词或工具后跑一遍,记录通过率。没有这个评估集,你会发现每次优化都是“拆东墙补西墙”。

多Agent协作是更高级的方向,把任务拆成规划、执行、审核三个角色。规划Agent负责拆解,执行Agent负责调工具,审核Agent负责检查结果。优点是可以给不同角色配不同模型和预算,缺点是复杂度上升很快。建议先把单Agent做到稳定再考虑。

可观测性也很值得做。把每轮循环的输入输出、工具调用、耗时、token消耗汇总成结构日志,最好再画成可视化面板。我之前排查问题全靠打印,后来做了一个简单的日志面板,定位效率至少翻一倍。

写在最后

我在实际构建里最大的体会是:Agent开发不是提示词工程加接口调用的简单叠加,它更像在开发一个“总会绕你弯子”的协作系统。你要把系统提示词当代码维护,把工具定义当接口文档设计,把模型当成一个能力很强但偶尔犯错的同事来管理,该给流程给流程,该加护栏加护栏。

真正跑通一个Agent后你会发现,它最大的价值其实不是“聪明”,而是能按照你的规则把脏活累活干完,并且每一步都能被追溯、被修正。最后分享一个缩小试错成本的建议:第一版一定要小,一个场景、三个工具、五十行主循环,跑通闭环后再逐步加记忆、加评估、加多角色,这条路比一开始就上重型架构稳妥得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询