拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI工程化从零搭建:核心模块拆解与生产实战解析

AI工程化从零搭建:核心模块拆解与生产实战解析

我接触AI工程化这条线已经有几年了,从最早用大模型接口做原型验证,到现在把整套体系跑在生产环境里,中间踩过的坑、推倒重来的设计、以及沉淀下来能直接用的方法,今天一口气整理出来。这篇"ai-engineering-from-scratch"不是理论科普,而是我实际从零搭一套AI工程体系的全过程,包含数据闭环、提示工程、Agent编排、评估测试和可观测性几个核心模块,适合刚入行的AI开发者、想从调接口转向做工程化的同学,以及已经在做AI应用但觉得系统越来越难维护的团队。

1. 整体设计思路:为什么值得从零开始

从零开始搭AI工程体系,很多人第一反应是"现在工具这么多,为什么要自己造轮子?"这个话题我认真想过。库和框架确实能帮你快速跑通demo,但一旦进入生产环境,你会发现真正的问题往往不在模型本身,而在模型之外——数据对不对、提示词稳不稳定、Agent调用工具是否可靠、系统出了问题时能不能定位。这些能力,恰好是"from scratch"最大的价值所在。

1.1 从零构建的核心动机:不是造轮子,而是吃透原理

我最早做AI应用也是直接套框架,LangChain刚火的时候跟风用过一阵。demo阶段确实爽,三五天就能拼出一个带记忆、带工具的聊天机器人。但往生产推的时候问题全来了:框架升级导致行为不一致、链式调用没法加监控、提示词稍微改一点整个流程就断。后来我干脆把框架拆掉,用原生Python从底层重写,反而稳了。

这不是说框架不能用,而是当你对底层每一层都有掌控力后,框架才能成为放大器,而不是天花板。举个例子,LangChain里的AgentExecutor帮你封装了"模型推理-工具调用-结果回填"的循环,但它内部的逻辑对你是个黑盒。一旦模型输出格式稍有变化,executor可能直接抛错,你连日志都看不懂。自己写的编排器,每一步的输入输出我自己定义,出了bug十分钟就能定位。

1.2 自底向上的认知路线:先搭骨架,再填血肉

我建议的路线是这样的,按依赖关系从底层往上四层:

  • 数据与评测层:这是地基中的地基。没有一份高质量的评测集,你后面做的所有优化都是盲调。没有干净的数据管道,你的RAG检索质量就无从谈起。
  • 模型接入与提示词层:模型API封装、Prompt管理、结构化输出解析,这一层决定了模型对你需求的响应质量。
  • 编排与工具层:Agent的核心逻辑,包含工具注册、调用策略、记忆管理、多Agent协作。这是"Harness Engineering"真正发挥作用的地方。
  • 产品与观测层:面向用户的接口层,加上日志、链路追踪、成本统计和效果评估。

这个顺序不要乱。我见过很多团队上来就搭Agent框架,连评测集都没有,最后问"为什么效果不稳",只能拍脑袋猜。先花一周时间把评测集和数据管道建好,后面所有优化都有锚点。

1.3 技术选型与关键决策

语言与框架层面,我最终的选择是Python 3.11 + FastAPI,编排逻辑全部自己写,只用少量的库:OpenAI SDK(或其他模型SDK)做模型接入、Pydantic做结构化输出校验、SQLite/PostgreSQL做存储。为什么不选重型框架?因为AI工程的复杂度和传统软件不一样,它最大的变数是模型输出本身,是high variance的。把编排逻辑握在自己手里,排查问题的成本最低。

模型选型上,我走的是多云多模型策略:核心对话用GPT-4o级别的大模型,简单分类和提取用轻量模型(如GPT-4o-mini或国产开源模型),既控制成本又保证质量。后面会详细说模型路由的实现方式。

2. 核心链路拆解与技术要点

这一节是全文的重点。我按一条完整AI应用从输入到输出的链路拆开讲,每个环节的关键决策和坑都会展开,这些全是实操中验证过的方案。

2.1 数据闭环建设:比模型本身更值得投资的环节

很多人以为"AI工程"就是调模型,但真正决定应用水准的是数据基建。我在项目里搭了三条数据流,缺一不可:

第一条是评测数据集。我维护了一份大约800条用例的数据集,覆盖了正常提问、边界情况、恶意输入和典型业务场景。每条用例都标注了预期行为标准和验收标准。每次改Prompt或换模型,我都在这个集上批量跑一遍,用通过率来衡量回归情况。没有这套机制,你根本无法判断改动到底是变好还是变坏。

第二条是RAG知识库管道。我从PDF、网页、Markdown文档中抽取文本,做清洗、分块和向量化入库。这里有两个坑必须有心理准备:一是文档格式杂乱,PDF里的表格和双栏排版提取出来经常是乱的;二是分块的策略直接影响检索效果,我试过固定长度分块、按标题切分、按语义切分,最终按标题层级+滚动窗口的方案效果最好。

第三条是线上日志回流。生产环境记录用户真实query、检索结果、模型输出和用户反馈,定期导出分析,筛出失败样本补充进评测集。这形成了数据飞轮:真实使用中的问题被捕捉,变成评测集里新的用例,再驱动下一轮优化。互联网上大部分"AI应用效果越用越好"的说法,靠的就是这个飞轮,而不是模型本身会变强。

2.2 Prompt Engineering提示工程的三板斧:Few-shot、CoT、结构化输出

Prompt Engineering提示工程是AI工程里最"看得见摸得着"的环节。我的经验是,提示词的设计要遵循三个递进层次,不要一上来就堆技巧:

第一层是明确指令。告诉模型要做什么、输入是什么、输出是什么、不要做什么。这里的关键是"说人话但要说全"。比如"分析用户评论的情感倾向"太模糊,你的模型会自由发挥。更有效的写法是:"你是一个电商评论分析师,下面是用户对商品的评论,请判断情感极性为正面、负面还是中性。只输出JSON,格式:{"sentiment": "positive/negative/neutral", "reason": "一句话理由"}"。

第二层是少样本示例。给模型2-3个输入输出的对子,比你在提示词里解释一万字都管用。少样本不只是"给例子",而是给"边界例子"。我一般会给一个常规例子、一个边界例子、一个易错例子。比如情感分析,除了常规的正例,我会加一个"这个商品一般般但物流很快"的混合评价样本,告诉模型这种情况下情感极性是neutral还是以主体评价为准。

第三层是Chain-of-Thought与结构化输出。对于需要推理的任务,我会用CoT引导模型分步骤思考,但要求它在最终答案中只输出结构化结果。一个常用的结构是:要求模型先在内部做一个思考过程,然后输出结论。如果你不需要展示推理过程,可以让模型"思考"但不把思考过程放在输出里(在API参数中关闭推理字段,或直接约定只输出最终结果)。对于必须要结构化结果的场景,我借助Pydantic来校验模型输出,输出不合规就重试一次,重试还失败就走降级逻辑。

Prompt实践过程中还有个容易被忽略的点:温度参数的选择。我做一般提取任务时温度设成0,几乎不输出随机内容;做聊天和创意生成时温度调到0.7到1.0之间;做代码生成则常设0.2附近。每类任务都会有不同的最优温度区间,这个建议不要照抄别人的配置,花两个小时做几次参数扫描,收益很大。

2.3 Agent与Harness Engineering:让模型学会调工具

"Agent"这个词被用了很多年,但这里我想说一个更落地的概念:Harness Engineering。简单说,就是为模型设计一整套可调用的工具和约束规则,让模型像人一样"会用工具"而不是"背答案"。

一个Agent系统通常包含四个部分:工具注册表、推理循环、执行沙箱、反馈机制。

  • 工具注册表:用JSON Schema描述每个工具的名称、参数、功能说明。模型看到这些描述,才知道什么时候该调哪个工具。工具描述写的质量直接决定了调用准确率,描述要写清"这个工具做什么"、"什么情况下用"、"什么情况下不要用"。
  • 推理循环:模型根据用户问题和已有工具,决定是直接作答还是调用工具。每次调用工具的结果都会回传给模型,让它决定下一步动作。我实现的循环结构大致是:用户请求进入,模型推理,如果输出包含工具调用指令,则先校验参数、执行工具、将结果拼接回上下文再让模型继续推理;如果模型没有工具调用意图,则把当前输出作为最终结果返回。循环最多迭代8次,防止死循环和上下文爆炸。
  • 执行沙箱:工具的执行环境要做隔离。比如代码执行类工具必须在受限容器里跑,文件操作要限定在特定白名单目录,外部API调用要设置超时和熔断。这个不是可选项,是安全底线。
  • 反馈机制:工具执行的结果要结构化地反馈给模型,包含执行状态、返回数据、错误信息(如果需要模型自纠错)。成功的反馈写明数据摘要,失败的反馈写明错误类型,好让模型调整策略。

