☰
AI下半场应用开发指南:RAG、Agent与函数调用实践
2026/10/17 7:01:58 网站建设 项目流程

过去两年,AI 行业的主线一直是“模型”。参数规模、上下文长度、榜单分数,每隔几个月就会被刷新一次,所有人都盯着基座模型的边际进步。但到最近,行业讨论的重心明显在变:模型侧的新闻密度在下降,应用侧的叙事在升温。不是模型不重要了,而是竞争焦点开始转移——从“把模型做得更聪明”,转向“把聪明转化为可交付、可度量、可运营的业务价值”。

这就是“AI 下半场”的核心含义:上半场拼的是模型能力上限,下半场拼的是应用层的工程能力、产品能力和成本控制能力。对开发者而言,这其实是一个更友好的窗口。因为应用层不需要你从零训练大模型,你需要的是把现成的模型能力正确接进业务系统,设计好检索、工具调用、结果校验和容错机制,让应用在真实场景里稳定可用。这篇文章会把下半场应用开发的主线讲清楚:为什么叙事重心发生了转移,一个典型智能应用由哪些部件组成,以及如何从零搭建一个可评估、可上线的应用参考架构。

读完你至少能回答三个问题:第一,为什么说应用层是新的主战场;第二,RAG、Agent、函数调用这些概念之间到底是什么关系,怎么选型;第三,用一套最小但完整的代码示例,把一个带检索和工具调用的智能应用跑起来,并知道如何验证效果和排查问题。

1. 为什么说 AI 进入下半场

先给一个判断:模型能力的通用性已经跨过了“可用”的临界点,但绝大多数业务场景还没有被真正改造。上半场的核心矛盾是“模型不够强”,所以大家疯狂堆参数、堆数据、堆算力;下半场的核心矛盾变成“模型够强了,但不知道怎么稳定地用起来”。

这个转变背后有几个具体的技术信号。

第一,模型调用成本在大幅下降。无论是开源模型的本地部署,还是商用 API 的定价调整,单次推理成本相比早期已经下降了一到两个数量级。成本下降的直接后果是,过去“不敢让模型参与核心流程”的场景现在可以尝试了,比如批量内容处理、客服自动应答、代码评审辅助、报表解读等。成本结构变了,应用形态才会变。

第二,模型能力的调用方式在标准化。OpenAI 兼容的 API 接口、函数调用(Function Calling)、结构化输出、向量检索生态,这些技术让开发者不需要理解 Transformer 内部细节,也能构建出复杂的智能应用。模型本身变成了“能力基础设施”,就像数据库和缓存一样,是应用架构里的一个组件。

第三,评测和可观测性开始被认真对待。上半场评测基准是公开榜单,下半场评测基准是业务指标:问答准确率、任务完成率、成本消耗、用户留存。这意味着 AI 应用从“炫技 Demo”走向“生产系统”,必须有指标、有日志、有回滚方案。

所以,下半场的新叙事不是“换一个更大的模型”,而是“用现有模型做出更可靠的产品”。开发者要补的课,也从模型原理,转向检索增强、Agent 编排、提示词管理、评测体系、成本治理和工程运维。

2. 应用层新叙事里的核心概念

要把智能应用做好,先要理清几个容易混淆的概念:RAG、Agent、函数调用、工作流、评测。它们不是互斥的,而是不同抽象层级上的组件。

2.1 RAG:检索增强生成

RAG(Retrieval-Augmented Generation)是目前落地最广的智能应用模式。它的思路很简单:模型回答问题时,不直接凭训练记忆生成,而是先从外部知识库检索相关内容,再把这些内容作为上下文交给模型,让它基于检索结果作答。

它解决的问题很明确:模型训练数据有截止时间,不知道你的私有业务知识,也容易在专业问题上“一本正经地胡说八道”。RAG 相当于给模型配了一个“可随时更新的外挂知识库”。对比微调,RAG 不需要重新训练模型,知识更新只需要重新写入向量库,成本低、迭代快。

