☰
LLM生产级应用:从提示词工程到RAG与Function Calling实战
2026/10/2 3:59:59 网站建设 项目流程

先讲一个我最近遇到的事。前阵子有个同事跑过来跟我吐槽,说LLM这玩意儿没用,让它写个脚本改了几轮还是错的,气得他把网页版对话框直接关掉了。我问他是怎么用的,他说:把需求复制进去,回车,把答案粘贴出来,完事。我说,问题不出在LLM身上,出在使用方式上——你还在用最原始的“人机对话”模式,当然觉得它像个只会说大话的实习生。

真正把LLM用出生产力的人,早就把聊天框扔到一边了。他们会在提示词层面做结构化设计,在API层面把模型接进业务系统,甚至用RAG和Function Calling把模型变成一个能查文档、能调接口、能自己拆任务的执行单元。这篇内容我不打算讲什么高深理论,就老老实实把我这一路用LLM踩过的坑、试过的方法、沉淀下来的套路讲清楚,给那些刚准备把LLM从“闲聊工具”升级成“生产工具”的人一个可以直接上手的路线图。

1. 先想明白你手里的LLM到底是个什么工具

很多人用不好LLM,根源在于对它有一个根深蒂固的错误认知:把LLM当成搜索引擎或知识库来用。这个误解不纠正,后面所有操作都是拧巴的。

1.1 把LLM当“搜索引擎”是最大的误解

搜索引擎的工作机制是:你输入关键词,它去全网匹配索引,返回一组“真实存在”的网页。LLM完全不是这么回事,它的本质是一个概率生成器——每生成一个词,都是它在自己学到的数万亿Token分布里,算出一个“最可能接在这个位置上的下一个词”。你看到的一整段流利的回答,其实是它一个字一个字“猜”出来的。

用个生活化的比喻:搜索引擎是一个带着文库的查字典的人,你说查什么,他去目录里找,找到了就把原文给你;LLM则是一个读过很多书、但记不清每本书具体出处的人,你开个头,他就顺着你的话头往下编,编得极其流畅、极其像模像样,但有没有编错,他自己并不知道。所以“LLM会一本正经地胡说八道”根本不是bug,而是它的底层机制必然带来的副产品。

这个认知直接影响你怎么用它。搜索引擎适合回答“事实型问题”:今天气温多少度?某公司官网是什么?LLM适合回答“模式型任务”:总结这段文字、把需求改写得更清晰、写一段符合这个模式的代码。这两者一旦搞混,你自然会觉得LLM“又菜又不靠谱”。

1.2 能力边界与Attention机制里的“三个点”

按我的实践经验,LLM真正擅长的领域大致有这么几块:文本的总结与改写、信息抽取与分类、代码生成与解释、翻译、头脑风暴和初稿起草。它不擅长的是:精确数学计算(三位数乘三位数都可能算错)、完全依赖最新信息的问答、多跳的复杂逻辑推理(超过五步就容易散)、以及需要与外部系统交互才能完成的操作。

顺便把检索词里那个很有意思的说法展开一下——“llm的token三个点:Key我是谁、Query我在找什么、Value我能提供什么”。这是在说Transformer里Attention机制的核心设计。你可以把每个Token看成一张“便利贴”,上面写着三栏:Key栏写“我是哪个词”,Query栏写“我在找跟谁相关的信息”,Value栏写“我真正携带的内容是什么”。模型每次生成下一个词时,会把新词的Query跟所有历史Token的Key做匹配,算出“它们跟我有多相关”,然后用这个相关度去加权汇总所有Value,最终决定最该生成什么。

这背后还有一个直接影响使用成本的细节:模型在处理长文本时,需要为每个历史Token保存一份Key和Value,也就是常说的KV Cache。上下文越长,KV Cache占的内存越大,推理成本越高。很多人在跑长文档时经常出现“上下文越长越慢越贵”的感受,根子就在这儿。

1.3 我把LLM的使用分成四个层级

这几年我观察下来,使用LLM的人基本分布在四个层级,你可以对照看看自己在哪。

第一层是“聊天问答层”:打开网页版对话框,有问题就问,答案拿来就用。这个层级几乎用不到任何技巧,体验也最不稳定。