多Agent协作是我在后面的版本里加进来的。这里说一个非常有用的设计模式:不要搞复杂的"规划-执行-反思"循环(又慢又不稳定),改成简单的"主-从"模式:主Agent负责拆解任务和决定交给哪个子Agent执行,子Agent各自负责一个专业领域(比如代码审查、SQL查询、文档撰写),执行完把结果返回主Agent汇总。这个模式在工程上复杂度低,表现却很稳。

2.4 AI测试开发:评测、断言、回归一个都不能少

AI工程里最难也最容易被忽略的环节就是测试。传统的单元测试讲究确定性,而模型输出天然不确定,所以要有专门的测试策略。

我把AI测试分成三个层次:

第一层是LLM效果评测,也叫离线评测。前面提到的评测集在这里用上,批量跑完后自动统计指标。对于分类任务的评测集,我计算准确率和精确率/召回率;对生成式任务,我采用两种方式混合评定:规则断言(是否包含关键字段、输出是否合规JSON、长度是否合理、是否包含违禁词等)加LLM-as-Judge(用一个更强的模型给产出打质量分)。LLM作为评判官是有偏差的,所以我的做法是给评判官非常明确的打分标准,并且对每一个评测集抽样做人工复核,校准它的一致率。

第二层是集成层面的测试。Agent系统的回归测试比单次模型调用复杂得多,跑一遍完整的工具调用链路,断言最终结果对不对。这种测试不求跑得多,但求覆盖核心路径和故障注入场景。故障注入的意思是人为构造工具报错、超时、字段缺失,看Agent会不会优雅降级而不是直接崩溃。

第三层是线上可观测性。线上环境加日志埋点,把每次请求的完整链路记录下来:用户输入、检索到的文档片段(含引用id)、模型输出、工具调用中间结果、Token消耗和延迟。有了完整链路,出问题时回放一遍就能定位。后期我加了AI反馈收集,用户在用的过程中可以对回复点"赞/踩/报错",这些反馈成为评估集的补充数据来源。

3. 实操过程:从零搭起一套最小可用的AI工程流水线

说了这么多方法论,现在进入实际构建过程。我会把整个实操流程完整拆出来,包含项目结构、代码示例、关键参数的选择理由,让你看完后能直接照抄一份回来改。

3.1 项目结构:模块划分与职责界定

我最终采用的目录结构是这样的,每个模块的边界都很清楚:

ai_engineering/ ├── config/ # 全局配置与模型路由规则 ├── data/ │ ├── datasets/ # 评测集、回归集 │ └── knowledge_base/ # RAG的原始文档与向量库 ├── ingestion/ # 数据采集与清洗管道 │ ├── loader.py │ └── splitter.py ├── llm/ │ ├── client.py # 统一模型调用接口,支持多厂商 │ ├── prompts/ # Prompt模板集中管理 │ └── parsers.py # 结构化输出解析与纠正 ├── agent/ │ ├── registry.py # 工具注册表 │ ├── orchestrator.py # 推理循环(核心编排器) │ ├── tools/ # 每个工具一个文件 │ └── memory.py # 对话记忆管理 ├── backend/ │ ├── main.py # FastAPI接口层 │ └── schemas.py # 请求/响应数据结构 ├── evaluation/ │ ├── runner.py # 评测执行器 │ ├── metrics.py # 指标计算方法 │ └── judge_prompts/ # 评测官的Prompt模板 └── monitoring/ ├── logger.py # 结构化日志 └── tracker.py # 调用链路与成本统计

这个结构的核心是单向依赖原则:agent层可以调用llm层,llm层不反向依赖agent层;evaluation层依赖所有上层但要保持独立。这保证了你可以单独测试某一层,不至于牵一发动全身。