2.2 Agent:从“回答问题”到“完成任务”

Agent 的本质是让模型具备“行动能力”。模型不再只是输出文字,而是可以调用工具、读取数据、做出决策、执行多步骤任务。比如一个数据分析 Agent 可以拆解用户问题,调用 SQL 查询数据库,再调用绘图工具生成图表,最后汇总成报告。

Agent 的核心机制是“模型自主决策 + 工具集合”。模型根据用户目标,决定下一步调用哪个函数,观察返回结果,再决定下一步动作。这个循环通常被称为 ReAct(Reasoning + Acting)。在大模型时代,Agent 的成熟度取决于两件事:模型本身的推理能力,以及工具调用的可靠性。

2.3 Function Calling:Agent 的地基

函数调用是把模型与外部系统连接的标准方式。开发者定义好一批函数的名称、参数和描述,模型在回答时判断是否需要调用某个函数,并按照约定格式返回参数。应用层拿到参数后执行真实函数,再把结果返回给模型继续生成。

函数调用之所以重要,是因为它把“模型决策”和“系统执行”解耦了。模型只负责判断“需要什么信息、调哪个工具”,真正执行事务、查库、发请求的是经过校验的代码。这同时也是一个安全边界:不能让模型直接执行任意代码,而要通过白名单函数。

2.4 工作流:确定性优先的折中方案

很多人把工作流和 Agent 混为一谈。实际上,工作流是“预先编排好的路径”,每一步做什么是固定的;Agent 是“运行时动态决策的路径”,下一步取决于模型判断。

在生产环境里,这是一个重要的选型判断:如果任务流程固定、每一步输入输出明确,优先用工作流,因为它稳定、可控、容易排查;如果任务开放、没有固定路径、依赖复杂推理,才考虑 Agent。业界常说“能工作流不 Agent”,本质是在确定性、成本和效果之间做平衡。

2.5 评测:应用上线的“质检关”

AI 应用的评测与传统软件测试有区别。传统测试有明确的断言,AI 应用的输出是开放文本,没有唯一正确答案。因此需要一套组合评测方法:规则匹配、关键词校验、模型辅助打分(LLM-as-a-judge)、人工抽检,以及面向业务的转化率、任务完成率等指标。

没有评测体系的 AI 应用,基本不具备上线资格。因为模型输出有随机性,一次运行成功不代表重复运行成功;没有评测,你无法判断一次 Prompt 修改是变好还是变坏。

下面用一个表格总结这些概念的关系:

概念要解决的问题关键特点适用场景
RAG模型不知道私有知识检索 + 生成,知识可更新知识问答、文档解读、客服
Function Calling模型无法获取外部数据结构化输出 + 函数执行查天气、查订单、写数据库
Agent单次问答无法完成复杂任务模型自主决策 + 多步循环数据分析、自动运维、研究助手
工作流流程固定但需要 LLM 参与节点编排,确定性执行信息抽取、文档处理流水线
评测输出不确定,难以验收多维指标 + 样本集上线前把关、回归测试

3. 环境准备与前置条件

为了让后面的示例能直接跑起来,我们先统一环境。本文采用 Python 作为示例语言,因为它的 AI 生态最完整,相关库更新最快。

建议环境如下:

  • Python 3.10 或更高版本;
  • 一个支持 OpenAI 兼容接口的大模型 API。可以是云端商用 API,也可以是通过 vLLM、Ollama 等工具本地部署的开源模型。版本和具体调用地址以你实际使用的服务为准;
  • 一个向量数据库。生产线常用 Milvus、Weaviate、PGVector 等,本文示例为了降低门槛,使用 Chroma 这类轻量级方案,本地运行足够演示;
  • 一个用于文本切片的文档解析工具,示例使用 LangChain 的文本分割器,你也可以只依赖标准库自己实现分段逻辑。

创建项目目录,并用虚拟环境管理依赖:

mkdir ai-app-narrative cd ai-app-narrative python3 -m venv .venv source .venv/bin/activate pip install openai chromadb langchain-text-splitters python-dotenv