第二层是“提示词工程层”:开始意识到给模型的指令需要设计,角色设定、输出格式、思维链引导,这些都是在这一层出现的玩法。效果比第一层稳定,但依然局限在“人机对话”的框架里。

第三层是“系统集成层”:开始通过API把模型接入自己的代码、业务流、自动化脚本,设置temperature、max_tokens、流式输出,让模型成为一个可被程序调用的服务。

第四层是“工作流编排层”:在第三层的基础上叠加RAG(检索增强生成)、Function Calling(工具调用)、多Agent协作,让模型不只是“回答一个单次问题”,而是参与一个完整任务的拆解和执行。

这篇文章的核心,就是第二层到第四层。第一层只能算“体验过”,从第二层开始,你才算真正“会用”。

2. 提示词工程:不是“说话技巧”,是结构化控制方法

很多教程喜欢把提示词工程讲得神乎其神,好像什么“魔法咒语”。我的看法是:别把它看得太玄,它就是一套“把任务描述转化成模型能稳定执行格式”的方法论。

2.1 角色设定的本质是约束输出分布

“请告诉我怎么优化这段代码”和“你是一位有十年经验的Python性能优化专家,请从算法复杂度和内存分配两个角度分析这段代码存在的问题,并给出修改建议”——这两条指令,模型生成的答案质量差距非常大,但原因远不只是“听起来更专业”。

从机制上讲,角色设定是在改变模型生成文本时的概率分布。当你说“你是专家”时,它在万亿Token中学到的“专业回答”相关的那部分分布会被激活,“随口闲聊”那部分的概率会被压低。这就好比让同一个人、用两种完全不同的心态去回答同一个问题:以专家身份作答时,他会自动启用更严谨的术语体系、更结构化的表达方式、更审慎的判断。LLM内部没有“身份”这个概念,但“身份”这个词会把它的输出分布往特定方向拉。

与角色设定配合使用的,还有一句同样重要的约束:“如果你不确定,请直接说不确定,不要猜测。”因为模型默认会“尽力补全”,你不给这条出口,它就硬编——这是对抗幻觉最便宜、最有效的一招。

2.2 一套可复用的三层提示词结构

我自己写生产级提示词,基本固定用一个三层结构:任务定义、输入上下文、输出格式约束。

任务定义:说清楚你要模型做什么,动作要具体。不要写“处理这段文本”,要写“从这段文本中抽取所有出现过的公司名称、金额数字和日期,并按时间排序”。动作越具体,模型跑偏的概率越低。

输入上下文:把需要处理的材料原样给到模型。注意,这里的“原样”很重要,很多人喜欢自己先“提炼”一遍再丢给模型,结果信息在中间已经损耗了。直接贴原文,让模型自己判断,效果通常更好。

输出格式约束:告诉模型答案长什么样。JSON?Markdown表格?固定字段列表?一定要写清楚。实践里我发现一个经验:当我对输出格式要求很强时,会再加一句“不要输出任何解释性文字,只输出指定格式的结果”,能大幅减少模型夹带私货的情况。

举一个我在实际项目里用过的提示词模板,业务场景是“把客服工单转成结构化记录”:

任务:你是客服工单解析助手。请从用户描述中抽取以下字段:问题分类、紧急程度、涉及订单号、用户诉求、是否需人工介入。 输入:<在这里粘贴用户工单原文> 输出规则: 1. 只输出一个JSON对象,不要输出其他任何文字; 2. 问题分类取值为:账号/支付/物流/售后/其他; 3. 紧急程度按P0-P3四级标注,P0表示资损或安全风险; 4. 如果某字段在原文中找不到,填null,不要猜测。

这套模板我用了很长时间,稳定性很高。它尤其适合后面要讲的“LLM输出接入程序”的场景——你给模型一个JSON Schema式的约束,程序就能直接解析结果,不再需要人去读阅读理解。

2.3 输出不稳定和Token估算

提示词做得再好,LLM同一个问题回答两遍,结果也可能不一样。这是采样机制决定的:每次生成都会带随机性。应对方法是用参数控制,最核心的是temperature。它控制的是模型在每次选词时的“胆量”:temperature越低,模型越倾向于每次都选概率最大的那个词,输出越稳定、越保守、越无聊;temperature越高,它越敢于选那些概率不高但可能更有创意的词,输出越多样、也越容易跑偏。

