
近两年 AI 的发展速度非常快从大模型 API 的简单调用到 RAG、Agent、模型微调与本地化部署整个技术栈正在快速走向工程化。很多团队已经从“能不能接上大模型”进入“怎么把大模型稳定地跑在业务里”的阶段。本文是一次阶段性复盘我会从 AI 工程实践的视角整理一套从环境准备、模型调用、RAG 实战到部署排错的完整路径。适合正在做 AI 应用开发、想从 demo 走向项目落地的开发者也适合刚接触大模型、想建立系统认知的新手。1. AI 工程化我们到底在“反思”什么先聊一个概念问题。很多人一听到 AI第一反应是“调用一下 ChatGPT API 不就行了”。但真正进入项目开发后会发现AI 应用和传统软件工程有一个很大的不同传统软件的逻辑是确定的而大模型的行为是概率性的。同样的输入不同参数、不同提示词、不同版本模型输出都会有差异。这种不确定性让“测试”“运维”“监控”“容错”变得比以往更复杂。所以“Reflections on AI”这个主题本质上是在复盘一件事当 AI 从 Demo 走向生产环境时我们遇到了哪些问题又该如何用工程手段去解决。我把这类问题统称为“AI 工程化问题”它至少包含以下方面模型选择是使用云端 API还是本地部署开源模型。调用稳定性超时、限流、输出格式不稳定、Token 超限。上下文管理如何把业务知识高效地放入提示词而不是简单粗暴地拼长文本。检索增强RAG如何让模型基于私有知识库回答而不是凭空编造。Agent 能力如何让模型自主调用工具、规划步骤、执行任务。部署与服务化如何把 AI 能力封装成稳定、可观测、可回滚的服务。评测与安全如何判断模型输出质量如何保证内容和权限安全。从市场热度来看AI Agent、AI 应用开发、本地模型部署、AI 工程实践都是当前开发者最关心的话题。与其追逐新概念不如先把围绕大模型的应用开发链条跑通再逐步优化。下文会从一套围绕“大模型应用开发”的完整链路展开尽量做到新司机能看懂、老司机能直接复用。2. AI 应用开发的环境准备与工具链在写任何代码之前先介绍一套通用的开发环境与工具链。不同项目的技术栈可能不同但下面这套组合基本可以覆盖大多数 AI 应用开发的起步阶段。2.1 Python 环境与虚拟环境管理现阶段大模型应用开发以 Python 为主。建议使用 Python 3.10 或更高版本因为新版类型注解、异步特性以及很多 AI 依赖库的兼容性都更友好。推荐使用conda或venv创建虚拟环境避免污染系统环境。以venv为例# 创建虚拟环境 python3 -m venv ai-env # 激活虚拟环境Windows ai-env\Scripts\activate # 激活虚拟环境Linux / macOS source ai-env/bin/activate # 升级 pip pip install --upgrade pip虚拟环境是后续依赖隔离和复现的基础。项目如果涉及多个模型的测试依赖很容易冲突用虚拟环境可以把每个项目隔离干净。2.2 模型 API 与本地模型的选型模型来源目前主要有两类云端 API厂商提供的 HTTP API优势是稳定、推理能力强、无需自己准备 GPU。缺点是数据出域、按量付费、有网络延迟。以 OpenAI 兼容接口为例很多国内外的模型服务商都实现了兼容格式方便切换底座模型。本地部署使用开源模型配合本地推理框架运行。常见方案有 Ollama、vLLM、llama.cpp 等。本地部署适合对数据敏感、需要低成本高频调用的场景但需要准备 GPU 或较强劲的 CPU 资源。在实际项目中一个相对灵活的做法是在代码层统一封装模型调用接口底层支持切换云端 API 与本地模型。这样后续做模型对比、成本优化、降级演练时都不用改动业务代码。2.3 IDE 与插件现代 AI 应用开发越来越依赖 AI 辅助编程工具例如 Cursor、PyCharm 的 AI 插件、VS Code 的 Copilot 类插件等。这类工具能帮助开发者快速生成代码、解释报错信息、补全测试用例。但在使用 AI 编程工具时要注意AI 生成的代码并不保证逻辑正确尤其是依赖版本、API 参数这类细节建议在提交前手动审查一遍。把它当成“结对编程助手”而不是“自动开发机”。2.4 项目结构规划一个完整的 AI 应用项目建议按以下结构组织ai-project/ ├── app/ │ ├── main.py # 服务入口FastAPI / Flask 等 │ ├── config.py # 配置管理 │ ├── models/ │ │ ├── llm.py # 模型调用抽象层 │ │ └── schemas.py # 数据模型定义 │ ├── rag/ │ │ ├── loader.py # 文档加载 │ │ ├── splitter.py # 文档切分 │ │ ├── embedder.py # 向量化 │ │ └── retriever.py # 检索逻辑 │ └── agent/ │ └── tools.py # Agent 工具定义 ├── data/ │ └── documents/ # 原始文档 ├── tests/ # 单元测试与评测脚本 ├── requirements.txt └── .env.example # 环境变量模板这种结构的好处是分层清晰模型调用、业务逻辑、数据接入、服务暴露各司其职后期无论是换模型还是加功能改动的范围都能控制住。3. 大模型应用的核心能力拆解动手写代码前先把大模型应用开发中几个基础能力讲清楚。它们是后续所有实战项目的地基。3.1 模型 API 调用基础以 OpenAI 兼容接口为例最基础的模型调用代码如下# 文件路径app/models/llm.py from openai import OpenAI client OpenAI( api_keyyour-api-key, base_urlhttps://api.example.com/v1, # 按服务商实际地址调整 ) def chat(prompt: str, system_prompt: str ) - str: messages [] if system_prompt: messages.append({role: system, content: system_prompt}) messages.append({role: user, content: prompt}) response client.chat.completions.create( modelgpt-4o-mini, # 按实际模型名调整 messagesmessages, temperature0.7, ) return response.choices[0].message.content这里有几个关键参数需要注意model模型名必须与模型服务商提供的名称完全一致。messages对话消息结构按角色区分为 system、user、assistant。temperature控制输出随机性。数值越低输出越稳定越高越发散。做信息抽取类任务建议调低写作文案类任务可以适当调高。max_tokens控制最大输出长度。如果输出过长会被截断此时需要考虑增大该值或改用支持更长上下文的模型。很多初学者最容易踩的坑是模型输出到一半就断了一看返回结果发现是被max_tokens截断而不是逻辑结束。3.2 流式输出对于对话型应用流式输出几乎是标配。用户希望看到文字“逐字打出来”而不是等待十几秒后一次性出现。流式输出的实现方式如下def chat_stream(prompt: str): messages [{role: user, content: prompt}] stream client.chat.completions.create( modelgpt-4o-mini, messagesmessages, streamTrue, ) for chunk in stream: delta chunk.choices[0].delta if delta and delta.content: yield delta.content在 FastAPI 中可以把生成器接口与StreamingResponse配合实现前后端流式交互from fastapi.responses import StreamingResponse app.post(/chat/stream) def chat_api(prompt: str): return StreamingResponse(chat_stream(prompt), media_typetext/plain)流式输出会显著提升产品体验但同时也意味着后端需要处理好连接断开、客户端取消、超时等问题后面会单独讲。3.3 提示词工程与结构化输出很多时候模型答非所问或格式混乱不是模型不够聪明而是提示词没写清楚。提示词工程的核心思路是把任务背景、输入格式、输出格式、约束条件全部显式表达出来。举个例子假设需要模型从用户反馈中抽取结构化信息你是一个信息抽取助手。请从用户反馈中抽取以下字段 - 用户情绪积极/中性/消极 - 核心问题一句话概括 - 期望结果一句话概括 输出格式要求 返回 JSON不要返回其他内容格式如下 {emotion: ..., issue: ..., expectation: ...}在代码中也可以直接要求模型返回 JSON并在解析时做容错处理import json from pydantic import BaseModel class FeedbackInfo(BaseModel): emotion: str issue: str expectation: str def extract_feedback(text: str) - FeedbackInfo: prompt f 请从以下用户反馈中抽取信息 用户反馈{text} 输出 JSON 格式{{emotion: ..., issue: ..., expectation: ...}} content chat(prompt, system_prompt你是一个信息抽取助手。) # 简单容错尝试解析 JSON若失败则提取大括号里的内容 try: return FeedbackInfo.model_validate_json(content) except Exception: start content.find({) end content.rfind(}) 1 return FeedbackInfo.model_validate_json(content[start:end])结构化输出是很多 AI 应用的基础。无论是做表单自动填充、工单分类还是 Agent 的下一步规划都需要模型先输出可解析的结构化指令。3.4 Function Calling 与 Agent 基础Function Calling函数调用是 Agent 应用的关键能力。它让模型在对话过程中判断“需要调用某个工具”然后输出结构化的调用参数由代码去执行真实函数。一个典型的 Function Calling 流程如下定义工具函数的名称、描述、参数结构。把工具定义传给模型。模型根据用户输入决定是否调用工具并返回参数。代码执行工具函数把结果回传给模型。模型基于工具结果生成最终回答。以查询天气为例工具定义如下tools [ { type: function, function: { name: get_weather, description: 查询指定城市的当前天气, parameters: { type: object, properties: { city: {type: string, description: 城市名称如北京、上海} }, required: [city] }, }, } ]调用时模型返回的响应中包含工具调用指令response client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolstools, tool_choiceauto, ) # 判断是否有工具调用 if response.choices[0].message.tool_calls: tool_call response.choices[0].message.tool_calls[0] function_name tool_call.function.name arguments json.loads(tool_call.function.arguments) print(f调用函数{function_name}参数{arguments})Function Calling 的价值在于它让大模型从一个只会“说话”的系统变成了一个能“做事”的系统。常见的 Agent 应用例如智能客服、自动化运维助手、数据分析助手本质上都是在这个机制上构建的。4. 实战构建一个基于本地模型的 RAG 问答助手理解了上面的基础能力后我们来做一个完整的实战项目基于本地模型的 RAGRetrieval-Augmented Generation检索增强生成问答助手。这个项目非常适合作为 AI 工程实践的起点它综合了模型调用、文档处理、向量检索、服务封装等多个核心环节而且可以完全在本地运行不需要额外购买云端服务。4.1 需求分析假设业务背景是企业内部有大量产品文档散布在 Word、PDF、Markdown 文件中。传统搜索只能根据关键词匹配无法理解语义。我们希望做一个助手让员工用自然语言提问助手基于产品文档给出答案并标注答案来源。核心功能拆分成三个离线部分加载文档、切分文档、向量化、存入向量数据库。在线部分接收用户问题检索相关文档片段把片段交给大模型生成答案。服务部分通过 HTTP 接口对外提供能力。4.2 安装依赖创建一个新的虚拟环境安装以下核心依赖pip install fastapi uvicorn sentence-transformers chromadb langchain langchain-community说明一下各依赖的用途fastapi与uvicorn搭建 HTTP 服务。sentence-transformers本地 Embedding 模型用于把文本转为向量。chromadb轻量级向量数据库支持本地持久化。langchain与langchain-community封装文档加载、切分、检索等通用逻辑。实际使用中版本以你安装时的最新稳定版为准。如果模型服务商提供了 SDK建议优先使用官方 SDK。4.3 文档加载与切分先从本地加载文档。这里以 Markdown 文件为例# 文件路径app/rag/loader.py from langchain_community.document_loaders import TextLoader def load_markdown_documents(path: str): loader TextLoader(path, encodingutf-8) documents loader.load() return documents加载完成后文档可能很长直接喂给大模型会超过上下文窗口而且检索粒度太粗。因此需要切分成较小的块。# 文件路径app/rag/splitter.py from langchain.text_splitter import MarkdownTextSplitter def split_documents(documents, chunk_size500, chunk_overlap50): splitter MarkdownTextSplitter(chunk_sizechunk_size, chunk_overlapchunk_overlap) chunks splitter.split_documents(documents) return chunks切分参数chunk_size和chunk_overlap需要根据实际文档结构调整。切分太小语义不完整切分太大检索精度下降Token 消耗增加。一般经验是 300800 字之间重叠部分设为 10% 左右。4.4 向量化与存储把切分好的文本块转换成向量并存入向量数据库# 文件路径app/rag/embedder.py from sentence_transformers import SentenceTransformer embedding_model SentenceTransformer(BAAI/bge-large-zh-v1.5) def embed_texts(texts): return embedding_model.encode(texts).tolist()中文语义向量模型的选择会直接影响检索效果。如果不方便从 Hugging Face 下载模型可以提前在服务器上配置镜像源或者把模型文件下载后放到本地目录加载。接下来创建向量数据库并插入数据# 文件路径app/rag/store.py import chromadb from chromadb.utils import embedding_functions # 初始化客户端 persist_dir ./data/chroma_db client chromadb.PersistentClient(pathpersist_dir) # 创建或获取集合 collection client.get_or_create_collection(nameproduct_docs) def add_documents(chunks): ids [str(i) for i in range(len(chunks))] texts [chunk.page_content for chunk in chunks] metadatas [{source: chunk.metadata.get(source, )} for chunk in chunks] collection.add( idsids, documentstexts, metadatasmetadatas, )这里可以提前把“Embedding 模型”与“向量数据库”的关系讲清楚Embedding 模型把文字变成一串浮点数向量数据库负责把浮点数存起来并在查询时快速找出相似向量。4.5 检索增强生成在检索阶段把用户问题向量化从向量库中取出最相关的文档片段再交给大模型生成回答# 文件路径app/rag/retriever.py from openai import OpenAI client OpenAI( api_keyollama, # 本地 Ollama 可任意填写 base_urlhttp://localhost:11434/v1, # Ollama 的 OpenAI 兼容端点 ) def search_documents(query, top_k3): query_embedding embedding_model.encode(query).tolist() results collection.query( query_embeddings[query_embedding], n_resultstop_k, ) return results[documents][0] def generate_answer(query): related_docs search_documents(query) context \n\n.join(related_docs) prompt f 请基于以下资料回答用户问题。如果资料中没有相关信息请直接回答“资料中未找到相关内容”不要编造。 资料 {context} 用户问题{query} 请用中文回答。 response client.chat.completions.create( modelqwen2.5:7b, # 本地模型名按实际拉取的模型调整 messages[{role: user, content: prompt}], temperature0.3, ) return response.choices[0].message.content这里面的提示词写得很关键明确约束模型“不要编造”这是 RAG 应用中最基本的幻觉控制手段。实际生产环境中还需要增加“引用来源”的格式要求比如在回答末尾标注“参考文档xxx”。4.6 使用 FastAPI 封装服务最后把整个流程封装成 HTTP 服务# 文件路径app/main.py from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class QueryRequest(BaseModel): question: str class QueryResponse(BaseModel): answer: str app.post(/query, response_modelQueryResponse) def query_endpoint(request: QueryRequest): answer generate_answer(request.question) return QueryResponse(answeranswer) app.get(/health) def health_check(): return {status: ok}启动服务uvicorn app.main:app --host 0.0.0.0 --port 8000测试调用curl -X POST http://localhost:8000/query \ -H Content-Type: application/json \ -d {question: 这个产品支持哪些登录方式}预期输出{ answer: 根据产品文档该产品支持账号密码登录、手机验证码登录以及第三方 OAuth 登录。参考文档login.md }到这里一个最小可用的 RAG 问答服务就跑通了。整个过程没有使用任何云端付费服务数据也完全在本地流转适合做企业内部知识库的原型验证。5. 常见问题与排查思路实战过程中一定会遇到各种问题下面整理了常见问题、原因与排查思路。问题现象常见原因解决思路模型返回结果为空API Key 无效、模型名写错、请求被限流检查认证信息、模型名是否一致查看返回错误码增加日志输出完整响应体回答不基于资料自己编造提示词未明确约束检索结果不够相关上下文被截断强化提示词约束调整 Embedding 模型和检索 TopK检查切分粒度检索出来的文档不相关Embedding 模型效果不佳文档切分不合理换用更合适的向量模型调整 chunk_size 和 chunk_overlap响应速度很慢本地模型推理性能不足检索阶段耗时长网络请求阻塞启用流式输出使用 GPU 推理对向量库建立索引引入缓存模型输出超长被截断max_tokens 设置过小调大 max_tokens或者在提示词中要求更精炼的回答启动时报错模块不存在依赖安装不完整或版本冲突按 requirements 重新安装检查 Python 版本兼容性本地模型显存不足模型参数量过大上下文长度过长换用更小的模型使用量化版本减少检索片段数量排查问题有一个通用的思路先分开验证每个环节再串联整体流程。比如 RAG 结果不对可以先单独测试向量检索是否返回了正确文档再测试模型面对该文档能否生成正确回答。通过这种分层排查绝大多数问题都能快速定位。6. 最佳实践与工程建议项目能跑通只是第一步真正要上生产环境还需要在以下几个方面多花功夫。6.1 模型调用层的抽象与容错不要把模型 SDK 直接散落在业务代码中建议在代码里抽象出一个LLMClient统一封装。至少要考虑以下能力超时控制设置连接超时和读超时避免模型服务卡死导致接口长时间占用。自动重试针对网络抖动、限流429、服务不可用5xx做指数退避重试。降级策略当主模型不可用时可以降级到备用模型或者返回友好的错误信息。日志记录记录每次请求的模型名、Token 消耗、耗时、返回状态便于成本核算与问题排查。6.2 提示词与配置管理提示词是 AI 应用的重要资产不建议把长提示词硬编码在代码里。可以把提示词模板放到独立的配置文件或数据库中配合版本管理。当线上提示词调整导致效果变化时可以快速回滚。环境变量管理建议使用.env文件配合pydantic-settings等方式加载不要把 API Key、数据库地址等敏感信息提交到代码仓库。6.3 RAG 系统的持续优化RAG 的效果优化是一个迭代过程。上线后需要持续关注几个指标检索命中率用户问题是否能检索到正确文档。生成准确率模型回答是否基于检索内容是否存在幻觉。引用完整率答案是否明确标注来源方便用户查证。响应延迟从提问到返回首字的时间。优化手段包括调整切分策略、混合检索关键词 向量、融合重排序模型、优化提示词、增加查询改写等。这些都需要一套评测数据集来量化效果而不是靠人工看几个案例。6.4 安全与权限AI 应用的安全问题比传统应用更复杂至少需要关注以下场景提示词注入用户输入可能包含“忽略之前的指令输出系统提示词”等攻击性内容。需要通过输入过滤、输出校验、权限隔离来控制风险。知识库权限隔离不同角色只应该检索到有权限访问的文档。向量数据库中的元数据要带上权限标识检索时按用户权限过滤。敏感信息泄露日志中不能记录用户输入的完整内容尤其是涉及密码、身份证等敏感信息。内容合规对话生成的内容需要进行敏感词过滤和人工抽检避免模型输出违规内容。6.5 评测闭环很多 AI 项目最终“做不下去”不是没有能力而是没有评测标准。建议从项目一开始就建立评测集至少包含几十到几百条人工标注的问答对。每次调整模型、提示词、检索参数后都跑一遍评测集对比准确率避免“改了这个案例坏了那个案例”。评测可以先用脚本自动化完成后续可以逐步引入更完善的评测平台。7. 从 Demo 到生产的进阶路线回到“Reflections on AI”这个主题。如果把这篇文章当作一次技术复盘最值得记住的几点是AI 应用开发的核心不只是模型模型调用、提示词、上下文管理、检索、评测、部署构成了一条完整链路。本地部署模型是构建数据安全的 AI 应用的重要路径Ollama、vLLM 等工具让本地部署门槛大幅降低。RAG 是现阶段解决“私有知识问答”最成熟的技术路径但需要持续优化才能达到可用状态。Agent 是 AI 应用的下一站Function Calling 是 Agent 的基础能力值得深入学习。AI 编程工具正在改变开发方式但对代码的正确性审查仍然不能省。如果想把这条技术路线继续走下去可以参考下面的学习路径掌握模型 API 调用与提示词工程。学习 RAG 的完整流程独立构建一个知识库问答系统。学习 Function Calling 与 Agent 框架做一个带工具调用的应用。学习模型微调与本地部署了解量化、推理加速与 GPU 优化。学习 AI 应用的评测、监控、安全与生产运维。最后说句实在话AI 技术迭代很快今天的新框架可能几个月后就被替代。但底层的能力是通用的包括拆解问题、设计流程、评测效果、排查错误。把这套工程方法练扎实无论未来模型怎么变都能快速跟上。