需要说明的是,这里没有写死某个库的精确版本。AI 生态迭代很快,固定版本号的意义不大,更重要的是理解接口思路。如果你在企业项目里使用,建议再配合 poetry 或 uv 锁定依赖版本,保证可复现构建。

在项目根目录创建.env文件,存放模型服务的配置:

# 文件路径:.env API_BASE=https://your-llm-endpoint.example.com/v1 API_KEY=sk-your-key EMBED_MODEL=your-embedding-model-name LLM_MODEL=your-chat-model-name

注意,不要把密钥提交到 Git 仓库。.gitignore中至少包含.env、.venv、__pycache__等条目。

从工程视角看,环境准备的真正难点不是安装依赖,而是“统一团队内的模型访问方式”。如果团队里不同成员各用各的客户端库、各配各的模型名,后续的提示词管理和评测脚本会很难收敛。建议从第一天起就约定:所有应用统一走一个封装好的模型客户端模块,模型名和 base_url 只从配置中心读取。

4. 核心流程拆解

一个典型的下半场 AI 应用,从需求到上线,通常经过六个阶段。

4.1 定义用户任务与边界

第一步不是选模型,而是把用户任务写清楚。比如“做一个客服助手”,这是一个模糊目标;拆解后应该是“客服助手只回答产品使用问题,不处理订单退款;当问题超出范围时,明确告知用户转人工”。任务边界决定了后面所有组件的设计:需要哪些工具、检索哪些文档、用什么评测指标。

这个环节最容易被忽视,但它决定了项目的天花板。边界模糊的 AI 应用,最后往往会变成“什么都想干、什么都干不好”的系统,还会带来额外的合规和安全风险。

4.2 选择应用架构模式

根据任务性质选择模式:

  • 开放式问答,如企业内部制度咨询,选 RAG;
  • 需要查询实时数据,如查库存、查物流,选 Function Calling;
  • 多步骤复杂任务,如“生成一份本季度销售分析报告”,选 Agent 或工作流;
  • 简单重复性任务,如批量提取合同关键字段,选工作流。

原则是:优先确定性方案,流程固定就不要让模型自由发挥。

4.3 设计知识库与检索策略

如果使用 RAG,检索质量直接决定回答质量。常见的流程是:文档加载 → 文本切分 → 向量化 → 写入向量库 → 查询时召回 → 重排。切分粒度、向量模型、召回数量和重排策略都值得单独调优。一个常见的误区是认为“向量模型越强越好”,实际上切分方式的影响往往更显著。

4.4 定义工具与权限边界

如果使用函数调用或 Agent,需要为每个工具写清楚函数名、参数 JSON Schema、描述文本和权限控制。工具描述写得越清楚,模型越不会误调用。权限控制遵循最小原则:工具只暴露必要的能力,不能在函数里塞入任意执行能力。

4.5 构建评测样本集

在写业务代码前,先准备 50 到 100 条评测样本,覆盖正常问题、边界问题、错误输入和拒答情况。每条样本标注期望行为。这个样本集是后续所有修改的“回归测试”,没有它,优化就会变成盲人摸象。

4.6 开发、评估与灰度上线

按前面的设计完成开发后,跑评测样本,观察指标变化,再通过小流量灰度方式上线。灰度期间对比新老版本的任务完成率、用户反馈和成本,达标后再全量。生产环境还需要设计兜底策略:模型调用失败时,是降级到固定话术,还是转人工,必须提前决定。

5. 完整示例代码实现

下面用一个“企业内部文档问答助手”作为示例,它同时用到 RAG 和函数调用,结构接近真实应用。

5.1 统一的模型客户端

先封装一个统一的客户端模块,便于切换模型和统一日志。

# 文件路径:src/llm_client.py import os from openai import OpenAI from dotenv import load_dotenv load_dotenv() _client = None def get_client() -> OpenAI: global _client if _client is None: _client = OpenAI( base_url=os.getenv("API_BASE"), api_key=os.getenv("API_KEY"), ) return _client def chat(messages, tools=None, temperature=0.2): kwargs = { "model": os.getenv("LLM_MODEL"), "messages": messages, "temperature": temperature, } if tools: kwargs["tools"] = tools return get_client().chat.completions.create(**kwargs)