我的经验值是这样:做信息抽取、JSON输出、代码生成这类“需要确定性”的任务,temperature设在0到0.3之间;做头脑风暴、文案润色、创意改写这类“需要多样性”的任务,可以放到0.7到1.2;1.5以上基本只在纯文学创作时才用,业务场景我几乎不碰。

另一个必须会算的参数是Token。Token是模型处理文本的最小单位,不是按字数算的。英文场景下,1个Token大概等于0.7到1个词,1个词大约1.3到1.5个Token;中文场景下,1个汉字大约1到2个Token。一个常用估算公式:字符数除以3,大概就是Token数。这个数字直接决定你的API账单和上下文能塞多少内容。你给模型发一段3000字的文档,实际消耗可能超过1500 Token,多轮对话里这些Token还会被反复打包进每一次请求,所以聊天窗口越长越贵,原因就在这里。

3. API接入:从“网页聊天”到“代码里跑起来”

只会用网页对话框,LLM永远只是你的聊天搭子。真正让它进入工作流的起点,是学会调API。这一步本身不难,难点在于选模型和理解那几个关键参数。

3.1 选模型前必须看懂的几个指标

市面上的模型很多,OpenAI的GPT系列、Anthropic的Claude系列、国内的DeepSeek、Qwen、GLM,都各有特点。我在选型时主要看四个指标:上下文窗口、单位价格、推理延迟、生态兼容性。

上下文窗口决定你一次性能喂多少材料。现在主流模型基本都在128K甚至200K以上,多数场景够用。但要注意,“支持128K上下文”不等于“所有128K内容它都能高质量处理”——绝大多数模型的长文注意力都会有衰减,到了长尾部分容易忽略关键信息。所以长文档场景别指望全部塞进去,该用RAG检索的时候还是要用。

模型价格差异很大,这个不用我多说,各家官网都有计价表。我的习惯是:线上高并发业务优先选便宜的小模型,复杂推理或生成质量要求高的场景才用大模型,两者配合而不是只用一个模型。

生态兼容性这点容易被忽略。OpenAI的API格式几乎是行业标准,绝大多数开源框架、SDK、Agent工具都默认兼容它,所以当你选择一个模型时,优先确认它是否支持OpenAI兼容格式的接口。现在国内几家主流的开源模型基本都兼容了这个格式,这对做工程的人来说非常省事。

至于“open llm leaderboard 等公开榜单”,我的看法是:榜单可以当作选型前的初筛工具,看看哪些模型进入视野,但最终选哪个,一定要用自己的业务数据实测——榜单上的排名是靠通用评测集跑出来的,你的场景可能完全不在它的分布里。顺便提一句,llm wiki这类社区知识库也建议多翻翻,里面很多坑和细节是官方文档里没有的。

3.2 最小的API调用长什么样

先看一段最小可用的调用代码,我以OpenAI兼容格式为例,这是目前最通用的接入方式:

from openai import OpenAI client = OpenAI( api_key="你的API密钥", base_url="模型服务商提供的接口地址" ) response = client.chat.completions.create( model="qwen-plus", messages=[ {"role": "system", "content": "你是资深后端工程师,回答必须简洁、严谨。"}, {"role": "user", "content": "请解释一下什么是死锁,并给出一个最小复现示例。"} ], temperature=0.3, max_tokens=1024 ) print(response.choices[0].message.content)

这段代码里有几个点值得展开说。

第一个是base_url。OpenAI官方SDK默认请求的是它家的地址,但你现在用的模型服务商很可能是别的厂商,或者你部署了一个兼容OpenAI格式的本地服务,这时候把base_url换成对方提供的地址就行。这是整个“第三方API接入”的通用套路:SDK不用换,换地址和模型名就能切到另一个服务商。很多团队做多模型网关,本质上也吃的是这个兼容红利。

第二个是messages的结构。这是一个消息数组,每个元素有role和content。system是系统指令,相当于在对话开始前给模型设定行为准则;user是用户输入;assistant是模型的历史回复。多轮对话时,把聊过的所有轮次全放进messages里再发出去,模型才能“记得”上下文——它本身没有记忆,所谓的记忆,就是你每次把历史再抄一遍给它看。

