1. AI工程不是调API:先弄清楚它在解决什么问题
过去两年,我见过太多从传统开发切过来的工程师,拿到大语言模型API的第一反应是"这不就是个接口吗,传参数拿返回值而已"。然后他们很快发现,一套基于大模型的系统,真正难的不是把模型跑起来,而是让它在各种边界场景下稳定、可控、可迭代地输出业务结果。这就是我理解的AI工程(AI Engineering from Scratch)的起点——从零开始,围绕概率性的模型能力构建确定性的工程系统。
AI工程和传统软件工程之间最核心的分水岭,在于你面对的对象不再是一段行为完全确定的代码,而是一个"每秒钟都可能给出不同答案"的智能体。传统工程里,你写if x > 0,x只要是正数就永远走这条路;而在AI工程里,同样的Prompt,同样的输入,两次调用的输出可能完全不同。这种不确定性不是bug,而是模型的固有属性。工程化要做的,恰恰是在这种不确定性的前提下,通过数据、约束、评测、兜底策略,把整个系统拉回到可控的轨道上。
所以AI工程涵盖的范围远不止"写Prompt"或者"调模型"这么简单。我个人的定义是:AI工程是一套围绕模型能力构建的生产体系,包括数据准备、模型选型、提示词编排、工具调用(也就是现在很热门的Harness Engineering)、输出校验、评测回归、监控告警,以及成本和延迟的持续优化。你把这些环节全部串起来,才谈得上一个真正能交付、能维护、能演进的AI项目。这篇文章,我就围绕这条主线,把从零构建AI工程能力的过程拆开讲清楚。
这篇文章适合两类人:一类是从传统后端或前端转过来的开发者,想系统补全AI工程的知识拼图;另一类是已经在用API做Demo,但项目一上生产环境就崩、一有边缘情况就翻车、不知道如何系统化迭代的实践者。我会尽量少讲抽象理论,多给可直接落地的做法和我在一线踩过的坑。
2. 打通第一公里:编程基础、数学底子和工具链的取舍
2.1 Python能力要练到什么程度才算"够用"
很多教程告诉你"学AI工程要先精通Python",但"精通"这个词太模糊了。我实际工作下来,真正高频用到的Python知识其实是有边界的,你按这个清单去查漏补缺,比漫无目的地刷语法书高效得多。
首先是类型标注和Pydantic类的使用。AI工程里,模型的输出需要被严格解析成结构化对象,类型标注不只是给人看的注释,更是运行时数据校验的第一道防线。我几乎所有的模型输出解析都依赖Pydantic v2,它能自动帮我做字段校验、类型转换、嵌套结构解析,配合大模型的JSON输出模式,能把脏数据挡在业务逻辑之外。
其次是异步编程。真实项目里,你要并发调用多个模型、批量处理数据、做流式输出,asyncio和httpx的异步客户端是必备技能。我见过不少同事用同步循环一个一个调模型,100条数据等十分钟,换成异步批量之后十几秒搞定。这个差距在生产环境中就是天壤之别。
然后是装饰器、上下文管理器、日志模块的熟练运用。这些是构建可观测系统的砖瓦。比如我会用一个@retry装饰器统一封装模型调用的重试逻辑,用自定义的log_context管理每次请求的trace_id,追踪一次用户请求从入口到模型再到工具返回的全链路日志。没有这套东西,线上出问题你根本不知道是哪一步在捣乱。
最后是工程习惯:虚拟环境管理(我推荐uv,速度和体验都远好于传统方案)、版本控制(不只是代码,还有Prompt和评测数据集的版本)、以及基础的shell和Git操作。这些看着不起眼,但恰恰是"从零到生产"最容易绊倒人的地方。
2.2 线性代数、概率和统计的"够用清单"
很多想入门的朋友被"数学基础"劝退,其实AI工程日常用到的高等数学,远没有想象中那么多。我自己整理了一个够用清单,基本覆盖日常工作的需求:
线性代数方面,把向量和矩阵的点乘、矩阵乘法、余弦相似度搞懂就够起步了。因为模型API返回的embedding向量,你需要计算两个文本的相似度、做向量检索,这些都是基础的向量运算。函数和导数的直观理解(知道梯度是"函数上升最快的方向")也有助于理解模型训练和微调,但如果你不打算从零训练模型,这部分不需要深挖。
概率统计方面,需要理解条件概率和贝叶斯定理的基本思想。做RAG检索时,"如何权衡检索结果的置信度"、做Agent时"如何判断工具调用是否成功",本质都是概率判断。更重要的是理解方差和分布:模型输出的波动方差、评测指标采样的置信区间,这些概念决定了你能否科学地判断"这次改动到底是变好了还是只是运气好"。
我的建议是:不要去买一本厚厚的教科书从头啃。先用最小可用的数学知识往前走,遇到具体问题再针对性补充。AI工程的核心难点从来不是数学推导,而是系统设计和工程取舍。
2.3 LangChain到底要不要学:我的真实看法
现在一提AI工程,很多人的第一反应是学LangChain或LlamaIndex。我的观点可能和一些教程不一致:初期不要直接上框架,先用原生代码把每个环节跑通,再引入框架提升效率。
原因有几个。第一,框架的抽象层级高,它帮你封装了Prompt模板、工具调用、记忆管理等逻辑,但封装的越多,你对底层机制的理解就越模糊。一旦框架版本升级导致行为变化,或遇到框架没覆盖的边缘场景,你就会束手无策。第二,AI工程的核心调试点(比如Prompt质量、输出解析、工具调用的失败恢复)恰恰是框架最薄弱的环节,如果你不懂底层逻辑,在这些地方只能对着黑盒猜。
我的学习路径建议是:先用原生HTTP或openaiSDK实现三轮调用流程——基础对话、结构化输出解析、工具调用;当你理解了每一步发生了什么,再去看LangChain等框架怎么抽象这些环节,自然就能判断哪些能力该用、哪些该绕过。等进入生产阶段,我自己通常还会保留一套原生核心逻辑,只在非关键路径上使用框架能力,这是后话。
3. Prompt、Harness、数据、评测与测试:五个核心组件的拆解
3.1 Prompt Engineering:把业务需求翻译成模型能执行的指令
我从零开始搭建AI工程时,第一个集中攻克的点是Prompt Engineering,它也是几乎所有热词搜索的起点。这里的核心哲学是:模型不像人那样能理解"你看着办"式的模糊需求,你必须把上下文、约束、输出格式、兜底行为全部显式写清楚。
一个可复用的Prompt结构,我通常拆分成六块:角色设定、任务描述、输入数据、输出格式、边界约束、示例。角色设定让模型选择合适的语体和行为模式;任务描述要写清楚"做什么、为谁做、产出什么";输入数据放在独立的界定符里头防止Prompt注入;输出格式最好用JSON Schema或模板明确锁定;边界约束要写明"什么情况下回答不知道、什么情况下拒绝回答";示例通常给两到三个,能显著提升输出质量的稳定性。
我日常实践中积累的三个关键要点:第一,少样本示例的价值往往大于对任务描述的长篇大论,三五个高质量示例比一大段抽象描述管用得多;第二,指令要写"要做什么",更要写"不要做什么"——比如"不要编造日志中不存在的信息"这类否定性约束能明显降低幻觉率;第三,把动态数据与指令模板分离,变量只占据固定位置,这不仅方便维护,还能避免用户输入中的干扰信息污染指令本身。
关于思维链和推理能力,现在流行的做法是让模型在回答前先展示思考过程(Chain of Thought),这在复杂的多步推理场景下确实有效。但要注意,如果你用的是带隐藏推理能力的API,通常不需要在Prompt中显式要求"请一步一步思考",模型自己就会在内部推理。实践中我倾向于在输出格式上要求"先给简要推理依据,再给结论",既能保留可解释性,又不破坏输出的结构化。
3.2 Harness Engineering:真正让模型进入系统的骨架
行业内新近讨论较多的Harness Engineering,指的是构建在模型调用之外的"马具"层——所有让模型接入外部世界的机制。类比来说,模型是一台高性能发动机,Harness就是离合器、变速箱、刹车和仪表盘,它决定了这台发动机能否平稳地驱动整车。完整案例里,Harness通常包含:结构化输出控制、工具定义与调用、上下文管理、错误恢复、以及安全护栏。
结构化输出是Harness的基石。我给所有生产级模型调用都启用JSON输出模式(OpenAI的response_format参数或厂商的等效能力),配合Pydantic做二次校验。这样模型输出的内容在进入业务逻辑前,已经被双重确认过格式和类型。工具调用(Function Calling)是Agent能力的核心:定义工具时,描述必须极其精确,包括参数名、类型、枚举、必填项和"什么时候才应该调用这个工具"的条件说明。模型决定调用工具之后,你的代码要负责执行真实操作,并把执行结果传回给模型做下一步决策。
上下文管理也是Harness的关键环节。一次真实Agent任务可能有多轮工具调用,每一步产生的中间信息都要被组织成"可追加、可截断、可压缩"的上下文结构。我会实现一个简单的消息管理器,按角色和来源区分消息,根据总token数触发老消息压缩策略(摘要或删除工具调用细节),确保最关键的当前任务信息始终在上下文窗口内。错误恢复方面,模型可能在工具调用时给出不存在的函数名或错误参数,Harness必须捕获错误、格式化报错信息再交回模型修正,而不只是崩溃退出。
安全护栏和兜底策略同样是Harness的职责:设定用户的权限边界、给工具调用添加审批环节、对模型的输出做敏感词和合规过滤、在模型连续多次失败时切换人工兜底流程。这套工程骨架才是Agent能够稳定完成多步任务的真正秘诀。
3.3 数据:高质量的输入决定高质量的输出
AI工程里最容易被低估的组件是数据。这里说的数据不只是训练数据,更包括运行时输入数据的准备工作。我自己的数据工程流程分为四层。
第一层是数据采集,就是把业务相关的文本、日志、文档、对话记录从各源系统同步到统一存储,这步要关注接口权限和数据量预估。第二层是清洗与去重,文本中常见的乱码、HTML标签、重复段落要提前处理,一个常见坑是采集的文档里混杂了大量页面导航信息,不清理的话模型会被这些噪声带偏。第三层是切分与入库,如果要做RAG,需要按语义粒度把长文档切分成合适块(我常用500到800字符加重叠区),再用embedding模型向量化写入向量数据库,这里每月都要关注切分策略是否适合当前文档类型——技术手册、合同条款、聊天记录各自适合不同的切分粒度。第四层是运行时数据增强,比如在检索前做同义改写、在回答生成时引用检索来源、在评测时构造反例输入。
关于数据版本管理,这一点多说一句。AI工程和传统工程的重大区别是,模型的输出质量是数据和Prompt的耦合产物。你改了一条数据,可能导致之前通过评测的用例回归。所以我会用类似DVC的工具或至少用Git管理所有数据的版本,每次变更都记录对应的模型版本和Prompt版本,形成可回溯的"数据-代码-模型"三元组。这在后续定位线上问题时能省下天大功夫。
3.4 评测:没有评测体系,AI工程就是玄学
我在多个项目里反复强调一个结论:AI工程是做"精度工程",本质是在概率模型上构建确定性系统。而要做到这一点,评测体系不是锦上添花,是地基。没有量化评测,你根本无法判断一次Prompt修改是优化还是劣化,也无法向团队或客户证明系统的能力边界。
评测集的建设从领域内真实命中样本入手。我通常会从历史日志、用户真实反馈、以及人工构造的边界案例中抽几百条样本,形成一个Golden Set(黄金评测集),每条样本标注期望输出或评分维度。这个集合要覆盖常见场景、边界场景、异常输入、恶意输入四个层次。规模不用一开始就大,但质量必须高,因为它是后续一切迭代的标尺。
评测执行分两层。第一层是自动化指标,适合有明确事实答案的任务:可以用答案匹配率、Rouge分数、语义相似度、JSON Schema通过率、工具调用成功率等客观指标。第二层是模型辅助评测(LLM-as-Judge),适合主观性强的任务:用一个更高能力模型对输出进行打分,但要注意Judge模型自身的偏差,我会用多维打分(相关性、忠实度、完整性、格式合规)并多次采样取平均,降低随机性。我还会定期抽一批结果做人工复核,校正自动指标和Judge的偏差。
最能体现"没有评测就是玄学"的例子是:"我的系统升级了一个模型版本,看起来回答质量提升了,但上线后发现特定类型的请求全面崩坏了。"这种情况几乎无法绕过评测集提前发现——只有当你有一组固定样本、固定的评分维度、以及"升级前必须过评测评测线"的硬性门禁,才能避免这种"看起来变好、实际局部退化"的陷阱。
3.5 AI系统的测试:与"确定性软件测试"完全不同
传统软件的单元测试是"输入确定、输出确定",而AI系统的测试策略必须适配概率性输出的特点。我在团队里推行的测试分层如下。
单元层面对函数做确定性测试——数据清洗函数、输出解析器、工具执行器这些纯代码逻辑照常写断言即可。集成层面,把模型调用封装成"可控依赖",测试时用一个Mock响应器返回预定义的不同版本模型输出(正常、缺字段、格式错误、恶意内容),验证你的Harness层能否正确处理。这一层能拦截大多数"模型输出稍微异常就崩"的问题。端到端层面,从黄金评测集里抽出冒烟用例,每次部署前自动跑一遍,设置通过率红线(比如95%),不达标就阻断发布。
这里要特别关注概率性带来的"假阳性通过"问题。模型输出有随机性,一次通过不代表永远通过。我的做法是:关键用例重复执行三次取多数投票结果;对必现项(比如"涉及个人信息时必须脱敏""输出必须符合JSON格式")写独立专项断言,不依赖模型的泛化能力;线上持续收集低置信度输出并转人工复核,反哺测试集和评测集。AI系统的测试,本质上是"代码测试 + 数据测试 + 模型行为测试"三者的结合,远不是跑一个pytest就够了。
4. 从零落地一个AI日志分析助手:完整工程实录
4.1 选一个合适的起点项目:日志分析助手
理论学习再多,不如动手做一个完整项目。我推荐从"AI日志分析助手"这个方向入手,因为它业务边界清晰、数据容易获取、评测也相对客观——日志里是否有异常、异常出现在哪一行,答案是明确的。我下面把整个构建过程拆开讲,包括选型理由、代码结构、踩坑点,你可以照着复现。
第一步明确需求:输入一段原始日志文本,系统要做三件事——检测日志级别和异常类型、提取关键字段(时间戳、服务名、错误码、IP等结构化信息)、给出处理建议。选Python写核心逻辑,数据库直接用SQLite即可,向量检索可以留着第二阶段再加。选这个项目的原因是:你不需要先解决"用户意图理解"这个最难的问题,而能把注意力集中在Prompt编排、输出解析、评测构建这些AI工程基本功上。
4.2 从Prompt到Harness的完整实现
核心代码我给出一个精简但生产可用的版本。首先定义输出结构,用Pydantic模型锁定:
from pydantic import BaseModel, Field from typing import List, Optional class LogEntry(BaseModel): level: str = Field(description="日志级别:DEBUG/INFO/WARN/ERROR/FATAL") service: Optional[str] = Field(default=None, description="服务名,无则null") error_code: Optional[str] = Field(default=None, description="错误码,无则null") anomaly_type: str = Field(description="异常类型:无/超时/连接失败/空指针/资源耗尽/其他") key_fields: dict = Field(default_factory=dict, description="其他关键字段") suggestion: str = Field(description="针对该日志的处置建议,不超过50字")然后构建Prompt模板,把日志内容放在界定符内,明确要求JSON输出:
PROMPT_TEMPLATE = """你是一个资深运维工程师,请分析以下日志并提取结构化信息。 要求: 1. 只依据日志文本本身,不得编造信息; 2. 按JSON格式输出,字段包括:level, service, error_code, anomaly_type, key_fields, suggestion; 3. anomaly_type必须从给定枚举中选择,无法判断填'未知'; 4. suggestion要具体可执行,控制在50字以内。 日志内容: <log> {log_content} </log> 请直接输出JSON:""" def parse_analysis(text: str) -> LogEntry: # 清理可能包裹的markdown代码块,再解析JSON cleaned = text.strip().removeprefix("```json").removesuffix("```").strip() return LogEntry.model_validate_json(cleaned)接下来是Harness的核心:模型调用封装、重试、以及错误修正。一个关键的工程细节是:当模型输出的JSON解析失败时,不要直接放弃,而是把解析错误信息回传给模型,让它修正。这能显著提升整体成功率:
async def analyze_log(client, log_text: str, max_attempts: int = 3) -> LogEntry: messages = [ {"role": "system", "content": "你是日志分析专家,输出严格JSON。"}, {"role": "user", "content": PROMPT_TEMPLATE.format(log_content=log_text)}, ] for attempt in range(max_attempts): try: resp = await client.chat.completions.create( model="your-model-name", messages=messages, response_format={"type": "json_object"}, temperature=0.1, ) return parse_analysis(resp.choices[0].message.content) except Exception as e: messages.append({"role": "assistant", "content": resp.choices[0].message.content}) messages.append({"role": "user", "content": f"输出解析失败:{e}。请按要求重新输出JSON。"}) # 指数退避或简单sleep,避免限流 await asyncio.sleep(1.5 * (attempt + 1)) raise RuntimeError(f"日志分析失败:JSON格式连续{max_attempts}次错误")温度参数这里设置为0.1,是因为结构化提取任务希望输出尽可能稳定,不需要创造性。我见过很多人忽略temperature对稳定性影响的权重,在提取类任务里,temperature高一点,输出格式错误的概率就会明显上升,这个参数值得花时间调。
4.3 数据准备和黄金评测集构建
数据从哪里来?可以找开源的日志数据集,更推荐的方式是从自己日常项目中导出真实日志。拿一千条真实日志,人工标注一小部分作为种子评测集,剩下的作为开发集做离线自测。种子评测集我建议控制在100到200条,涵盖:正常日志占一半、超时和连接失败各占两成、各类业务错误码、以及几条约定噪声日志。
评测指标怎么定?这里不追求完美,至少有四个客观维度:一是JSON解析成功率,满分为100%;二是level字段准确率,建议90%以上;三是error_code提取的精确率和召回率,目标85%以上;四是anomaly_type的准确率,这是最难的一个,因为有些日志从字面上很难判断类型。每周跑一次评测,记录指标变化,所有的Prompt优化都以这些数字是否提升为准绳。有了基线数字,后续的Prompt迭代就不再是凭感觉"看起来变好了",而是有数据支撑的持续改进。
4.4 结果评测与迭代优化:我踩过的三个坑
第一次跑完整流程时,我的系统在日志分析上犯了三个经典错误,写在这里供你参考。
第一个坑是"JSON双重嵌套"。我的Prompt要求模型输出JSON,而模型有时会在JSON外套一层Markdown代码块,或者用单引号替代双引号。所以我加了清理函数,但更稳妥的方案是强制使用response_format参数,从模型侧锁死JSON输出,再配合清理逻辑做双保险。
第二个坑是temperature和随机性。我在初版把temperature设成了默认值,结果同一个日志连续跑5次,有3次结果不同,评测指标忽高忽低。后来改成0.1,重复跑5次的结果才基本一致。对于任何结构化提取任务,低temperature是基线配置,需要在一致性和多样性之间找到平衡点。
第三个坑是评测集样本偏差。我最初用真实日志构建评测集,结果模型对日志的泛化还行,但一碰到人工构造的边界输入(比如空的日志、纯符号日志、超长几十KB的日志)就崩。把评测集补齐这些异常样本后,系统的鲁棒性才真正上去。这个经验直接说明了评测集覆盖"错"和"怪"比覆盖"多"更重要。
5. 进阶路径与可靠性:从"能跑"到"靠谱"
5.1 Agent、多AI协作与工作流编排:下一个能力台阶
日志分析助手还属于"单模型单任务"模式,但真实业务里,很多需求天然是工作流。比如日志分析助手要真正落地运维监控,还需要对接工单系统、执行重启脚本、通知值班人员——这个链条就涉及Agent和工具调用了。再进一步,是多AI协作(Multi-Agent):一个Agent负责日志初筛,一个Agent负责根因分析,一个Agent负责对外沟通,它们共享上下文、互相校验结果、按需唤醒。我在一个内部项目中实现过一个四Agent协作的故障分析系统,整体准确率比单Agent提升了约25%,主要原因是分工之后,每个Agent的Prompt更加聚焦,不被不相关的任务干扰。
多Agent编排的核心工程挑战是上下文隔离和结果汇聚。我的做法是通过事件总线传递消息,每个Agent只读取关心的事件,更新自己的状态,再发布新事件。这样系统的可观测性非常强,每个环节都能知道是谁、在什么时间、基于什么上下文、产出了什么结果。推荐你在这个阶段阅读LangGraph这类编排框架的源码,理解状态机和路由是怎么设计的,而不是直接用。
5.2 可靠性工程:监控、兜底、成本和延迟
模型一旦真正上线,可靠性的维度就开始爆炸式增长。我在生产环境里最看重的四个指标:错误率(模型抛异常的比率)、重试率(并发的重试触发比例)、超时分布(P50、P95、P99)以及单位请求成本。日志分析助手这类文本提取任务,P99延迟目标应当控制在2到3秒内,单次调用的成本应尽量控制在特定的分钱级别以下。
系统兜底的策略也很重要。我通常设计三级兜底:第一级是复用经典正则规则和启发式规则,在模型输出明显异常时直接采用规则结果;第二级是降级模型,优先用快速便宜的模型,失败再切换到高精度模型;第三级是人工审核通道,把低置信度样本推送给人做二次确认,同时回填到评测集。没有这套兜底,任何一个线上小波动都可能变成用户投诉。
成本控制方面,我的经验是"过路的车都值得搭":第一,优先使用上下文缓存,减少重复token;第二,小任务用轻量模型,只有根因分析这类高难度任务才动用大模型;第三,对流式输出消费端做节流,不等完整回答、按片段实时处理。成本优化和延迟优化往往是一体的,设计时就要一起考虑。
5.3 几条避坑建议和一个学习路径参考
最后分享几个我在一线实战中反复撞墙后总结的避坑建议,没有任何技术含量但每一分都是切肤之痛。
第一,Prompt版本必须纳入版本管理。我在Git里单独建了一个prompts/目录,每个Prompt文件标注了适用模型版本、上线时间和评测得分。改Prompt前先复制一份到experiments/,跑完评测再决定是否合并。这一习惯让团队在追线上回归时少吵了无数架。
第二,警惕评测集过拟合。你的Prompt和评测指标绑定久了,评测集上的得分会虚高,因为模型"记住"了这组样本的偏好。我的办法是每两周从线上日志里抽一批全新样本加入评测集,保持评测集的新鲜度和难度。
第三,不要害怕用"笨方法"。我见过不少团队花几周时间设计复杂的Agent编排,最后发现一个简单规则加两个Prompt就解决了80%的问题。从简单方案起步,用数据说话,复杂方案只在你真的需要时引入。
如果你从零开始,我的建议路径是:理解Prompt和结构化输出(1周)→ 完成一个包含评测的简单提取项目(2周)→ 加入工具调用和Agent能力(3到4周)→ 构建多Agent协作的完整工作流(4到8周)→ 最后回来打磨可靠性和成本。每一步都要有能拿得出手的Demo和数值指标,这样的学习才不是空中楼阁。
我这两年最深的体会是,AI工程的核心竞争力不在于你掌握了多少新概念和框架,而在于你有没有一套"快速验证、量化迭代、稳定兜底"的工程循环。