这里把模型调用集中在一个函数里,后续无论是加日志、加限流、加成本统计,都只需要改这一个文件。

5.2 知识库构建与检索

第二步,把企业文档切分并写入向量库。文档可能是 Markdown、PDF 或 Word,这里用一个本地文本文件演示。

# 文件路径:src/build_kb.py from langchain_text_splitters import RecursiveCharacterTextSplitter import chromadb TEXT_FILE = "data/knowledge.txt" COLLECTION_NAME = "company_docs" text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, ) with open(TEXT_FILE, encoding="utf-8") as f: raw_text = f.read() chunks = text_splitter.split_text(raw_text) client = chromadb.PersistentClient(path="./chroma_db") collection = client.get_or_create_collection(COLLECTION_NAME) # 简单起见,这里直接使用 Chroma 内置的默认嵌入函数。 # 生产环境建议替换为专用 embedding 服务,并注意维度一致性。 for i, chunk in enumerate(chunks): collection.add( documents=[chunk], ids=[f"chunk_{i}"], metadatas=[{"source": TEXT_FILE, "chunk_index": i}], ) print(f"写入完成,共 {len(chunks)} 个文本块")

切分参数 chunk_size 和 chunk_overlap 需要根据文档特性调整。过小会丢失上下文,过大则检索精度下降。一个可行的做法是:先跑一批样本,观察召回内容的相关性,再反向调整参数。

查询时,我们从向量库召回最相关的片段,并拼装成 Prompt:

# 文件路径:src/rag_query.py import chromadb from llm_client import chat client = chromadb.PersistentClient(path="./chroma_db") collection = client.get_or_create_collection("company_docs") def retrieve(query, top_k=3): result = collection.query(query_texts=[query], n_results=top_k) return result["documents"][0] def ask(question): docs = retrieve(question) context = "\n\n".join(docs) system_prompt = ( "你是企业内部知识助手。请仅依据提供的资料回答问题。" "如果资料中没有答案,直接说明不知道,不要编造。" ) messages = [ {"role": "system", "content": system_prompt}, { "role": "user", "content": f"参考资料:\n{context}\n\n问题:{question}", }, ] response = chat(messages) return response.choices[0].message.content if __name__ == "__main__": print(ask("公司年假政策是怎样的?"))

这段代码体现了 RAG 的完整闭环:召回 → 拼装上下文 → 生成回答。关键约束在 system prompt 里:限制模型只能依据资料回答,这是控制幻觉的第一道防线。

5.3 添加函数调用能力

再给助手增加一个“查询剩余年假天数”的函数,演示 Function Calling 的接入方式。

# 文件路径:src/agent_with_tool.py import json from llm_client import chat tools = [ { "type": "function", "function": { "name": "get_remaining_leave_days", "description": "查询员工剩余年假天数,需要传入员工ID", "parameters": { "type": "object", "properties": { "employee_id": { "type": "string", "description": "员工ID,例如 E1001" } }, "required": ["employee_id"] } } } ] # 模拟的员工年假数据,生产环境应替换为真实的 HR 系统查询逻辑 FAKE_LEAVE_DATA = { "E1001": {"name": "张三", "remaining_days": 7}, "E1002": {"name": "李四", "remaining_days": 3}, } def get_remaining_leave_days(employee_id: str) -> dict: # 实际项目中,这里应通过内部接口或数据库查询,并做好权限校验 return FAKE_LEAVE_DATA.get(employee_id, {"error": "员工不存在"}) def run(query): messages = [ {"role": "system", "content": "你是企业助手,需要查询时请使用工具。"}, {"role": "user", "content": query}, ] response = chat(messages, tools=tools) message = response.choices[0].message if message.tool_calls: # 模型请求调用工具 for tool_call in message.tool_calls: args = json.loads(tool_call.function.arguments) result = get_remaining_leave_days(**args) messages.append(message) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False), }) # 将工具结果交给模型,生成最终回答 final = chat(messages, tools=tools) return final.choices[0].message.content return message.content if __name__ == "__main__": print(run("E1001 今年还剩几天年假?"))