3.2 模型接入与统一客户端

先写统一模型客户端。为什么要统一封装?因为不同模型供应商的接口格式、Token计费方式、错误码都不同,统一封装后,上层根本不用关心底层是哪个模型,切换模型只改配置,代码不动。

# llm/client.py from typing import AsyncIterator import json import httpx class LLMClient: def __init__(self, provider: str, model: str, api_key: str, base_url: str): self.provider = provider self.model = model self.api_key = api_key self.base_url = base_url self.client = httpx.AsyncClient(timeout=60.0) async def complete( self, messages: list, temperature: float = 0.0, max_tokens: int = 2000, response_format: dict | None = None, tools: list | None = None, ) -> dict: payload = { "model": self.model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, } if response_format: payload["response_format"] = response_format if tools: payload["tools"] = tools resp = await self.client.post( f"{self.base_url}/chat/completions", headers={"Authorization": f"Bearer {self.api_key}"}, json=payload, ) resp.raise_for_status() return resp.json() # config中配置模型路由表 MODEL_ROUTES = { "chat": {"provider": "openai", "model": "gpt-4o", "temperature": 0.7}, "extract": {"provider": "openai", "model": "gpt-4o-mini", "temperature": 0.0}, "judge": {"provider": "anthropic", "model": "claude-3-5-sonnet", "temperature": 0.0}, }

这套封装下来,我切模型基本只花两分钟:改配置、跑一轮回归测试确认效果没有退化就上线。还有一点重要的是容错与重试。网络超时、限流(40x状态)、模型服务繁忙(503)都做了重试,指数退避加重试上限三次。实测下来能把IO异常导致的失败率从一个难以接受的水平压到几乎没有。

3.3 Prompt模板管理:像管理代码一样管理提示词

Prompt不能散落在代码里。我建了专门的prompts/目录,每个任务一个文件,用{placeholder}占位符,加载时动态填充。这样做的直接好处是:产品同学可以在不改代码的前提下调话术,工程师只需要提供参数上下文。

# llm/prompts/analyze_sentiment.txt 你是一个电商评论分析专家。请根据用户评论判断情感极性。 要求: 1. 只输出JSON,格式为:{"sentiment": "positive|negative|neutral", "confidence": 0.0-1.0, "reason": "简短理由"} 2. 不要输出任何多余文字。 3. 如果评论包含多重情绪,以主体情绪为准。 示例1: 输入:这个手机性价比很高,电池很耐用。 输出:{"sentiment": "positive", "confidence": 0.95, "reason": "用户正面评价性价比和续航"} 示例2: 输入:商品一般,但快递很快。 输出:{"sentiment": "neutral", "confidence": 0.8, "reason": "商品评价偏负但物流评价正面,整体情绪中性"} 新输入的评论: {user_input}

为什么示例里放一个"混合情绪"的例子?因为这是模型最容易出错的地方。If you give it only straightforward examples, it will default to binary classification when facing a mixed one.边界示例让模型明确"遇到混合情况怎么处理",这是Few-shot最值得花心思的地方。

Prompt的版本管理同样重要。我直接在文件名里带上版本号(analyze_sentiment_v3.txt),并在评测报告中记录每个版本在评测集上的通过率。改Prompt的完整流程是:复制一份带新版本号的文件,改动内容,在评测集上跑对比,通过率不低于旧版才允许切换。这个流程看起来繁琐,但避免了无数次"上线后发现效果还不如以前"的翻车。

3.4 Agent编排器:推理循环的核心实现

Agent编排是整个系统的心脏。我实现的编排器核心逻辑如下:

# agent/orchestrator.py class Orchestrator: def __init__(self, llm_client, tool_registry, memory, max_iterations=8): self.llm = llm_client self.tools = tool_registry self.memory = memory self.max_iterations = max_iterations async def run(self, user_message: str) -> dict: history = await self.memory.get_context(user_id=...) messages = history + [{"role": "user", "content": user_message}] tool_results = [] for step in range(self.max_iterations): response = await self.llm.complete( messages=messages, tools=self.tools.schemas(), ) message = response["choices"][0]["message"] # 判断是否要调用工具 if message.get("tool_calls"): for tool_call in message["tool_calls"]: tool_name = tool_call["function"]["name"] tool_args = json.loads(tool_call["function"]["arguments"]) # 关键:执行前做参数校验 validated_args = self.tools.validate_args(tool_name, tool_args) result = await self.tools.execute(tool_name, validated_args) tool_results.append({"tool": tool_name, "result": result}) # 将工具结果追加回消息上下文,继续循环 messages.append({ "role": "assistant", "content": message.get("content") or "", "tool_calls": message["tool_calls"], }) for tool_call, result in zip(message["tool_calls"], tool_results[-len(message["tool_calls"]):]): messages.append({ "role": "tool", "tool_call_id": tool_call["id"], "content": json.dumps(result, ensure_ascii=False), }) continue # 没有工具调用,视为最终答案 return {"answer": message["content"], "trace": messages} return {"answer": "处理超时,请简化问题或稍后重试。", "trace": messages}

有几个地方是我踩过坑后加上的,务必要注意:

第一,工具参数校验不可省。模型输出的工具参数偶尔会违反JSON Schema约束,比如少字段、类型不对。我在执行层前面加了一层基于Pydantic的校验,不合规直接返回错误信息给模型,让它自己纠正。这个方法简单但有效,把工具调用失败率降低了一个量级。

第二,循环必须设上限。我踩过的坑是模型在一个任务上反复自我纠正停不下来,白白烧钱还拖慢响应。设置8次循环上限,超过直接返回超时话术。上层再做兜底,比如推荐用户拆分问题或转人工。

第三,上下文管理要有预算。每轮工具调用都会把结果拼回消息历史,下一次请求的Token成本会膨胀。我的策略是给上下文设置预算(比如4K Token的预算给工具结果),超预算时触发压缩,把旧的消息和工具结果摘要化。摘要化用一个小模型完成,保留了关键信息但大幅缩减体积。

3.5 RAG检索:知识库的落地与调优

RAG系统我搭建了完整的过程,这里挑几个关键参数重点讲。

分块参数是最先要调的。我的文档以技术手册和产品说明为主,测试后发现按层级标题切分加滚动窗口的方式效果最好。基本逻辑是:先用文档结构把内容切成大块,每块再按窗口滑动切成小段,段与段之间保持部分重叠。重叠的作用是避免一个完整语义单元被从中间切断导致检索时信息丢失。重叠字数一般控制在块长度的10%-15%,比如块长800字,重叠大概100字。

向量模型的选择我做过对比,结论是通用向量模型够用,但在垂直领域(我这边是工程文档),领域微调后的向量模型检索精度有可感知的提升。如果不想自己做微调,用通用模型时注意一个细节:向量模型对英文和中文混合内容有差异,如果文档含大量代码和英文术语,检索结果里中英文混排的情况会有影响,需要针对性地清洗。

召回策略上,我用的不是单纯的TopK,而是"TopK+重排"。先用向量检索出Top 20候选,再让一个交叉编码器(cross-encoder)模型对候选和query做相关度打分,取Top 3-5作为上下文。这个两段式检索把精度提升了一大截。代价是多了几十毫秒延迟,对质量敏感的场景完全值得。

检索质量的一个容易被忽略细节是查询改写。用户原始query是口语化的:"上次那个接口权限的问题怎么解决来着",直接拿去做向量检索效果很差,因为RAG知识库里的文档是书面技术语言。我在检索前加了一个查询改写步骤,让模型把口语化query改写成适合检索的关键词组合,再去做向量检索。这个小改动让检索的命中率提升非常明显。

3.6 评测系统的实装:可量化的效果回归

评测系统是整套工程里花费时间最多、收益也最大的部分。我实现了三个核心组件:

runner.py是评测执行器。给定一个评测集的路径,依次执行每个用例,把模型输出收集起来。数据流是这样的:从评测集读用例,构造提示词,调用模型(支持配置多模型对比),拿到输出后传给metrics.py计算指标,结果汇总成一张报告表。

metrics.py的指标分为规则型和判定型。规则型指标包括JSON格式合规率、关键字段缺失率、输出长度、敏感词命中率、包含某必备证据的错误率等,都是硬性检查,零成本执行。判定型指标用LLM-as-Judge,提示词模板放在judge_prompts/下,每次都让judge给出分数和理由。为了防止judge对同一答案的评分不稳定,我会配置温度为零并跑两次取均值。