第三个是max_tokens。它限制的是模型最多生成多少Token,不是它思考多久。如果你希望模型输出长答案,这个值要给够;但与此同时,生成得越长,费用越高、延迟越大。实战里我见过太多人没设这个参数,结果模型在一个“请列举所有方案”的任务里一口气输出几千个Token,既费钱又慢。

3.3 流式输出、超时和报错处理

网页版聊天为什么打字是一字一字蹦出来的?因为使用了流式输出。API默认情况下是等模型全部生成完,一次性返回给你。如果答案很长,你会看着接口转圈十几秒。加上stream=True后,SDK会持续地把已生成的片段推给你,你可以边收边展示。代码写法如下:

response = client.chat.completions.create( model="qwen-plus", messages=messages, stream=True ) for chunk in response: delta = chunk.choices[0].delta.content if delta: print(delta, end="")

注意,流式模式下每个chunk里的字段跟普通模式不完全一样,需要从delta里取内容。做聊天类产品、Copilot类工具,流式几乎是标配,否则用户体验会糟糕到像死机。

报错处理也是必须提前做的。接口调用有网络波动和限流,所以要加超时和重试。我在实践中一般设置连接超时10秒、读取超时60秒,对429限流和5xx服务端错误做指数退避重试,最多重试3次。有些同学会遇到类似“llm request failed: provider rejected the request schema or tool payload”的报错,这类问题几乎都出在请求体上:要么是tools参数里的Schema格式不合法,要么是messages里有非法字段,要么是传了当前模型不支持的参数。排查思路是:先把请求体用日志打印出来,拿原始JSON去对照API文档,逐字段排查,而不是盲目重试。

4. 把模型变成员工:RAG与工具调用组合拳

到这一步,你已经能在代码里调用LLM了。但只靠“模型自己的知识”,它做不了很多真实业务——比如回答公司内部文档的问题、给用户查实时库存、自动算一笔结算金额。这就是RAG和Function Calling上场的时候。

4.1 RAG不是“把文档塞给模型”,而是先检索再生成

很多人第一次听说RAG时,以为RAG就是“把资料传给模型让它读”。这个理解不对。如果文档超过几千字,你直接全塞进上下文,费用高、效果差,而且没法解决知识更新的问题——模型去一次的知识是死的,你不可能每次更新点资料就重新训练一个模型。

RAG的完整思路是四步:先把你自己的知识库(PDF、Word、Wiki、数据库导出的文本)切分成小块,每块几百个Token左右;把每个块转成向量(一串表示语义的浮点数),存进向量数据库;用户提问时,把问题也转成向量,和库里的所有向量算相似度,挑出最相关的几个块;最后把“用户问题+检索到的几个块”拼进提示词,一起发给LLM,让它基于这些材料回答。

这四步里最难的是切块和召回质量,而不是“调一个模型”。切块尺寸、重叠大小、按什么逻辑切(按段落、按章节还是按固定字符数),都直接影响召回效果。我自己的经验:中文文档按章节或语义段落切,块大小控制在300到500字,相邻块之间留50字左右的重叠,防止跨块的语义被切断。

一个最小可跑的RAG实现,我常用的组合是Embedding模型加Chroma向量库,代码量很小:

from sentence_transformers import SentenceTransformer import chromadb embedder = SentenceTransformer("BAAI/bge-small-zh-v1.5") client = chromadb.PersistentClient(path="./kb_store") collection = client.get_or_create_collection("company_kb") # 入库:切块、向量化、写入 chunks = split_document(doc_text, chunk_size=400, overlap=50) vectors = embedder.encode(chunks) collection.add( ids=[f"chunk_{i}" for i in range(len(chunks))], documents=chunks, embeddings=[v.tolist() for v in vectors] ) # 查询:向量化问题、检索、拼上下文 question = "离职证明需要哪些材料?" q_vec = embedder.encode([question]) results = collection.query(query_embeddings=[q_vec.tolist()], n_results=3) context = "\n".join(results["documents"][0]) prompt = f"请基于以下资料回答问题,不要编造:\n{context}\n\n问题:{question}"