这段代码展示的是 Agent 的最小循环:模型决定调用函数 → 应用执行真实函数 → 结果返回模型 → 模型生成最终答复。注意,工具执行发生在应用层代码,模型只负责决策“调哪个工具、传什么参数”。这是安全边界的关键所在。

5.4 简单评测脚本

最后,写一个最小的评测脚本,用规则和关键字检查回答是否达标。这个脚本可以先跑通流程,后续可以升级为模型辅助打分。

# 文件路径:src/evaluate.py from rag_query import ask test_cases = [ {"question": "公司年假政策是怎样的?", "must_contain": ["年假"]}, {"question": "今年节假日安排是什么?", "must_contain": ["节假日"]}, {"question": "怎么申请出差报销?", "must_contain": ["报销"]}, ] def evaluate(): passed = 0 for case in test_cases: answer = ask(case["question"]) ok = any(kw in answer for kw in case["must_contain"]) passed += int(ok) print(f"问题:{case['question']}") print(f"回答:{answer[:80]}...") print(f"通过:{ok}\n") print(f"评测通过率:{passed}/{len(test_cases)}") if __name__ == "__main__": evaluate()

这种基于关键字的评测很粗糙,但能作为最初的回归基线。真正的评测体系通常包含:答案相关性、忠实度(是否忠于检索资料)、完整性、格式合规性、成本和时间开销。前两项尤其适合用“另一模型打分”的方式实现,也就是 LLM-as-a-judge,用强模型给弱模型的输出质量打分,再配合人工抽检。

6. 运行结果与效果验证

按顺序执行以下命令验证整个应用流程:

# 1. 写入知识库 python src/build_kb.py # 2. 运行 RAG 问答 python src/rag_query.py # 3. 运行函数调用示例 python src/agent_with_tool.py # 4. 运行简易评测 python src/evaluate.py

如果一切正常,第一步会输出“写入完成,共 N 个文本块”,第二步会输出基于知识库生成的回答,第三步会输出“张三(E1001)今年剩余年假为 7 天”之类的回答,第四步会输出评测通过率。

判断成功的标准有三个:

  • 问答内容确实来自知识库,而不是模型自由发挥;
  • 函数调用返回了正确的数据,且格式符合预期;
  • 评测通过率达到基线要求,并记录在文档里。

如果失败,不要急着改代码。先确认环境层面的东西:API 是否能连通、模型名是否配置正确、向量库目录是否有写入权限、依赖是否完整。再按第 7 节的排查表逐条核对。

这里有一个非常重要的原则:单独一次运行成功,不代表应用是可靠的。你需要连续运行多次,或者扩大测试样本,观察输出是否稳定。如果模型输出时好时坏,问题大概率出在 Prompt 描述不清晰或检索召回质量不稳定。

7. 常见问题与排查思路

问题现象可能原因排查方式解决方案
模型调用报 401/403API Key 错误或没有权限检查 .env 配置,单独发一个测试请求更换正确的 Key,确认接口权限
回答内容与知识库无关检索召回错误打印检索到的 chunks,检查内容相关性调整切分参数,增加重排,更换 embedding 模型
模型拒绝回答问题系统提示词过于严格检查 prompt 限制条件放宽约束,或补充“可以依据资料回答”的说明
函数调用参数格式错误JSON Schema 与工具实现不一致查看模型返回的 arguments 原文修正 Schema 描述,做参数类型强校验
工具调用超时外部接口响应慢查看工具执行日志给工具调用设置超时和重试策略
回答中编造知识库没有的内容Prompt 没有明确限制依据检查 system prompt增加“资料外内容必须说明不知道”的限制
多次运行结果不一致采样温度过高或检索不稳定对比多次输出,查看 temperature 配置调低 temperature;固定检索排序
成本增长过快参考资料拼接过长统计每次调用的 token 数量压缩上下文,限制召回数量,增加缓存