评测报告长这样:

用例ID输入摘要模型A模型B判定理由
EVAL-001混合情感评论错误 (neutral→positive偏置)正确 (neutral)模型A忽略了对物流的正面评价
EVAL-002JSON输出合规合规两模型均输出合法JSON
EVAL-003检索到无关文档拒绝回答基于无关文档给出错误答案模型B直接采信了错误的检索结果

看到报告后,针对失败的用例做归因是最高价值的工作。归因时特别注意两个方向:是Prompt问题(指令不够明确)、是检索问题(知识库没覆盖正确内容)、还是模型能力问题(复杂推理确实做不好)。归因对了,下一次迭代才有方向。这套追踪-归因-改进的闭环是AI工程和"调花活"的分水岭。

4. 常见问题与排查技巧实录

最后把我在实操中高频遇到的问题汇总,按出现频次排序。这些问题网上分散着各种说法,我这里全部是亲手验证过的方案,可以直接参考。

4.1 模型输出不稳定,同样的输入结果时好时坏

这个问题几乎每个人都遇到过,原因通常落在三处:

  • 温度设置过高。检查你的任务是否是生成型任务,如果不是,温度应当调低甚至设零。对于要求严格的提取和分类任务,温度0是合理选择。
  • 提示词本身有歧义。模型对模糊指令会随机解释,所以提示词要明确"做什么、怎么做、不要做什么"。我给出的建议是:写完Prompt后用5个边界用例做快速测试,哪里不满意改哪里。
  • 缺少示例。增加2-3个覆盖边界情况的示例,把"易混淆的输入"和"期望输出"写进提示词里。

排查方法很直接:固定温度并跑10次同一用例,看输出方差。方差大说明提示词或任务定义本身有问题,而不是模型"抽风"。

4.2 上下文膨胀:Agent越跑越慢,Token成本飙升

Agent系统最常见的资源黑洞就是消息历史越来越大。我实测过一个5轮对话、每轮调用2次工具的Agent,到第5轮时消息历史里的Token已经逼近模型的上限,响应延迟高到不可接受。

解决方案是实行三层退出机制:

  1. 就近裁剪:检索和工具结果只保留最近N轮,早期的工具结果直接丢弃。
  2. 摘要替换:达到预算阈值时,把整段早期对话用摘要替代,摘要保留关键实体和决策。注意不能用大模型做摘要,成本太高,用小模型或纯规则做。
  3. 循环截断:编排层的最大迭代数设上限,见前面的代码实现。

这三层退出机制配合起来,能把单次请求的Token消耗压在预算以内。

4.3 工具调用出问题,参数五花八门

模型调用工具的失败率比很多人想象的更高。常见问题包括:参数类型传错(字符串传成数组)、必填字段缺失、参数值超出枚举范围、工具名拼写错误。

我的排查思路是"防御性编程 + 喂饭式纠正":

  • 工具Schema要写详细:不仅描述这是什么工具,还要描述每个参数的格式和示例值。最好把枚举值、正则表达式都写进去,模型在生成时就可以参考。
  • 加Pydantic校验:校验失败时,不要把原始错误直接返回给模型,而是返回"缺少参数xxx,它应该是integer类型,正确格式为:..."。模型看到这个纠正信息后自行修复的成功率很高。
  • 工具逻辑要兜底:即便模型传了不合法参数,很多工具内部其实可以容忍部分错误(比如默认值兜底),不要动不动就报错。

4.4 RAG检索结果差,召回不到正确内容

检索质量差的三大原因:分块不合理、查询与文档语义差距大、向量模型不适合你的语料。排查顺序也按这个来:

第一步,直接看分块结果有没有把关键内容切断。比如一段代码块中间被硬切开,检索时根本拼不起来。

第二步,检查query和文档的语言风格差异。用户口语化提问和文档书面语在向量空间里差异很大,上文提到的查询改写方案是应对这个痛点的最直接手段。

第三步,如果前两步都没有问题,尝试换向量模型或在领域数据上微调。普通通用模型对长尾专业术语的编码能力是有限的。

4.5 LLM-as-Judge的评测结果不可信

