大模型不会“跳跃”:多步推理与Agent工程如何绕过LLM的天然局限
如果你最近在关注大模型应用开发,很可能见过一个很扎眼的标题:LLMs Can't Jump。它字面意思是“大语言模型不会跳跃”。第一次看到这个标题时,我甚至觉得它比很多论文摘要都更精确——它一针见血地指出了大模型在真实工程里的最大别扭:LLM 本质上只能一个 token 一个 token 地往后预测,它没法像人做题那样,跳过十几页草稿纸直接写出答案。这个限制不是某家模型厂商的毛病,而是自回归架构的天然约束。
这篇文章不打算逐字解读那篇 PDF,而是想借这个标题,聊清楚一个对开发者也同样重要的问题:为什么大模型在多步推理、工具调用、Agent 规划这些任务上会“跳不动”,以及工程上应该怎么绕过去。你不需要是算法研究员,只需要正在调 LLM API、做 RAG、写 Agent、或者纠结如何选择提示策略,就能从中得到可落地的判断:哪些任务可以直接交给大模型,哪些任务必须拆成小步,哪些场景必须给它装一个“外部跳板”。
后面的内容会先讲清楚“不能跳跃”的原理,再解释它为什么是多步推理和 Agent 场景的隐形天花板,然后用三个可以直接改写的 Python 示例,分别演示直出回答、函数调用、最小 Agent 循环这三种路径。最后附上一份排查清单和生产环境建议。我的核心判断是:LLMs Can't Jump 并不是模型缺陷,而是一个接口契约——真正会做工程的人,不是逼模型去跳,而是把路拆成台阶,或者给它一部电梯。
1. “LLMs Can't Jump”到底在说什么
理解这句话,要先回到大模型生成文本时的基本机制。无论是 GPT 架构还是其他主流自回归语言模型,它的工作方式都是:给定一段已经生成的文本,预测下一个 token 的概率分布,然后选一个 token 拼接到文本后面,再继续预测下一个。这个过程是严格串行的,每一步都依赖上一步的输出。你可以把它理解成一个只能向前走、不能后退也不能越步的独木桥,模型永远在“当前位置预测下一个词”。
“Jump”这个词,在计算机科学里有非常明确的含义——跳转指令,也就是程序不按照顺序逐条执行,而是直接跳到某个地址继续运行。人类推理中也有类似“跳跃”的能力:一个经验丰富的工程师看到一个报错日志,可能三秒钟就判断出问题在配置文件里,而不是从头到尾检查代码;一个数学成绩很好的人,可以跳过大量中间运算,直接说出思路的方向。这种基于经验、模式识别和启发式规则的“跳跃”,恰恰是自回归模型不具备的。
模型不具备跳跃能力,是因为它没有一个独立的“全局规划器”。它的全部决策只来自当前上下文里已经出现的 token。例如,你要模型解一个 50 步的数学题,模型只要在第 10 步出现一个微小偏差,后面的 40 步就会跟着错。它无法像人一样先在心里画一条“答案大概在这个区间”的路线,再回头检查哪一步出了问题。
从工程角度看,这个限制最直接的影响是:凡是需要多步精确推导、长程规划、或多个环节状态传递的任务,直接丢给 LLM 都容易翻车。模型不是不知道怎么做,而是缺少“跳到下一个关键节点”的能力。它会老老实实把每一步都生成出来,但中间一旦上下文过长,注意力分散,误差就开始累积。
这里需要区分一个容易混淆的概念:很多人以为“模型输出很快,所以它一定也能快速规划”,实际上输出速度和推理深度是两回事。模型可以在几百毫秒内生成一段看似完整的答案,但这个答案可能是“流畅的错误”,因为它并没有经历人类意义上的试错与回溯。那些看起来聪明的“跳跃”,本质上是训练语料中相似问题的模式匹配,而不是实时规划。
所以,“LLMs Can't Jump”不是在说模型蠢,而是在说:模型的推理路径是线性的、局部的、缺少全局回溯的。开发者在设计应用时,必须把这一点当作底层约束来接受,而不是反复调 prompt 奢望它有一天能“开窍”。
2. 为什么“不能跳跃”会在多步任务里放大成灾难
如果只是单轮问答,比如“这首歌的原唱是谁”“这个 API 的返回字段是什么”,自回归的逐 token 生成完全够用。因为这类任务不需要内部状态传递,答案就在语料里,模型只需要做模式匹配。问题出现在需要“多步操作”的任务中:每一步的输出会成为下一步的输入,任何一步出错,都会像滚雪球一样被放大。
最典型的场景是数学和逻辑推理。设一个需要 5 个子步骤的计算题:读取条件、建立方程、化简、代入、求值。每一步都可能因 token 采样、上下文注意力、或者数值格式问题产生偏差。人类可以跳到某一步回头检查,但 LLM 的生成过程里没有“回头”这个选项。一旦第 2 步写错了式子,第 3 步以后的所有内容都会在错误的起点上继续完善,最终得到一个看起来很像样的错误答案。这也是为什么很多论文和工程实践都强调:高风险的数值计算任务,不应该让模型直接心算,而应该让它调用计算器或代码解释器。
第二个放大场景是长程 Agent 任务。比如你让一个 Agent 去“调研三款向量数据库并给出选型建议”,如果这个 Agent 只有一个大模型,没有外部工具和外部记忆,它就会尝试把整份调研报告“一口气”生成出来。问题是:它会编造不存在的数据库版本、编造不存在的基准测试数据、混淆不同产品的特性。这本质上不是因为模型想撒谎,而是它需要在一次串行生成中完成太多“跳跃”:从检索记忆,到比较参数,再到形成结论,中间没有任何验证环节。
第三个场景是组合优化类任务,例如调度、排班、路径规划。这类任务的状态空间会出现组合爆炸,而自回归模型每一步只选择一个 token,它没有能力在全局状态空间里进行搜索和回溯。你可以通过 prompt 让它“看起来在推理”,但只要分支一多,它很快就会陷入局部最优或干脆输出一个不可行方案。真正能解决这类问题的方法,是把问题转成约束求解、搜索算法,或者至少把大问题切分成多个可以独立验证的小问题。
这也引出一个判断:LLM 适合做“局部模式识别”和“语言生成”,不适合做“需要全局搜索”的确定性计算。工程上最稳妥的做法,是把 LLM 当作一个“善于理解意图和生成文本的操作员”,而不是一个“万事通计算器”。它不懂的时候,应该让它调用工具;它记不住的时候,应该给它 RAG 知识库;它无法保证正确的时候,应该用流程去兜底。
从成本角度看,“不能跳跃”还会带来提示词和上下文长度的膨胀。为了弥补模型不会自动拆步的问题,工程上常常需要用很长的 system prompt 去约束它,这反过来增加了每一轮请求的 token 消耗和延迟。在某些长上下文任务里,成本会高到令人意外。这也是为什么近两年“LLM 应用为什么需要编排框架”会成为热搜问题——因为编排框架本质上就是在替模型处理那些它“跳不过去”的环节。
3. 不能跳跃不等于不能成事:三种补偿手段
既然知道了模型不能跳跃,工程上的思路就很简单:要么让它不要跳跃,要么帮它跳跃。围绕这个思路,业界形成了三种主流补偿手段,按成本从低到高分别是提示工程、外部工具、协作编排。
第一种是提示工程,核心是显式要求模型分步。最典型的方法是 Chain-of-Thought,也就是让模型在输出里先写推理过程,再写最终答案。它之所以有效,是因为它把模型本来就要做的一步步预测“外化”成了可见文本,从而减少了每一步之间的隐含跳跃。对于简单的多步计算和通用问答,这种方案往往立竿见影。它的局限在于:如果任务本身需要真实的数据查询或计算,模型只能“在想象中分步”,没法真正访问外部状态,所以仍可能产生幻觉。
第二种是外部工具,核心是让模型把计算和检索交给专业模块。典型例子是函数调用(function calling / tool use)。模型不再自己心算 12+3 5,而是输出一个“调用计算器工具,参数为 12+3 5”的结构化请求;程序接收到请求后,真正执行计算,再把结果回传给模型。这样,模型只需要完成“理解问题、决定调用哪个工具、解析返回结果”这三件事,全部都是它擅长的文本理解与生成。工具还解决了“跳不过去”的另一个痛点:工具返回的中间结果可以写入上下文,作为下一步的稳定输入,避免了模型自做主张地更新状态。
第三种是协作编排,核心是把多个模型调用和工具调用组合成一个有状态、可回滚的流程。你可以用 LangChain、LlamaIndex、Spring AI 这类编排框架,也可以自己写一个几十行的循环:模型输出意图,程序执行动作,观察结果,再交给模型继续决策。这个模式正是 Agent 的基本形态。它看起来复杂,但实际上是绕过“不能跳跃”最彻底的方式——因为流程本身已经不要求模型一次性完成所有工作,而是把工作分解成多个“小步”,每一步都经过外部验证。
这三种手段并不互斥,实际生产里往往组合使用:先用提示工程让模型输出结构化需求,再用工具完成精确计算,最后用编排框架管理多轮状态。如果你看到一些团队宣称“我们只靠一个 600 行的 prompt 就能让模型完成复杂任务”,不必太当真;更可靠的系统设计,永远是让 LLM 处在自己擅长的狭窄通道里,然后用工程手段补齐它缺失的全局控制能力。
4. 环境准备与最小调用示例
下面开始进入可操作的示例。先说明运行环境,这些示例都以 OpenAI 兼容的 API 为标准。无论你使用的是国内大模型厂商的 API、开源模型搭建的推理服务,还是某个云平台托管的模型接口,只要它支持 chat.completions 这种调用格式,代码都可以直接复用。具体模型名称和接口地址,以你自己的服务商文档为准。
建议使用 Python 3.9 以上版本,并创建独立虚拟环境,避免依赖冲突。
python -m venv .venv
source .venv/bin/activate # Windows 使用 .venv\Scripts\activate
pip install --upgrade openai
如果你使用的是 OpenAI 官方 SDK,示例里的 OpenAI 客户端类可以直接用;如果你使用的是兼容网关,也可以把 base_url 设置为网关地址。下面是第一个最小示例,用来确认 API 通路是否正常,同时我们也会在后面基于它做对比实验。
# 文件路径:llm_minimal.py
from openai import OpenAI
client = OpenAI(
api_key="YOUR_API_KEY", # 替换为你的 API Key
base_url="YOUR_BASE_URL", # 替换为你的推理服务地址,官方可留空
)
def ask(prompt: str, model: str = "your-model-name") -> str:
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=0.0, # 可复现任务尽量用 0
)
return resp.choices[0].message.content
if __name__ == "__main__":
print(ask("你好,请用一句话介绍自己。"))
这个代码运行后,预期会输出模型的自我介绍。如果报错,第一步先检查两点: base_url 是否指向正确的服务地址, api_key 是否有足够权限。另外,很多本地推理服务支持的是 v1 路径,例如 http://localhost:8000/v1 ;如果你用的框架不同,路径前缀可能不同,需要按服务商说明调整。
关于精度问题,这里也顺带提一句,因为很多人在本地用开源模型时遇到过“为什么同一个 prompt 两次结果不一样”。如果在本地使用 Transformers 加载模型,要显式指定加载精度,常见选择是 fp16、bf16、fp32。
# 文件路径:model_precision_demo.py
# 本示例用于展示本地模型加载时的精度设置,具体模型名称以你下载的路径为准
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "your-local-model-path-or-hf-id"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16, # 或 torch.float16 / torch.float32
device_map="auto",
)
这里不深入展开精度对结果的影响,但可以记住一个原则:需要严格控制结果可复现的线上服务,对浮点精度和采样参数的配置要统一;bf16 在大多数新硬件上速度和显存占用都更友好,但在极少数模型上可能出现精度异常。更多细节可以参考“LLM 大模型之精度问题 fp16/fp32/bf16 详解与实践”这类专门材料。接下来进入正题,看“直出问题”和“分步推算”到底差在哪里。
5. 示例一:同样一个问题,直出 vs 分步
这个示例模拟的是开发中很常见的窘境:同一个问题,让模型直接给答案,和让模型分步写过程,结果经常不一样。我们用一个不需要真实数据的小学数学题来做对比,避免引入外部变量。
# 文件路径:llm_cot_compare.py
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY", base_url="YOUR_BASE_URL")
MODEL = "your-model-name"
direct_prompt = (
"一个农场有 12 只鸡,每天新增 3 只,农民每天卖掉 2 只。"
"5 天后农场有多少只鸡?请直接给出最终答案,不要列出中间过程。"
)
cot_prompt = (
"一个农场有 12 只鸡,每天新增 3 只,农民每天卖掉 2 只。"
"5 天后农场有多少只鸡?请你一步一步列出计算过程,再给出最终答案。"
)
def ask(prompt: str) -> str:
resp = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=0.0,
)
return resp.choices[0].message.content
print("===== 直出模式 =====")
print(ask(direct_prompt))
print()
print("===== 分步模式 =====")
print(ask(cot_prompt))
这个问题的标准计算方式是:每天净增加数量是 3 减 2 等于 1 只,5 天后总量是 12 加 5 等于 17 只。在不给出真实模型输出的前提下,这里只强调一个更常见的规律:直出模式下,模型作为自回归生成器,会在更短的上下文里直接给出答案,缺少自我纠错空间;分步模式下,模型被迫把中间计算写出来,每写一步都会成为下一步的上下文依据,因此更容易发现矛盾。
这段代码里值得关注的不是“哪个模式一定对”,而是两个工程要点。第一, temperature=0.0 只能减少随机性,不能消除随机性,尤其是推理服务使用 fp16 或 bf16 时,精度仍可能带来微小波动;如果对结果一致性要求很高,需要在前端做结果校验。第二,Prompt 的措辞会影响模型是否愿意展示中间步骤;有些模型即使用户不要求,也会因为训练数据习惯而自动输出过程,这并不代表它具备更强的推理能力,只是输出风格不同。
运行这段代码后,如果发现直出模式结果正确、分步模式反而错误,也不要惊讶。分步模式并非银弹,它会增加 token 消耗和延迟,而且在极少数情况下,模型可能会为了编造一个“看起来合理的过程”而引入误差。工程上的选择依据是:任务是否真的需要多步验证?如果需要,分步模式的价值很大;如果只是简单问答,直出模式成本更低。
6. 示例二:用函数调用给 LLM 装一个“跳板”
示例一解决的是“让模型别跳步”,但遇到真实计算时,即使模型把过程写得很完整,它仍然可能在最后的四则运算上出错。更可靠的方案是把计算工作外包给工具。这就像给一个只能走独木桥的人修了一部电梯——模型不用自己跨越计算鸿沟,只需要按电梯按钮。
下面示例展示一个最基础的工具调用流程:模型判断需要用到计算器,返回一个结构化请求,程序执行计算,再把结果回传给模型。
# 文件路径:llm_tool_calc.py
import json
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY", base_url="YOUR_BASE_URL")
MODEL = "your-model-name"
def calculate(expression: str) -> float:
# 仅用于演示!生产环境严禁直接 eval 用户或模型传来的表达式
return eval(expression, {"__builtins__": {}}, {})
tools = [
{
"type": "function",
"function": {
"name": "calculate",
"description": "计算数学表达式,支持 + - * / 和括号",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "例如 12 + 3 * 5",
}
},
"required": ["expression"],
},
},
}
]
messages = [
{
"role": "user",
"content": "请计算 (12 + 3 * 5) * (7 - 2) 的结果,不要自己心算,必须使用计算器工具。",
}
]
resp = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=tools,
temperature=0.0,
)
choice = resp.choices[0]
if choice.message.tool_calls:
tool_call = choice.message.tool_calls[0]
print("模型请求调用工具:", tool_call.function.name)
print("工具参数:", tool_call.function.arguments)
args = json.loads(tool_call.function.arguments)
tool_result = calculate(args["expression"])
print("工具执行结果:", tool_result)
messages.append(choice.message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(tool_result),
})
final_resp = client.chat.completions.create(
model=MODEL,
messages=messages,
temperature=0.0,
)
print("模型最终回答:", final_resp.choices[0].message.content)
else:
print("模型没有调用工具,实际输出:", choice.message.content)
这段代码有四个关键点。
第一, tools 参数可以让模型在生成文本之外,输出一个结构化的工具调用请求。模型本身并不会执行代码,它只是输出“我想调用 calculate,参数是 (12+3 5) (7-2)”。执行权始终在我们手里。
第二,工具返回后,必须把 tool 消息追加到 messages ,并且带回 tool_call_id ,否则后续模型回答无法关联到之前的工具结果。这是 OpenAI 兼容协议的要求,也是最容易踩的坑。
第三,真实生产环境里, eval 是非常危险的操作。无论是模型生成的表达式,还是用户传入的内容,都不应该被直接执行。你需要使用安全的表达式解析库,例如 asteval ,或者只允许白名单内的操作符和函数。
第四,这个示例虽然在演示计算器,但它已经是一个完整的“工具调用闭环”骨架。把它扩展到查询数据库、调用天气 API、检索知识库,思路完全一致。模型负责理解意图和决定调用哪个工具,程序负责真正执行,然后把结果交回给模型,让模型基于真实结果生成最终回复。
运行这段代码时,可以做一个大胆尝试:故意把 prompt 改成一个很复杂的表达式,比如 (123456789 * 987654321) % 1000000 ,观察模型是否会选择调用工具而不是自己硬算。合格的模型通常会选择调用工具,因为工具描述里明确说了“必须使用计算器工具”。
7. 示例三:一个最小 Agent 循环骨架
示例二解决的是“单次工具调用”,但很多真实任务需要多轮“思考-行动-观察”的循环。例如你让 Agent 先查一个 API,再根据返回结果决定下一个 API 调用;或者先检索文档,再写总结,再生成测试用例。这类任务不能靠一次函数调用完成,需要一个组合编排。
这里不引入 LangChain 这类重型框架,而是用不到 30 行代码实现一个最小 Agent 循环,用来理解 Agent 的本质。它做的事情很简单:不断把模型输出追加到对话里,让模型自己决定下一步怎么走,直到输出“完成”或者达到最大步数。
# 文件路径:mini_agent_loop.py
from openai import OpenAI
SYSTEM_PROMPT = (
"你是一个任务分解助手。你会收到一个目标,请把它分解成多个可顺序执行的小步骤。"
"每一步必须给出明确的可验证结果。不要试图一步到位。"
"当你认为整个任务已经完成时,最后输出“任务已完成”。"
)
def run_agent(goal: str, max_steps: int = 5):
client = OpenAI(api_key="YOUR_API_KEY", base_url="YOUR_BASE_URL")
model = "your-model-name"
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": goal},
]
for step in range(1, max_steps + 1):
resp = client.chat.completions.create(
model=model,
messages=messages,
temperature=0.2,
)
content = resp.choices[0].message.content
print(f"Step {step}: {content}")
messages.append({"role": "assistant", "content": content})
if "任务已完成" in content:
print("Agent 判断任务完成,循环结束。")
break
else:
print("达到最大步骤数,需要进一步拆解任务或补充工具能力。")
if __name__ == "__main__":
run_agent("调研三款向量数据库,给出选型建议")
这个循环真正的价值,不是它每一步的执行质量,而是它提供了一个“状态容器”:每一步的模型输出都会被放回上下文,下一次调用就能基于前一步继续推进。这其实就是 Agent 最朴素的控制流——大模型负责决策,外部代码负责把决策结果“固化”下来并传递下去。
在实际生产环境里,你通常会在循环体里增加几个关键动作:解析模型输出中的工具调用意图,执行工具函数,校验工具返回值,追加为 tool 消息,然后再调用模型。也就是说,把示例二的函数调用逻辑嵌入到示例三的循环里,就构成了一个完整的 ReAct 模式(Reasoning + Acting)。这也是很多 Agent 框架的核心思想。
为什么不建议所有团队都直接上一个重型编排框架?因为框架会隐藏大量细节。如果你不理解“模型输出需要被追加进消息历史”“工具结果必须回传”这些原理,一旦遇到奇怪的上下文错乱,排查成本会非常高。先用这个最小循环跑通流程,再根据需要使用框架,是比较稳的学习路径。
运行这段代码时,如果模型很快输出“任务已完成”,但内容明显很单薄,说明任务分解约束还不够严格。可以调整 system prompt,要求“每一步必须包含一个可执行的动作和一个可验证的产出”,也可以把任务改成更具体、确实需要多步行动的问题,例如“查询本机 Python 版本和 pip 版本,并对比解释差异”,再加入执行 shell 命令的工具。
8. 常见问题与排查思路
在实际开发中,围绕“LLM 不能跳跃”这个限制,团队遇到的高频问题其实高度集中。这里整理成一张排查表,按出现频率排序。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 模型跳过中间步骤直接给答案,结果明显错误 | 提示词未要求分步;任务本身超出模型能力上限 | 对比直出和分步两种 prompt 的输出 | 显式要求分步;把任务拆成更小的子任务 |
| 步骤一多,模型在最后几步忘掉前面的约束 | 上下文过长导致注意力衰减;中间结果没有持久化 | 查看完整请求日志,确认关键中间信息是否出现在最后的上下文中 | 用外部状态保存关键结果,每次只传最近的关键步骤 |
| 函数调用返回格式解析失败 | 模型版本不支持 tools;工具 schema 描述不清;temperature 过高 | 查看 API 原始响应中 tool_calls 字段是否为空或格式异常 |
降低 temperature;检查 schema 名称、参数类型;确认模型服务商支持 function calling |
| 工具执行结果没有被模型采纳 | 消息顺序错误; tool_call_id 未回传 |
检查 messages 中 assistant 与 tool 的顺序和 ID 是否配对 |
按协议原样回传 assistant 消息和 tool 消息 |
| 同样的 prompt 两次输出不一样 | 采样随机性;浮点精度不一致;推理框架配置不同 | 将 temperature 设为 0;固定随机种子;记录精度配置 | 对可复现任务关闭采样;统一 fp16/bf16/fp32 配置 |
| 模型拒绝接受任务,直接输出安全提示 | 安全拒识规则过严;任务描述触发关键词 | 查看拒绝的具体输出文本,分析触发原因 | 在业务允许范围内改写任务描述,避免敏感关键词;不能用绕过安全限制的方式 |
| Agent 循环进入死循环 | 模型没有判断完成条件;max_steps 设置过大或过小 | 观察每步输出是否在重复相同内容 | 在 system prompt 中明确完成条件;设置合理的最大步数;加入重复检测 |
| 长 Agent 任务成本居高不下 | 每轮都把所有历史消息传给模型 | 查看请求的 token 使用量 | 用摘要或外部记忆压缩历史;只传最近 N 轮消息 |
如果只是 API 调用不返回,第一步看的不是 prompt,而是网络连通性和认证信息。把 base_url 和 api_key 打出来核对,再确认模型名是否存在于你的服务商账号中。对本地推理服务,还要额外检查显存是否够用,以及请求路径是否指向 v1 接口。
如果发现模型总是忽略工具,不要立刻怀疑模型能力,先检查工具描述。描述要具体到“什么情况下调用、参数含义是什么”,很多模型对抽象描述的字面理解很弱。另外,工具数量也不宜一次给太多,一般建议控制在 5 到 8 个以内,超出后模型的选择准确率会明显下降。
9. 生产环境最佳实践与工程建议
前面讲了很多原理和示例,这里把能直接落地到生产环境的方法总结成几条建议,都是踩过坑之后才会意识到重要性的东西。
第一,永远默认 LLM 会出错,所以要把“验证”放到流程里,而不是依赖模型自觉。比如模型计算完毕,调用一个外部计算器校验;模型写出一段 SQL,先在测试库执行一次;Agent 完成某一步,用规则引擎检查输出是否包含必需字段。把验证节点嵌入流程,是抵抗“不能跳跃”最有效的手段。
第二,任务边界要清晰。LLM 的输出应该被限制在“它擅长的小通道”里:意图识别、文本生成、结构化信息抽取、自然语言到参数的转换。涉及到确定性计算、状态变更、权限操作,一律交给代码和工具。最小权限原则同样适用于 LLM:只给它的工具访问它完成当前任务所需的最小范围,不要在工具描述里写所有参数,更不要给它一个能执行任意 shell 命令的工具。
第三,可观测性要前置。Agent 和生产环境里的多工具调用,最怕黑盒。建议每轮循环都记录:输入 prompt、模型原始输出、解析出的工具调用、工具执行结果、最终回答。这个日志不只是排错用,也是评估模型效果、调整 prompt、控制成本的数据基础。没有日志,遇到一次偶发错误,你会无从下手。
第四,成本控制要结合精度和模型选择。高并发场景尽可能用小模型处理简单分类和抽取,只把复杂推理交给大模型。同一套流程里,可以用不同模型的组合:先用一个便宜小模型做初筛,再用大模型处理置信度低的问题。这比一味追求最强模型更符合工程性价比。精度选择上,线上服务要固定 bf16 或 fp16 的配置,并接受“相同 prompt 仍可能有微小波动”的现实。
第五,提示词和工具描述要当成代码来维护。建议把它们纳入版本管理,每次变更都记录 diff,并配套一组回归测试用例。这组测试用例不需要很多,但必须覆盖:直出答案的边界、分步推理的正确性、工具调用的格式、故障恢复的表现。不要只靠“这个 prompt 在我测试时表现好”就上线。
最后还要提醒一点:不要把用户输入直接拼进 system prompt,也不要把用户输入作为工具参数直接执行。提示注入和工具滥用是 Agent 场景最现实的安全威胁。合法授权、测试环境验证、备份与回滚,这些老生常谈的工程纪律,在引入大模型后不是变轻了,而是变得更重了。一个错误的工具调用,可能比一次错误的代码提交造成更大的影响。
如果这篇文章能给你留下一个最深的印象,我希望是:遇到复杂任务时,不要祈祷模型“跳”过去,而是拆开它、铺成台阶、或者接上外部工具。LLM 的边界就在这里,接受边界,才能用好边界。下一步,你可以试着把文中的最小 Agent 循环扩展成一个带工具调用的完整 demo,比如让模型查询一个本地 JSON 文件,再根据结果生成总结报告。这个练习足够小,却刚好能把“不能跳跃”这句话,从标题变成你手上的工程能力。
更多推荐
所有评论(0)