1. 从“会用AI”到“懂AI”,程序员差的是什么
过去一年多,我身边几乎每个程序员都在用AI写代码、查报错、做代码审查。但聊深一层,大部分人其实停留在“提问—复制—粘贴—报错—再提问”这个循环里。遇到复杂需求,比如让AI自主调用多个工具完成一个完整任务,或者让大模型输出稳定、格式可控的结果,很多人就开始抓瞎,不知道该怎么描述、怎么约束、怎么排查。
原因很简单:你只是会“用”AI,但不理解AI背后那几个核心概念。LLM是什么、Agent怎么运作、提示词为什么稍微改几个字效果就天差地别——这些基础知识,恰恰决定了你是把AI当高级搜索用,还是真正把它当作一个可编程的“数字员工”来使唤。
这篇文章我打算把LLM、Agent、提示词这三个概念掰开了讲清楚,不堆公式,不抄文档,全部用我实际开发和调试过程中积累的经验来讲。内容更适合已经写过几年代码、想系统补齐AI认知盲区的程序员。看完之后,你至少能回答这几个问题:大模型生成内容的原理到底是什么?为什么需要Agent而不是一个更大的模型?写好提示词和写好代码之间有什么共通之处?
我尽量用大家熟悉的编程概念来做类比,毕竟咱们都是写代码的人,用“函数”“状态机”“测试用例”这些词去理解LLM和Agent,会比看十篇科普文章都要快。
2. LLM到底是什么:它不是一个数据库,而是一个“文字接龙大师”
2.1 大模型的本质是预测下一个词
很多人对LLM(大语言模型)有个误解,觉得它内部像Wikipedia一样存了大量知识,需要的时候直接查出来。这是错的。GPT、Claude、Qwen这类模型,本质上做的事情非常简单:给定前面一段文字,预测下一个最可能的词(token)是什么。
把这句话拆开看:
- “给定前面一段文字” —— 这是输入,也就是你的prompt;
- “预测下一个最可能的词” —— 这是模型在每一步做的事情;
- 生成一整段回答,其实是模型反复执行“预测下一个词”这个过程数百上千次的结果。
你可能会想:“就这么简单?”对,核心机制就是这么“简单”。但为什么效果这么惊人?因为模型内部的参数量达到千亿级别,它在训练过程中从海量文本里学到的不只是词与词之间的相邻关系,而是句法结构、逻辑推理、世界常识、甚至某种程度的“意图理解”。就像一个围棋高手,他在棋盘上落子的本质也只是一次决策,但这个决策背后是成千上万局对弈积累出来的判断力。
用代码来类比会更清晰。你可以把LLM理解成一个特殊的函数:
def llm(context: str, max_tokens: int = 100) -> str: # 这是一个极其简化的示意 output = "" for _ in range(max_tokens): next_token = predict_next_token(context + output) output += next_token return output每一轮预测,模型都会把之前已经生成的所有内容重新纳入考虑,再决定下一个词。这就是为什么你让AI写一篇长文时,它能保持前后连贯——因为它在写后面几句话的时候,依然“记得”自己前面写过什么。
这个理解非常关键,因为它引出一个重要结论:LLM没有真正的“记忆”,它的“记忆”只能来自当前对话的上下文窗口。一旦超出上下文窗口的长度,最早的内容就会被截断或丢失。这解释了为什么处理超长代码文件时,模型经常“忘记”文件开头的变量定义。
2.2 上下文窗口:LLM的“工作内存”
上下文窗口(Context Window)是理解LLM能力的第二个关键概念。它指的是模型一次性能“看到”的最大token数量。你可以把它类比成程序员的工作内存:
- 你写代码的时候,IDE里打开的文件、临时记住的变量名、当前函数的调用链——这些是“工作内存”,决定你处理当前任务的效率;
- 而硬盘上的所有代码、你不用的时候根本不会去想的旧项目——这是“长期记忆”。
LLM没有长期记忆。它对某个话题的“知识”全部压缩在参数里,对话过程中它唯一能依赖的就是当前上下文窗口内的内容。所以就有了两个实际工程问题:
第一个问题是信息超限。把整个项目所有文件一次性塞进对话,窗口肯定装不下。实际项目中我一般按模块拆分,或者用工具把关键函数提取出来,只给模型喂必要的信息。
第二个问题是信息稀释。即便窗口撑得住,塞了太多无关内容,模型对真正重要的那部分“注意力”也会被稀释。有一个真实例子:我需要AI帮我重构一个函数,但因为担心信息不全,把整个项目说明文档、配置文件、历史改动记录都贴进了对话。结果模型把重点放在了一个无关紧要的配置项上,反而忽略了我标注的核心重构目标。后来我把对话精简成只保留目标函数、输入输出示例、约束条件,效果立刻改善。
所以,在使用LLM时有一条黄金法则:上下文不是越大越好,而是“够用且精准”最好。就像git提交时不要把所有文件都加进去,而是只提交相关的改动。
2.3 temperature与随机性:为什么同一个问题两次结果不一样
用LLM编程时,很多初学者会困惑:为什么我同一个prompt提交两次,结果往往不一样?这不是Bug,这是模型的设计特性。
模型在预测下一个token时,并不是永远选概率最高的那个,而是根据一个叫“温度”(temperature)的参数在概率分布上进行采样。温度越高,结果越随机、越有创造性;温度越低,结果越确定、越接近概率最高的选项。
用编程来类比:贪婪解码相当于每次都选概率最大的函数,结果最稳定;temperature = 0.7相当于在候选函数里做加权随机选择,这次选A、下次可能选B,但总体趋势还是偏向高概率选项。
实际项目里我一般这么配置:
- 写代码、改Bug、生成结构化JSON时,用偏低的温度(0到0.3),保证输出稳定、可预期;
- 做头脑风暴、命名、写文案、生成测试用例变体时,用偏高的温度(0.7到1.0),希望模型给我一点意外之喜。
理解temperature还有一个好处:当你发现AI输出不稳定、时好时坏时,你首先会怀疑是不是温度设置太高,而不是去怀疑prompt写错了。这能帮你省下不少排查时间。
3. Agent到底是什么:从“问答工具”到“数字员工”的进化
3.1 单纯靠LLM解决不了的场景
LLM本身有一个明显的天花板:它只能“说”,不能“做”。它可以告诉你“应该调用某个API来获取天气数据”,但它自己不能去调用;它可以给你一段“应该能解决这个问题”的代码,但它不能把这段代码跑起来验证。
举个具体例子。假设你接到一个任务:写一个脚本,从二十个数据源抓取信息,清洗、合并、生成一份报告,最后把报告发送到指定邮箱。如果只用LLM,你需要自己写代码、调试、运行、处理异常;但如果你用一个Agent,你只需要告诉它“去完成这个任务”,它会:
- 拆解任务:获取数据、清洗、合并、生成报告、发邮件;
- 选择合适的工具:写一个爬虫脚本、调用pandas做清洗、用模板生成HTML、调用SMTP接口发信;
- 执行并观察结果:跑起来,看到报错就修,修完再跑;
- 循环直到任务完成。
这就是Agent存在的根本原因:LLM只能生成计划,Agent来执行计划。
3.2 Agent的经典架构:模型、规划、工具、记忆
Agent不是某一个具体技术,而是一套软件架构。业内比较公认的Agent设计框架包含四个核心模块。
第一个模块是模型(Model),通常是LLM,负责理解指令、生成决策和推理。它是Agent的“大脑”,但“大脑”本身不能干活。
第二个模块是规划(Planning),负责把大任务拆解成可执行的小步骤。这就像你把一个大型重构任务拆成“改数据结构、改调用方、跑测试、修兼容性”四个子任务。规划能力决定Agent是东一榔头西一棒子,还是能有条不紊地推进。
第三个模块是工具(Tools),这是Agent与外部世界交互的接口。常见工具包括代码解释器、API调用、数据库查询、搜索引擎、文件读写等。在代码实现上,每个工具通常表现为一个函数,LLM根据当前任务决定调用哪个函数、传入什么参数。
第四个模块是记忆(Memory),用于保存任务过程中的中间状态、历史结果、以及从错误中学到的经验。注意,这里的记忆分为短期记忆和长期记忆两种,短期记忆就是当前任务的上下文,长期记忆则是Agent在多次运行之间保留的信息,通常需要外部存储。
我画过一张非常简陋的Agent工作流程图,用文字描述就是这样一个循环:
用户指令 → 模型理解 → 规划子任务 → 调用工具 → 观察结果 → 更新记忆 → 决定下一步 → 循环处理 → 输出最终结果从实现角度看,这个循环本质上就是一个while循环:模型输出“下一步调什么工具、传什么参数”,系统执行工具、拿到结果,再把结果塞回上下文,让模型继续决策,直到模型输出“任务完成”。
3.3 代码实例:手写一个极简Agent
纯理论容易飘,我写一个最小可运行的Agent示意代码,你拿去看一眼就明白核心循环是怎么回事。
import json # 模拟两个工具 def search_files(keyword: str) -> str: # 真实场景这里会调用文件系统搜索 return f"找到了包含 {keyword} 的3个文件:a.py, b.py, c.py" def read_file(path: str) -> str: # 真实场景这里会读取文件内容 return f"文件 {path} 的内容是:...(此处省略具体内容)" TOOLS = { "search_files": search_files, "read_file": read_file, } def call_llm(messages: list) -> str: # 真实场景这里会调用GPT/Claude等模型API # 这里返回一个模拟的模型决策结果 if "找到入口文件" in messages[-1]["content"]: return json.dumps({"function": "search_files", "args": {"keyword": "main"}}) return json.dumps({"function": "read_file", "args": {"path": "main.py"}}) def run_agent(task: str) -> str: messages = [{"role": "user", "content": task}] max_steps = 10 for step in range(max_steps): decision = json.loads(call_llm(messages)) result = TOOLS[decision["function"]](**decision["args"]) print(f"Step {step+1}: 调用 {decision['function']}({decision['args']}) -> {result}") messages.append({"role": "assistant", "content": str(decision)}) messages.append({"role": "tool", "content": result}) if "完成了" in result: return "任务完成" return "达到最大步数,任务终止" run_agent("请分析项目结构,找到入口文件并读取内容")这个代码极其简化,但你已经能感受到Agent的核心思想:模型不是直接输出答案,而是输出“调用什么工具、传什么参数”的决策;系统负责执行决策,并把执行结果反馈给模型,模型再继续决策。
真实生产环境里的Agent比这个复杂得多,有工具注册机制、有错误重试逻辑、有任务中断恢复、有并行的子Agent协同,但骨架就是这个while循环。
3.4 ReAct模式:把“思考”和“行动”交错进行
实际使用Agent时,单纯让它“干一步看一步”效率并不高。更有效的模式叫ReAct,即Reasoning + Acting,让模型在每一步先思考“现在该怎么做、为什么这么做”,再执行具体动作。
你可以把ReAct理解成一个程序员的工作习惯:拿到任务后先花时间想清楚方案、列出风险点、规划测试策略,然后才开始敲键盘;跑完一步测试,观察结果,再决定下一步怎么调整。
实现ReAct的方式,通常是在prompt里要求模型按固定格式输出“思考”和“行动”两个字段:
思考:当前任务需要先定位入口文件,因为后续依赖分析都基于入口。 行动:调用search_files工具,搜索关键词"main"。然后系统把“思考”和“行动”都记录下来,行动结果再反馈给模型,进入下一轮思考。这种“思考—行动—观察—再思考”的循环,让Agent的每一步决策都有据可查、可追踪、可回滚。对程序员来说,这就像在代码里打日志,出了问题随时可以定位是哪一步逻辑错了。
我在实际调试Agent时,经常要求系统把每一步的“思考”内容打印出来。很多时候你看着模型“思考”到第三步方向就歪了,根本等不到工具执行出结果就能发现问题。这个习惯帮我省了大量排查时间。
4. 提示词工程:不是“会说话”,而是“会写函数”
4.1 提示词的本质是“函数的入参约束”
网上总有人说“提示词工程就是学会跟AI聊天”,这话对了一半。聊天式的提示词确实能解决日常问题,但工程化使用AI时,提示词更像是一份“函数签名说明”或者“接口文档”。
想象你写了一个函数:
def process_report(data_source, output_format, include_summary=True): """处理数据报告。 Args: data_source: 数据来源路径,支持csv、json output_format: 输出格式,支持md、html include_summary: 是否包含摘要统计 """ pass调用方(也就是你或你的用户)需要知道这个函数的入参是什么、有什么约束、返回什么结构。同样,当你向LLM下达任务时,提示词就是你与模型之间的“接口契约”。写得模糊,模型只能靠猜;写得精确,模型就知道该从哪个角度入手、以什么格式输出、避免什么雷区。
一个工程化的提示词通常包含这几个要素:
- 角色(可选):让模型以特定专业视角思考,比如“你是一名资深Python后端工程师”;
- 任务目标:明确说明要做什么,越具体越好;
- 输入数据:把源数据、上下文放在显眼位置;
- 约束条件:什么能做、什么不能做、输出格式要求;
- 示例:给一两个正例或反例,比写一百字解释都管用;
- 输出格式:要求JSON、Markdown、表格还是代码。
你可以把这些要素理解为函数参数和注解:角色是装饰器,任务目标是函数名,输入数据是入参,约束条件和输出格式是返回类型注解。
4.2 结构化提示词:少走弯路的模板参考
结合我自己的开发经验,整理一个相对通用的结构化提示词模板。平时接到需要批量处理的任务,我基本都会套这个模板再微调。
## 角色 你是一名资深的Python自动化脚本开发者,擅长用简洁、健壮的代码解决实际问题。 ## 任务目标 编写一个Python脚本,读取指定目录下的所有CSV文件,合并并去重,输出汇总数据到excel文件。 ## 输入数据 目录路径:/data/raw 字段格式:id, name, amount, date ## 约束条件 1. 只使用Python标准库和pandas; 2. 去重规则:id+date完全一致时去重; 3. 必须处理文件不存在、字段为空等异常情况; 4. 输出文件名为merged_YYYYMMDD.xlsx。 ## 输出格式 直接输出完整的Python代码,并在代码前用三行说明脚本的使用方式和依赖环境。你可能会觉得这个模板太长,但实际效果非常明显。我对比过:同样的任务,聊天式提示词可能让模型给出十个不同版本的代码,而结构化提示词基本一次到位,偶尔需要微调参数。
有一种比较常见的抗拒心理是“我写提示词怎么像在写文档,太费时间了”。我的回应是:写一段好提示词花五分钟,换来的是两小时的代码调试时间和吐血的烦躁感。这笔账怎么算都划算。
4.3 few-shot:给模型“参考答案”胜过讲“做人道理”
在提示词里加示例,就是few-shot学习。模型的推理能力虽然强,但在面对具体格式要求时,少废话、多给例子才是最有效的。
我踩过一次很深的坑。有段时间我需要让AI批量生成某种结构化JSON数据,我写了一大段描述:“注意字段类型,注意嵌套层级,注意数组为空时输出空数组不要输出null……”结果模型输出的JSON十次里有七八次不合规。后来我改成了给两个具体例子:一个正常数据,一个边界情况,直接贴在提示词里。效果立竿见影,合规率基本到百分之百。
用编程类比就更好理解了:你不写文档,而是在代码注释里放两个example:
# Example 1: {"id": 1, "items": [1, 2, 3]} # Example 2: {"id": 2, "items": []} # 注意:空数组时输出[],不是null给示例,本质上是给模型划出了一条“格式边界”。大模型在训练时看过海量的JSON文档,你给的两个示例会帮它锁定你想要的Schema,比任何口头描述都精准。
但要注意,示例不要过多,两到四个一般是甜点区。给太多示例反而会挤压本就有限的上下文空间,也容易让模型机械模仿你给的例子,丧失对合理变化的泛化能力。
4.4 提示词调试方法论:像调试代码一样调试prompt
提示词工程里有一个概念叫“调试”,方法和调试代码高度相似。我在项目里固定使用一套流程,分享出来供参考。
第一步是基线测试。同一个prompt,连续提问五次,观察输出差异。如果五次结果高度一致,说明prompt约束力强;如果乱七八糟,说明约束不够或者任务本身模糊。
第二步是单变量调整。一次只改一个要素,比如角色设置、输出格式、示例数量。不要同时改三个变量,否则你根本不知道哪个改动产生了效果。这跟二分定位Bug的思路完全一致。
第三步是构建测试集。用一组固定的测试输入去验证prompt的稳定性,而不是一次成功后就直接上生产。尤其是如果模型后续换了版本或参数微调,旧的prompt可能不再可靠,重新跑一遍测试集非常必要。
第四步是记录版本。把prompt像代码一样纳入版本管理,我一般用类似这样命名:prompt_batch_v3.md。否则过了一个月,你完全不记得当初那句“限制输出长度在200词以内”是在哪个版本里加的。
我见过很多开发者在翻车之后反复改prompt,越改越长,最后变成一篇两三千字的小作文,模型依然输出不理想。问题往往不是prompt不够长,而是关键约束被淹没在大量冗余文字里。好的prompt讲求精确和简洁,而不是把所有可能性都列举一遍。
5. 把三者串起来:一个完整的AI应用实战复盘
5.1 项目背景与需求拆解
理论讲了一堆,我们来看一个我在实际项目里做过的完整案例。需求大体是这样:要给一个内部平台写一个“智能代码审查助手”,输入一段代码或一个PR的diff,自动输出审查意见,包括潜在Bug、安全隐患、可读性问题,并给出修改建议。
需求拆解后,我很快就意识到纯LLM不够用。原因有三点:
第一,代码审查需要读取外部信息:比如项目里的代码规范文档、历史提交记录、依赖配置等,这些超出了单次对话的上下文;第二,审查结果需要一个固定结构,方便后续自动化处理;第三,整个流程涉及多次打开文件、搜索相关内容,纯靠一次问答根本做不完整。
于是我把方案设计为:一个Agent,里面内置读取代码库的多个工具,配合一个严格的提示词模板,最终由LLM生成结构化审查报告。这正是LLM、Agent、提示词三个概念的组合拳。
5.2 Agent工具设计与核心实现
给Agent设计的工具包括:
read_file(path):读取指定文件内容;search_symbol(name):在整个代码库中搜索某个函数/变量的定义和使用位置;get_diff(branch):读取当前分支与目标分支之间的代码差异;check_security(code):调用一个简单规则引擎,快速检测硬编码密钥、SQL注入等常见安全问题。
在Agent运行流程上,我划分成四个阶段:
- 定位阶段:读取PR描述、获取diff文件列表;
- 探索阶段:逐个打开变更文件,搜索相关上下文(依赖了哪些函数、在哪些地方被调用);
- 分析阶段:结合代码规范文档,对每个变更点生成初步意见;
- 汇总阶段:把所有意见拼装成结构化报告,包含问题等级、问题描述、修复建议、风险说明。
代码骨架大致如下:
def run_code_review_agent(diff_info: dict) -> dict: messages = [] # 阶段1:初始化上下文 messages.append(init_prompt(diff_info)) # 阶段2:循环调用工具 while True: decision = llm_decision(messages) # 模型输出下一步工具调用 if decision["action"] == "finish": break result = execute_tool(decision["action"], decision["args"]) messages.append(tool_result_message(result)) # 阶段3:让模型汇总报告 final_report = llm_generate_report(messages) return parse_report(final_report)这个代码只是示意,但它展示了三个概念怎么协作:Agent负责循环控制,LLM负责每个节点的决策,提示词负责让LLM的决策保持在正确轨道上。
5.3 核心提示词与参数配置经验
这个项目里,最终维持稳定效果的提示词有几个关键设计,你可以直接借用到自己的场景。
首先是工具描述要明确。给Agent的工具注册时,不光注册函数本身,还要把“什么时候用这个工具、什么样的参数是合理范围”写清楚。一个典型的工具描述长这样:
工具名称:search_symbol 用途:在整个代码库中搜索指定函数、类、变量的定义位置和引用位置。 适用场景: - 需要知道某个函数在哪里定义或在哪里被调用时; - 需要分析跨文件依赖关系时。 参数约束: - symbol: 必须是有效的标识符,长度1-100字符,不要包含空格。工具描述写得清晰,Agent就不会在该搜索时猜一个不存在的方法名,更不会在一个只改了配置文件的任务里调用代码搜索工具。
其次是决策指令要限定输出格式。我们要求LLM每一步的决策输出必须是一个固定结构的JSON对象:
{ "action": "search_symbol", "args": {"symbol": "parse_config"}, "reason": "需要定位parse_config函数的定义,以判断改动是否会影响调用方" }这么设计的好处是解析稳定、日志可查、出错也能快速定位是哪一个步骤出了问题。
第三是最终报告模板要严格。我们给出一个审查报告模板,模型输出时按模板填充,包括“问题位置、严重等级、问题描述、修复建议”四个字段。这样所有审查结果可以直接导入工单系统,不需要二次加工。
温度参数我设的是0.2。因为这个场景需要的是稳定、保守的判断,不需要创意。如果你把温度设成0.8,第二天同一段代码可能审出完全不同的意见,这在工程上是不可接受的。
5.4 效果对比与经验教训
上线后我们随机挑选了二十个历史PR做对比测试。结论是:这个Agent能发现一部分真实有价值的问题,尤其是空指针风险、资源未关闭、明显SQL注入,这些在“定位——探索——分析”的流程下都能稳定检出。但它也会有一些“幻觉型建议”,比如建议你修改一个并不存在的问题,或者在一段很正常的代码里强行挑出风格问题。
这个经历让我对AI应用的工程化有了一个更清醒的认识:Agent和提示词再强,最终交付物依然需要人工复核。AI更像一个高效的新人同事,而不是一个百分百可靠的资深专家。最佳使用方式是让它来扩大审查覆盖面、消除低级错误,再由有经验的开发者做最终判断。
另一个深刻教训是控制Agent的Token消耗。我们的Agent在“探索阶段”特别容易产生大量工具调用,每次调用都会把结果塞回上下文,Token消耗像流水一样。后来我加了两个限制:每个文件只允许读取两次,每次工具调用返回的内容截断到两千字以内,并且设置了总步数上限。否则一个简单的PR可能跑出几万Token,账单让人肉疼。
6. 绕坑指南:我在LLM、Agent、提示词项目中踩过的坑
6.1 上下文污染,定位最深的坑
如果你在一个Agent项目里发现模型行为越来越离谱,先别怀疑模型变笨了,大概率是上下文里混入了太多无关信息。我们有一次在代码审查场景中出现了很神奇的现象:Agent审到一个配置文件居然给出了“打开某个文件查看函数定义”的决策。排查半天发现,是早期某个步骤的中间结果在上下文里没清理,模型被旧信息误导了。
解决办法是严格控制上下文中每个工具返回的内容。该截断的截断,该写入长期记忆的就不要让它在短期上下文里反复出现。这就像你写代码时不会把整个dubug日志全发给同事,而是贴出关键那一行。
6.2 工具注册不足导致Agent“绕圈子”
Agent能力再强的模型,也会受限于你给它注册的工具集合。有一次我让Agent完成一个“查找所有测试失败的用例并生成报告”的任务,它的第一轮决策居然是“先读取源代码推断可能失败的用例”。这明显走了弯路。问题就出在我没有给它注册“运行测试并读取结果”的工具,它只能用已有的读文件、搜索这类工具去“推断”结果。
修复方式很简单:补充一个执行pytest并捕获输出的工具。之后Agent的第一步决策就变成了“运行测试工具,读取失败列表”,路径立刻清晰。所以,Agent的表现上限很大程度上取决于你的工具集设计,模型只负责在有限选项中做选择。
6.3 提示词“过度约束”导致模型僵化
有约束很必要,但约束不能走向极端。有一段时间我在提示词里把每个输出字段都精确到必须几个字,结果模型产出的审查意见全是套话,比如“建议优化代码可读性”——放到任何代码上都成立,等于没写。
后来我调整了约束策略:结构层面严格约束,内容层面保留发散空间。给框架、给字段、给必填项,但不限制每句话的长度和用词。模型反而给出了更具体、更有针对性的意见。
这个经验可以总结成一句话:提示词约束的粒度,应该从代码思维里借鉴“接口稳定、实现自由”的原则。接口(输出结构)死死咬住,实现(具体表达内容)放开让它发挥。
6.4 版本依赖:底层模型一换,Agent立刻“变笨”
有一次我们把Agent底层模型从一个版本升级到另一个版本,没有动任何提示词和工具,结果效果明显下滑。当时的直接反应是“模型变笨了”,后来对比发现,新模型对某些工具调用的格式偏好不同,原来的示例引导它产生偏差。
排查之后,我只是在提示词的few-shot示例里更新了两个调用样例,效果就恢复正常了。这件事提醒我:Agent系统的组合特性决定了任何一个组件升级都可能引起连锁反应。每次大模型版本升级,都要重新跑一遍基线测试集,不要默认“升级是向后兼容的”。
6.5 成本控制:每个环节都要记账
做过Agent项目的人,大多经历过“跑着跑着发现余额急剧下降”的惊吓。这个项目里我们做了非常细致的Token记账,给每个阶段、每个工具调用都打上标记。上线跑了一周后统计发现,最耗Tokens的竟然不是最终报告生成,而是“探索阶段”大量读取文件带来的上下文膨胀。
优化手段之前提过:限制工具返回内容长度、限制调用次数、设置最大循环步数、把中间结果做摘要而不是原样返回。这里再补充一个技巧:对于不需要原始全文的工具结果,让模型只提取关键信息再放入上下文,能把Token消耗砍掉一半以上。
7. 后续可以怎么扩展:从“会搭”到“会优化”
如果你自己动手把上面的极简Agent跑通了,接下来可以往这几个方向深入。
第一个方向是稳固记忆模块。目前的Agent基本靠上下文窗口活着,窗口一满就“失忆”。你可以尝试引入外部向量数据库,把历史决策、工具结果、错误记录都存起来,需要时检索相关部分塞回上下文。这本质上是在做长期记忆。
第二个方向是多Agent协作。单个Agent能力有限,但你可以把任务拆给多个角色Agent:一个Agent负责代码搜索、一个Agent负责测试运行、一个Agent负责报告生成,通过一个协调Agent做仲裁汇总。这就像全栈一个人做不如一个团队分工协作更高效,但多Agent之间如何通信、如何避免重复动作,是一个真正的工程难题。
第三个方向是评估系统建设。没有评估,你根本不知道这次prompt改动是好是坏。最简单的做法是维护一套固定的任务集和参考答案,每次改动后跑一遍准确率。再进一步可以引入评分模型,让一个裁判Agent对主Agent的输出打分类似地评估输出质量。
第四个方向是让Agent学会自我反思。在任务结束时增加一个“总结反思”步骤,让模型结合刚才执行过程中遇到的报错和无效尝试,输出哪些策略有效、哪些策略失败,并把反思结果作为下次任务的先验知识。这不是花架子,多轮迭代之后,Agent的工具选择路径会明显更合理。
8. 最后分享一点个人体会
做AI应用开发这段时间,我最大的感受是:真正难的不是模型,而是工程。LLM像一台发动机,动力很强,但你要设计好变速箱、传动系统、刹车和仪表盘,它才能成为一辆好开的车。
提示词工程也是一样,它不是“说话技巧”,而是一种接口设计能力。你定义得越清晰,模型发挥得越稳定。Agent更是如此,它要求你把大任务拆成小步骤,每一步都想清楚输入输出、异常处理和退出条件——这种事情程序员每天都在做,只是换成了一种全新的执行载体。
我建议看到这里的你,不要只停留在刷文章和看演示。自己动手搭一个最简单的Agent,哪怕只是调用本地文件系统,走过一遍“模型做决策—工具执行—结果回填—继续决策”的完整循环,你会瞬间理解这些概念之间是怎么协同的。纸上得来终觉浅,这句话放在AI编程领域尤其成立。
这次就分享到这里。如果你也在做相关的尝试,欢迎在评论区聊聊你踩过的坑,尤其是那些看起来是AI问题、实际上是工程问题的坑——这块的讨论,市面上还太少。