Judge模型打分不稳定,是评测体系最大的隐患。我用过一个模型做judge,同一段输出两次打分差了一个等级,把我整懵了。

后来建立起了一套校准流程:每个评测集抽20条样本由人工打分,与judge打分做一致率对比。一致率低于85%时,先检查judge的Prompt是否清晰,再看judge模型是否过小而无法理解任务。做了两件事之后,一致率稳定超过90%:一个是给judge提供详细的评分量表(每个等级给典型样例),另一个是让judge打分时必须输出具体的理由。有了这两点,judge的判断稳定性强很多。

提示:评价标准和裁判本身对结果影响巨大,如果评测结果长期和直觉不符,先怀疑评测环节的设计,而不是模型效果。

4.6 多模型协作比单模型更难的三大隐患

跑多AI协作时会遇到单Agent没有的麻烦:

其一,重复执行:主Agent把同一个任务同时分配给两个子Agent,浪费Token,而且结果还不一致。我用编号+去重来避免:每个任务先检查是否已有同类任务在执行,有就等待结果,不重复派发。

其二,互相指责:子Agent返回错误结果时,主Agent的纠正逻辑写得不好就会变成互相推诿的循环。我的做法是子Agent在返回结果时必须附带信心分和使用的数据来源,主Agent结合这两项判断是"重试"还是"降级"。

其三,编排者瓶颈:所有子Agent的输出都要汇聚到主Agent汇总,主Agent的上下文承受了很大的压力。这时摘要机制尤其重要,子Agent返回后先压缩成关键结论再交给主Agent处理。

4.7 线上服务稳定性问题排查清单

这部分是从开发到生产的一点记录,单独列出来是因为它们每天都在发生:

  • 请求超时后怎么处理?重试一次,不行就降级返回默认值。
  • 模型服务出错,如何保证用户不感知?做一个"可用性缓冲池":多模型冗余,一个挂了自动切换备用路径(比如从一个供应商切到另一个)。
  • 延迟过高怎么优化?逐层测:模型响应耗时、检索耗时、工具执行耗时。90%的延迟问题出在模型本身和Prompt设计(生成长文比短问答慢得多),10%出在冗余的编排逻辑上。我一度发现自己的代码里有串行调两个无关工具的地方,改成并行后直接减半延迟。

排查时一定要靠日志,不要靠猜。附录里给一份我用的结构化日志字段样例,你可以直接抄:

{ "request_id": "req_20250101_abc123", "user_id": "u_999", "task": "analyze_sentiment", "model": "gpt-4o", "prompt_version": "analyze_sentiment_v3", "temperature": 0.0, "duration_ms": 832, "tokens": {"prompt": 1240, "completion": 88, "total": 1328}, "tool_calls": [], "retry_count": 0, "ok": true, "error_code": null }

日志每个环节都打,审计和成本统计靠它,出问题回放也靠它。

结束语前的经验补充

最后再分享几个我折腾完整个体系后的个人感受,不算总结,就是一些实际判断。

从零手写这套AI工程体系最直观的收益是可控性和调试效率。框架帮助你省了一个月的建设时间,但也把DEBUG的成本转移给了你——黑盒内部的错误是双倍难查。自己写虽然花时间,但每一行代码都是透明的,出了问题能快速定位。这个取舍在早期可能不明显,一旦业务复杂起来、线上问题多起来,回报是很可观的。

另外一个意想不到的收益是团队沟通的效率变高了。因为Prompt模板、评测集、日志、指标全都在一个仓库里,产品、算法、工程围绕共同的数据协作,不再各说各话。之前那种"模型效果不行"的一团迷雾,被"评测集27号用例不过、原因是因为检索结果里混了过期文档"这种精确表述取代。方向对了,团队才不会内耗。

如果你正准备开始做AI工程,我建议的第一件事不是写代码,而是花半天时间把你未来要评测的场景列出来,哪怕只有50条用例。先把评测集建起来,后面每一步优化都有了标尺。从这个标尺出发,再去搭数据管道、写Agent编排、做系统观测,整套体系会自然长出来,不需要模仿谁,也不需要套什么模板。踩过的坑我都替你标记了,动手去做的时候少走一点弯路,这篇就值了。

返回列表