这段代码跑通之后,你就拥有了一个能针对自己内部知识问答的LLM服务。在此基础上再叠加“llm wiki”的思路,把每个知识块补充上来源、更新时间、可信等级这些元信息,会让后续回答的准确性和可追溯性好很多。至于GraphRAG这类进阶形态,本质是把知识块之间的关联关系抽成图结构再做检索,适合强关联场景,初期没必要直接上,先从朴素RAG跑起来,理解透再升级。

4.2 Function Calling:让LLM替你调工具

RAG解决的是“让模型知道没有学过的最新知识”,但模型仍然不能查真实系统、不能做计算、不能执行动作。比如用户问“这个订单什么时候能送到”,模型并不知道你家系统的物流数据。Function Calling就是为此设计的。

它的工作方式很巧妙:不是“让LLM自己去调接口”,而是“让LLM决定该调哪个接口,并生成参数”。你预先告诉模型有哪些工具可用、各自需要什么参数,模型在回答时会输出一个“我建议调用工具X,参数是Y”的结构化结果,你的程序拿到这个结果后自己去执行真实的接口调用,再把调用结果回传给模型,模型基于真实结果生成最终回答。

以算运费为例,我定义一个工具:

tools = [ { "type": "function", "function": { "name": "get_shipping_fee", "description": "根据包裹重量和配送距离计算运费,单位为人民币元。", "parameters": { "type": "object", "properties": { "weight_kg": {"type": "number", "description": "包裹重量,单位kg"}, "distance_km": {"type": "number", "description": "配送距离,单位km"} }, "required": ["weight_kg", "distance_km"] } } } ]

这段Schema定义看起来有点啰嗦,但它极其重要:描述写得越清楚,模型选择工具、生成参数的准确率越高。很多“provider rejected the request schema or tool payload”的报错,就是因为tools参数里格式不对、required字段缺失或者类型定义自相矛盾。我在接第三方API时,吃过最大的亏就在这:拿了一个模型的tools定义去调另一个供应商,结果对方严格校验Schema直接拒了。所以换模型商时,tools格式一定要重新对照文档确认。

工具调用有个坑必须提醒:Function Calling生成的内容是“建议”,不是“事实”。模型可能因描述模糊而调错了工具,也可能在参数里夹带了幻觉数字。所以在执行真实接口前,你的代码必须做参数校验——比如重量不能为负数、距离必须大于零。我一直跟团队强调:LLM只负责“决策”,程序负责“校验和执行”,这层防线不能省。

4.3 多Agent编排:拆任务比一个全能Agent靠谱

当你把RAG和工具调用跑通之后,很快就会遇到一个新的瓶颈:单个模型调用难以完成复杂任务。比如“根据用户报错,定位代码问题并生成修复补丁,再补一个单元测试”——这事儿如果硬塞给一次模型调用,结果通常会崩溃在一半。

解决思路是拆。把一个大任务拆成多个小步骤,每一步由一个专门的Agent完成,步骤之间有上下文传递。我把这种模式叫作“流水线式多Agent”:一个负责解析用户意图,一个负责检索代码库,一个负责生成修改,一个负责生成测试用例。每个Agent只做一件事,任务边界清晰,模型的输出质量会高很多。

但多Agent编排有个代价:每一轮都会调用多次模型,延迟叠加、成本变高、错误也会被一级一级放大。所以我的建议是:能用单次调用解决的问题,不要上多Agent;只有任务确实需要拆解、或每一步需要不同的工具和提示词策略时,才值得上。另外,必须有总控逻辑:设定最大轮数、超时、以及对每一阶段输出做自动校验,防止Agent陷入死循环。网上聊“基于LLM的单元测试”时经常有人幻想“模型自己写测试自己验证自己修”,实测下来效率并不高——模型生成测试用例的能力不错,但测试是不是真的覆盖到了正确逻辑,还得靠人写断言或借助覆盖率工具判断。模型当执行者,监督者的角色得留给人。

