过去一年,技术社区里讨论 AI 的方式发生了明显变化。前年大家还在争论大模型的能力边界,去年开始研究提示词怎么写,到了今年,越来越多的团队已经不再满足于“能跑通 Demo”,而是把 AI 当作基础设施接到业务流程中。作为长期参与 AI 应用落地的人,我能明显感觉到:真正的拐点不是某个模型参数的跃升,而是 AI 工程化能力的成熟。模型开始像数据库、消息队列一样,成为业务系统里一个可以被设计、被观测、被运维的组件。本文不打算做空泛的趋势判断,而是围绕 AI 应用开发这条主线,梳理拐点背后的技术变化,并给出一套完整可运行的最小工程示例,包含大模型接入、RAG 检索增强问答和 Agent 工具调用三个核心模块。无论你是刚开始接触 AI 开发,还是已经在做项目重构,这篇文章都能提供一套可以直接上手的思路。
1. 背景与核心概念
1.1 为什么说拐点已至
判断一项技术是否到了拐点,不是看发布会上的演示有多惊艳,而是看它是否开始进入软件工程的“常规组件清单”。2025 年之后,AI 应用开发的节奏明显加快了,背后的信号有三个。
第一个信号是模型能力的商品化。对话模型、向量模型、多模态模型都变成了可以通过标准化接口调用的服务,团队不再需要从零训练模型,而是把模型当作能力底座,把精力放在业务设计上。第二个信号是开发框架逐渐稳定。过去一年里,围绕大模型的开发范式沉淀出了几个高度一致的模式:提示词管理、上下文组装、工具调用、结果评测。这些模式正在被封装成框架和平台能力,普通开发者不需要自己造轮子。第三个信号是评测意识被普遍接受。再也不是“感觉回答得不错”就算完成,而是用测试集、指标和回归机制来度量 AI 应用的质量。
这三个信号对开发者的影响非常直接。过去决定 AI 应用质量的是模型本身,今天决定质量的更多是工程链路:数据怎么组织、提示词怎么管理、检索怎么做、工具调用怎么兜底、效果怎么评测。这条链路,就是 AI 工程实践要解决的核心问题。
1.2 三个绕不开的技术概念
在 AI 应用开发中,有三个概念出现的频率最高:大语言模型、RAG、Agent。很多讨论把三者混在一起,实际上它们解决的是不同层面的问题。
大语言模型是推理和生成的基础能力,负责理解用户的输入并生成文本。它像一个“能力内核”,但它本身不掌握企业私有数据,也无法访问外部系统。RAG 全称 Retrieval-Augmented Generation,也就是检索增强生成,核心思路是在模型生成之前,先从外部知识库中检索与问题相关的内容,把检索结果和问题一起交给模型,让模型基于这些材料来回答。这种方式能显著减少模型胡编乱造的概率,是知识库问答类应用的主流方案。Agent 强调的是任务规划与工具调用。模型不只是在对话,而是在理解任务后决定调用哪些外部工具,比如查天气、查日历、操作数据库,最后把工具结果整合成答案。
| 概念 | 核心能力 | 典型场景 |
|---|---|---|
| 大语言模型 | 文本理解、推理、生成 | 对话、写作、代码生成 |
| RAG | 外部知识检索 + 生成 | 企业知识库问答、文档助手 |
| Agent | 任务规划、工具调用、结果决策 | 智能客服、自动化办公、数据分析 |
1.3 拐点背后的核心技术变化
如果我们再往下拆一层,会发现拐点背后其实是应用架构的转变。以前的 AI 应用大多是单次调用:用户输入一句话,模型返回一句话。现在的主流应用变成了多阶段的流水线:先做意图识别,再检索数据,再组装上下文,再调用模型,最后可能还要执行动作并校验结果。
这种架构转变带来了新的设计话题,比如上下文工程。模型能接收的输入长度有限,如何把最相关的信息放进上下文、如何控制上下文规模、如何设计系统提示词,逐渐成为 AI 开发的核心技能。与此同时,AI 编程工具也改变了开发者的工作方式,Cursor 这类 AI 编程工具已经能完成大量重复性编码工作,但前提是开发者自己能设计清楚模块边界和数据结构。工具越强大,对开发者工程能力的要求反而越高。
2. AI 应用开发的关键技术栈
2.1 大模型接入的三种方式
选择怎么接入大模型,是 AI 应用开发的第一步。目前常见的方式有三种。
第一种是使用云厂商的模型 API。这种方式接入最快,模型能力通常最强,按调用量计费,适合快速验证业务想法和中小规模应用。第二种是本地部署开源模型。越来越多的开源模型已经具备了相当强的对话和工具调用能力,结合 Ollama、vLLM 等工具,团队可以在自己的服务器上运行模型,数据不出内网,长期成本更可控,特别适合数据敏感的业务。第三种是混合部署。把轻量任务交给本地小模型,把复杂推理交给云端大模型,兼顾成本、延迟和效果。
这里重点说本地部署。对很多开发者来说,用 Ollama 搭建一个本地推理环境是最快的上手路径。Ollama 本身就是一个模型运行时,它提供 OpenAI 兼容接口,意味着我们不需要修改业务代码,只要把接口地址指向本地服务,就能在各种项目里复用同一套调用逻辑。这也是本文章节中要演示的方式。
2.2 从 Prompt 到 Agent 的演进
最早的大模型应用开发,核心就是写提示词。把任务描述清楚,把示例放进去,模型就能按预期工作。但随着业务复杂化,纯提示词方案暴露出两个痛点:一是模型只具备“嘴”的能力,无法执行动作;二是面对多步骤任务时,一次提示词往往覆盖不了完整的流程需求。
于是出现了 Function Calling,也就是函数调用。模型在生成回复之前,会先判断“需要调用哪个函数、传入什么参数”,然后由代码真正执行函数,再把结果回传给模型,由模型整合输出。在此基础上,Agent 的概念被逐渐放大:一个流程里可以有很多个函数,模型自主决定调用顺序。我曾经在一个办公自动化项目里用这个模式实现了“查询库存—生成采购建议—写入审批表”的完整链路,效果比固定流程脚本灵活很多。
2.3 RAG 检索增强生成
RAG 解决的是“模型不知道”和“模型瞎编”两个问题。模型的知识截止于训练数据,企业内部的新文档、实时数据它都看不到,RAG 先用检索把相关资料找出来,让模型基于资料回答。
标准 RAG 流程可以拆成四个步骤。第一步是文档切分,把长文档切成适合检索的片段。第二步是向量化,用 Embedding 模型把每个片段转成向量。第三步是检索,把用户问题也转成向量,用余弦相似度找到最相关的片段。第四步是生成,把检索到的片段和用户问题一起交给模型。想做好 RAG,切分策略和检索质量比模型本身更关键,这也是后面实战部分要演示的重点。
3. 环境准备与版本说明
3.1 开发环境清单
在开始写代码之前,先准备一套可复现的运行环境。不同机器的软件版本可能有差异,本文以常见环境为例,重点演示配置思路,具体版本需要根据你的项目实际情况调整。
| 组件 | 用途 | 说明 |
|---|---|---|
| Python 3.10+ | 运行示例代码 | 推荐使用 pyenv 或 Anaconda 管理虚拟环境 |
| Ollama | 本地大模型运行时 | 支持 Linux / macOS / Windows |
| Docker | 可选,容器化部署 | 服务器部署更推荐 |
| VS Code 或 Cursor | 开发工具 | 建议安装 Python 插件和 AI 辅助插件 |
3.2 安装本地模型运行时
先在本地安装 Ollama。Linux 和 macOS 可以使用官方安装脚本:
curl -fsSL https://ollama.com/install.sh | shWindows 用户直接前往 Ollama 官网下载安装包。如果你的服务器上已经装了 Docker,也可以用容器方式运行:
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama安装完成后,拉取两个模型。一个是对话模型,本文示例使用 Qwen2.5 7B;另一个是向量模型,用于 RAG 的文本向量化:
# 拉取对话模型 ollama pull qwen2.5:7b # 拉取向量模型 ollama pull bge-m3 # 查看已安装模型 ollama list拉取完成后,执行ollama serve启动服务。Ollama 默认监听 11434 端口,并提供 OpenAI 兼容的接口地址http://localhost:11434/v1。你可以先用一句话验证模型是否正常工作:
ollama run qwen2.5:7b "你好,请简单介绍你自己"3.3 创建项目结构
为了让示例清晰可复制,我们使用扁平结构,一个目录就是一个完整的后端服务:
ai-assistant-demo/ ├── main.py # FastAPI 服务入口 ├── llm_client.py # 大模型对话模块 ├── rag_engine.py # RAG 检索模块 ├── agent.py # Agent 工具调用模块 ├── requirements.txt # Python 依赖 └── README.md # 项目说明三个业务模块互相独立,方便你单独抽取复用。
4. 完整实战:构建一个最小 AI 助手服务
4.1 添加依赖
先创建requirements.txt,包含 Web 框架和 OpenAI 客户端库。因为 Ollama 暴露的是 OpenAI 兼容接口,所以我们直接复用 OpenAI 官方 Python SDK,不需要额外适配。
# 文件路径:ai-assistant-demo/requirements.txt fastapi uvicorn openai pydantic安装依赖:
pip install -r requirements.txt建议创建独立的 Python 虚拟环境,避免污染全局环境。
4.2 编写大模型调用模块
llm_client.py是整条链路的基础模块。它封装了对话接口,其他模块通过调用chat()函数来获取模型回复。关键点在于,我们通过环境变量把接口地址、模型名和密钥全部配置化,这样代码既可以在本地 Ollama 上运行,也可以切换到云端模型服务。
# 文件路径:ai-assistant-demo/llm_client.py import os from openai import OpenAI BASE_URL = os.getenv("BASE_URL", "http://localhost:11434/v1") API_KEY = os.getenv("API_KEY", "ollama") MODEL = os.getenv("MODEL_NAME", "qwen2.5:7b") client = OpenAI(base_url=BASE_URL, api_key=API_KEY) def chat(prompt: str, system_prompt: str = "你是一个乐于助人的 AI 助手。") -> str: """调用 OpenAI 兼容接口完成一次对话。""" resp = client.chat.completions.create( model=MODEL, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt}, ], temperature=0.7, ) return resp.choices[0].message.content这里需要解释两个参数。BASE_URL指向 Ollama 的兼容接口,API_KEY在本地场景下可以填任意占位值,因为 Ollama 不做身份校验。MODEL必须和本地已经拉取的模型一致,否则会报model not found错误。
4.3 实现 RAG 检索增强问答
rag_engine.py实现了一个最小可用的内存版 RAG。它先把文档列表向量化,收到查询时计算余弦相似度,返回最相关的片段。这个版本没有引入向量数据库,是为了让原理更直观;正式项目可以用 Chroma、FAISS 或 Milvus 替换存储层,业务代码基本不用改。
# 文件路径:ai-assistant-demo/rag_engine.py import os from openai import OpenAI BASE_URL = os.getenv("BASE_URL", "http://localhost:11434/v1") API_KEY = os.getenv("API_KEY", "ollama") EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "bge-m3") client = OpenAI(base_url=BASE_URL, api_key=API_KEY) _doc_embeddings = [] def build_index(doc_list: list) -> None: """把文档列表转成内存向量索引。 正式项目中建议使用向量数据库存储, 这里用列表存储是为了让示例足够简单、易于理解。 """ global _doc_embeddings _doc_embeddings = [] for text in doc_list: vec = _embed(text) _doc_embeddings.append({"text": text, "vector": vec}) def retrieve(query: str, top_k: int = 3) -> list: """计算 query 与所有文档的余弦相似度,返回 top_k 条最相关内容。""" if not _doc_embeddings: return [] q_vec = _embed(query) scored = [] for item in _doc_embeddings: sim = _cosine(q_vec, item["vector"]) scored.append((sim, item["text"])) scored.sort(key=lambda x: x[0], reverse=True) return [text for _, text in scored[:top_k]] def _embed(text: str) -> list: resp = client.embeddings.create(model=EMBEDDING_MODEL, input=text) return resp.data[0].embedding def _cosine(a: list, b: list) -> float: dot = sum(x * y for x, y in zip(a, b)) na = sum(x * x for x in a) ** 0.5 nb = sum(x * x for x in b) ** 0.5 if na == 0 or nb == 0: return 0.0 return dot / (na * nb)注意retrieve()返回的是原始材料,真正组织提示词并调用模型生成答案的逻辑,放在服务入口层处理。这样检索和生成解耦,后面换数据库或换模型都很方便。
4.4 实现 Agent 工具调用
agent.py演示了 Agent 最常见的实现方式:把工具列表交给模型,模型自主判断调用哪些函数,代码执行函数后用结果继续对话。示例中提供了两个工具:一个是查询天气,一个是获取当前时间。
# 文件路径:ai-assistant-demo/agent.py import datetime import json import os from openai import OpenAI BASE_URL = os.getenv("BASE_URL", "http://localhost:11434/v1") API_KEY = os.getenv("API_KEY", "ollama") MODEL = os.getenv("MODEL_NAME", "qwen2.5:7b") client = OpenAI(base_url=BASE_URL, api_key=API_KEY) TOOLS = [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市的天气信息(示例数据)", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称,例如:北京"} }, "required": ["city"], }, }, }, { "type": "function", "function": { "name": "get_current_time", "description": "获取当前时间", "parameters": {"type": "object", "properties": {}}, }, }, ] def get_weather(city: str) -> str: """模拟天气查询,真实项目请替换为第三方天气 API。""" return json.dumps( {"city": city, "weather": "晴", "temperature": 25}, ensure_ascii=False, ) def get_current_time() -> str: return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") def run_agent(user_message: str) -> str: """让模型自主决定是否调用工具,循环直到得到最终答案。""" messages = [{"role": "user", "content": user_message}] for _ in range(5): resp = client.chat.completions.create( model=MODEL, messages=messages, tools=TOOLS, tool_choice="auto", ) msg = resp.choices[0].message if not msg.tool_calls: return msg.content or "" messages.append(msg) for tc in msg.tool_calls: fn_name = tc.function.name args = json.loads(tc.function.arguments or "{}") if fn_name == "get_weather": result = get_weather(args.get("city", "")) elif fn_name == "get_current_time": result = get_current_time() else: result = json.dumps({"error": "unknown tool"}) messages.append( { "role": "tool", "tool_call_id": tc.id, "content": result, } ) return "工具调用次数超过上限,请简化问题后再试。"这个循环是 Agent 的核心机制:模型返回tool_calls说明它想调用工具,代码执行后把结果以role: "tool"的消息追加进对话,模型再基于最新的完整上下文继续推理。循环次数限制是必要的,否则遇到复杂问题时可能陷入无限调用。
4.5 启动服务与验证
最后用 FastAPI 把三个模块串成一个 HTTP 服务。我使用lifespan机制在服务启动时初始化 RAG 索引,这样请求到达时检索数据已经就绪。
# 文件路径:ai-assistant-demo/main.py from contextlib import asynccontextmanager from fastapi import FastAPI from pydantic import BaseModel from agent import run_agent from llm_client import chat from rag_engine import build_index, retrieve @asynccontextmanager async def lifespan(app: FastAPI): # 启动时构建内存索引,方便示例直接演示 RAG build_index( [ "AI Agent 是一种能够感知环境、做出决策并执行动作的智能体程序。", "RAG(检索增强生成)在生成前先从外部知识库检索相关内容,降低模型幻觉。", "本地部署大模型通常使用 Ollama 或 vLLM,通过 OpenAI 兼容接口对外提供服务。", ] ) yield app = FastAPI(title="AI Assistant Demo", lifespan=lifespan) class ChatRequest(BaseModel): message: str use_rag: bool = False class ChatResponse(BaseModel): reply: str references: list = [] @app.post("/chat") def chat_api(req: ChatRequest): if req.use_rag: docs = retrieve(req.message, top_k=3) context = "\n".join(docs) prompt = f"请基于以下资料回答问题:\n\n{context}\n\n问题:{req.message}" reply = chat(prompt) return ChatResponse(reply=reply, references=docs) reply = chat(req.message) return ChatResponse(reply=reply) @app.post("/agent") def agent_api(req: ChatRequest): reply = run_agent(req.message) return ChatResponse(reply=reply)启动服务:
uvicorn main:app --reload --port 8000然后打开一个新的终端窗口验证接口。先测普通对话:
curl -X POST http://localhost:8000/chat \ -H "Content-Type: application/json" \ -d '{"message": "你好,请介绍一下你自己"}'再测试 RAG 增强对话:
curl -X POST http://localhost:8000/chat \ -H "Content-Type: application/json" \ -d '{"message": "RAG 能解决什么问题?", "use_rag": true}'最后测试 Agent 工具调用:
curl -X POST http://localhost:8000/agent \ -H "Content-Type: application/json" \ -d '{"message": "现在几点了?"}'如果一切正常,你会看到模型分别返回自我介绍、基于 RAG 资料生成的回答,以及通过工具获取的实时时间。到这里,一个同时具备对话、检索增强和工具调用能力的 AI 助手后端就完整跑通了。
5. 常见问题与排查思路
AI 应用开发中,报错和异常几乎不可避免。很多问题看起来五花八门,但根因往往集中在少数几个环节。下面按排查顺序整理了一张速查表。
| 问题现象 | 常见原因 | 排查与解决思路 |
|---|---|---|
Connection refused | Ollama 服务未启动 | 先执行ollama serve,确认 11434 端口可访问 |
model not found | 模型尚未拉取到本地 | ollama pull qwen2.5:7b,再ollama list确认 |
| 首次请求非常慢 | 模型冷启动加载到内存 | 提前用ollama run qwen2.5:7b "hi"做预热 |
| 回答被截断 | 未显式配置max_tokens | 在create方法中设置max_tokens=2048 |
| 显存不足 OOM | 模型参数量超过显卡容量 | 换小参数模型,或使用 4bit 量化版本 |
| 工具调用不生效 | 模型本身不支持 function calling | 查看模型文档,确认是否支持 tools 参数 |
| 检索结果不相关 | 文档切分粒度过大或过小 | 调整 chunk 大小和重叠窗口,必要时加重排 |
| 中文乱码 | 终端编码问题 | Windows 下先执行chcp 65001 |
除了表格里的问题,还有两个高频场景需要单独强调。第一个是本地模型和向量模型的版本匹配。Embedding 模型的输出维度可能不同,如果在代码里切换了模型但没清空索引,会出现维度不一致的报错。遇到这种情况,重启服务重新构建索引即可。第二个是 Agent 循环失控。虽然示例里限制了 5 次循环,但真实业务中工具输入参数可能来自用户输入,需要做参数校验和结果平滑处理,避免把异常结果直接回传给模型。
6. AI 工程化的最佳实践
6.1 用工程思维管理提示词
很多团队把提示词写在代码里,维护起来很痛苦,改一句话要重新发版。更好的做法是把提示词当作配置资产管理:系统提示词、少样本示例、输出格式说明全部放配置文件,用版本控制追踪变更。业务提示词变化频繁,应该允许运营人员在高权限管理界面中调整;涉及模型行为的核心提示词,则要经过评审再发布。提示词不只是“写得好不好”的问题,还是可维护性和团队协作的问题。
6.2 建立评测与回归机制
AI 应用的输出天然带有随机性,没有评测机制就无法控制质量。建议从第一天就建立最小评测集,哪怕只有二十条典型问题。每次修改提示词、换模型、调参数,都跑一遍评测集,观察回答是否退化。评测指标可以采用定性打分和定量指标结合的方式,定量指标包括回答准确率、格式合规率、关键字命中率等。这说起来简单,但很多团队因为怕麻烦而跳过,结果上线后出现各种概率性错误,排查成本比写评测集高得多。
6.3 安全与合规边界
AI 应用涉及的安全问题比传统后端更复杂。首要是输入侧的控制,对用户输入做敏感信息检测和长度限制,防止提示词注入。然后是权限隔离,涉及工具调用时,必须遵循最小权限原则:模型能调用的工具应该只限定在业务必要范围内,工具函数内部要对入参做校验,不能直接把数据库连接或文件系统暴露给模型。最后是输出侧的校验,尤其是生成 SQL、代码或 HTML 的场景,必须对输出内容做二次检查。这里要特别提示:涉及生产环境的数据变更操作,必须经过审批,在测试环境