这里的每一个问题,都可以在架构设计阶段提前规避。以幻觉问题为例,除了在 Prompt 中强调“依据资料”,更重要的是设计“引用溯源”:要求模型在回答时标注引用的文档片段编号,用户点击即可查看原文。这个设计既降低幻觉影响,又提升用户信任度。

8. 最佳实践与工程建议

8.1 提示词也纳入版本管理

很多人把 Prompt 直接写在业务代码里,这是应用层最容易踩的坑。Prompt 与代码耦合后,每改一句提示词都要发一次版本,且无法回滚。更推荐的做法是把 Prompt 模板抽离到独立配置,或者用专门的提示词管理工具,让 Prompt 变更走评审和测试流程。

8.2 成本治理要前置

AI 应用的开发体验和线上成本差异很大。开发时随便造测试数据,几十万个 token 不觉得什么;上线后每个用户每次会话都调用模型,账单增速会非常可观。建议从一开始就做三件事:记录每次调用的 token 消耗;对高频问答增加缓存;对超过成本上限的功能设置提醒。

8.3 可观测性分三层

智能应用的可观测性至少分三层:模型层(每次调用的 token、延迟、成本)、应用层(函数调用是否成功、检索耗时)、业务层(用户是否得到了满意答案)。三层日志要带上同一个 trace_id,才能把一次用户请求完整串起来。没有 trace 的应用,排查问题时会非常痛苦。

8.4 安全边界要明确

模型能力越强,安全边界越重要。要遵守几个原则:模型不直接执行 SQL 或代码,而是通过白名单函数间接操作;工具函数内部必须做权限校验,不能只依赖模型判断;私有数据不能进入模型训练,也不能发送到没有授权的外部服务;涉及删除、修改生产数据的操作,必须二次确认并要求人工审批。涉及任何生产环境的变更,都应该先在测试环境中验证,并准备好回滚方案。

8.5 建立回归评测机制

建议把评测脚本接入持续集成流水线。每次修改 Prompt、调整切分参数、更换模型版本时,都自动跑一遍评测集,对比新旧指标。没有回归评测的优化,本质上是在赌运气。对于有一定规模的项目,评测集最好由业务方和技术方共同标注,不要全部由开发人员自己拍脑袋写。

8.6 版本与灰度策略

模型版本会频繁升级,不要在生产环境“悄悄换模型”。推荐做法是:新旧模型并行,小流量灰度,对比任务完成率和成本后再全量切换。如果新模型在某类问题上效果下降,可以利用 Prompt 里的路由逻辑,对特定问题走旧模型。

9. 总结与后续学习方向

AI 下半场的叙事主线,已经从“模型能力竞赛”切换到“应用工程化竞赛”。模型变成了可调用的基础设施,真正的竞争壁垒来自三个地方:对业务场景的理解深度、对检索与 Agent 架构的工程运用水平、对评测和成本治理的认真程度。换句话说,当人人都能调用同一个模型时,谁能把应用做得稳定、可控、低成本、可度量,谁就掌握了下半场的主动权。

建议下一步这么做:把你手头一个真实的业务问题,按本文的六个阶段拆解一遍,定义一个最小可行场景,至少完成“知识库构建 + RAG 问答 + 评测基线”三个环节。跑通之后再考虑是否引入 Agent 或函数调用。不要一上来就搭一个复杂系统,先把一个场景做深做透。可以继续深入的方向包括:检索重排策略与召回质量分析、Agent 工具调用稳定性与防循环机制、基于模型打分的评测体系搭建、以及 LLM 应用的链路追踪与成本治理。

最后提醒一句:对任何要上生产的智能应用,先把安全边界和回滚方案写在设计文档里,再开始写代码。AI 应用与传统软件一样,出问题不可怕,可怕的是没有预案。

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

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

立即咨询