在这里顺便提一句“模型网关/切换工具”的玩法。现在有Claude Code这类工具,支持通过配置切换不同的模型后端,比如接入DeepSeek、Qwen、GLM等。我实际用下来的体验是:这类工具最大的价值不是“哪个模型强就用哪个”,而是让你能在同一个工作流里做模型降级——主力模型超时或限流时,自动切到备用模型,避免业务中断。你自己搭多模型服务时也同理,统一封装一层接口,内部做路由和降级,比让业务代码直接绑定单一模型靠谱得多。

5. 上线前必须盘清楚的账:成本、延迟与质量权衡

功能跑通了,只是第一步。真正把LLM放进生产环境,你会发现最棘手的问题都不是“回答得对不对”,而是成本、延迟和质量的三角权衡。

5.1 成本不只是API价格,还有Token浪费

先看一个真实的成本测算。假设你的业务每天调用1000次LLM接口,每次请求平均消耗2000个输入Token、3000个输出Token。再假设模型价格为每千输入Token 0.014元、每千输出Token 0.028元(这是实际商用模型的大致价格区间),那一天的API费用是:

1000 ×(2 × 0.014 + 3 × 0.028)= 1000 ×(0.028 + 0.084)= 112元

一个月就是三千多元。如果你把一次请求的上下文从2000 Token提升到8000 Token,费用会成倍往上翻。所以成本控制的重点,从来不只是选便宜的模型,而是管住Token浪费。

最常见的Token浪费有两个来源。一个是给模型塞了它用不上的内容——比如把一整份几十页的合同丢进去,只问其中一条条款,检索召回完全能替代这种暴力做法。另一个是长对话里的历史消息:每轮都把全部聊天记录发一遍,聊得越久,单次请求消耗越大。应对办法是加“消息裁剪”:超过一定轮数或Token数后,把早期消息压缩成摘要,只保留最新的几轮和摘要。

5.2 延迟和质量的跷跷板

模型越大,生成质量通常越高,但推理延迟也越长。很多业务场景对延迟极其敏感——用户在线等结果超过3秒就会流失。我的经验是分层路由:第一优先用最便宜的小模型处理80%的简单请求,只有在小模型置信度不足(比如模型自己返回了“不确定”,或抽取结果为空)时,才升级到大模型做二次处理。

这个“先小后大”的思路看起来很稳,执行起来也有个坑:小模型的“不确定”表达不一定可靠,它可能不告诉你“不确定”,而是干脆给你一个错的。所以我通常会在小模型输出后加一道简单的规则校验:关键字段是否为空、输出是否符合Schema、数字是否在合理范围内。校验不通过才走大模型兜底。这样一来,平均延迟下来了,平均成本降下来了,兜底质量也有保障。

5.3 没有评估就是盲人摸象

最后一条,也是我最想强调的:LLM应用的工程质量,取决于你离不离开得了“感觉还行”这个模糊地带。我见过太多团队,改了一版提示词就说“看起来效果好多了”,上线之后又被人反馈错误百出。原因就是没有建立评估集。

做法不复杂,但需要坚持。第一步,从真实业务里攒20到50个有代表性的输入,把正确答案标好,这就是你的最小回归测试集。第二步,每次改动提示词、换模型、调参数,都拿这批数据跑一遍,记录输出、人工或自动打质量分。第三步,用LLM给自己当裁判——“llm as judge”这套玩法,让一个能力强的大模型按你定义的标准给另一个模型的输出打分,可以加速回归,但不能完全替代人工:大模型评审也有自己的偏好和幻觉,我的习惯是让LLM打分,同时人工抽检20%,两边结论偏差大时要人工复核。

这套机制看起来繁琐,但它是唯一能让你自信地说“这版改动比上版好”的方法。它跟写单元测试的逻辑完全一样:测试先于代码,评估先于上线。区别只是LLM的输出不是确定性的,所以评估集要覆盖更多样、回归的频率要更勤。

我自己在实际操作中还有一个体会:与其把希望全部寄托在“换一个更聪明的模型”上,不如先把自己的提示词结构、工具调用校验、评估回归这些基本功做扎实。模型一年比一年强,但方法论是稳定的——你把任务拆得足够细、把反馈闭环做得足够紧,LLM这个“读了万卷书的实习生”才能真正变成你团队里的生产力。先把这套体系搭起来,模型再升级,你只需要换一个更聪明的“实习生”进来,整个流水线不用重新设计。

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

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

立即咨询