从零开始搭建你自己的AI工程实践“ai-engineering-from-scratch”这个项目标题看着简单其实背后藏着一个很现实的问题真正把一个AI想法落地成能跑、能迭代、能上线的系统到底要补多少课我在一线做AI工程已经有几年时间见过太多人把“调用一个LLM接口”误当成“AI工程”的全部结果做出来的东西距离生产环境还差很远。这个项目本质上就是一条自己走过的路不依赖任何现成的全家桶方案从提示词设计、模型调用、上下文管理、Agent编排再到测试评估一步步把一个AI应用从零开始搭起来。这篇文章会把整条实践路径拆给你看适合刚入门AI开发的工程师、产品经理和所有想认真搞AI落地的人。我自己在这个项目里踩过的坑不少其中最大的一个是市面上90%的教程都在教你怎么“用”AI而不是教你怎么“工程化”AI。用AI只需要会写提示词工程化AI则需要你理解模型行为、掌控不确定性、设计评估闭环。这篇文章就是冲这个来的我会把我花了大量时间换来的经验尽量平铺直叙地讲清楚。1. 项目定位与整体设计思路1.1 从零开始到底意味着什么“from scratch”这个词经常被人误解。很多人以为从零开始AI工程就是不用任何框架手写神经网络的反向传播或者从tokenizer开始实现一个GPT。说实话那是科研活不是工程活。工程意义上的“从零开始”指的是不依赖某个一体化平台帮你把所有事情都包办掉而是自己掌握AI应用的每一个关键环节。我在这个项目里的定义很明确模型层的API照用自己训练大模型成本太高且对多数业务场景没必要但应用层的所有东西全部自己搭。这包括提示词体系的设计、上下文的组装与管理、工具调用的协议、Agent的执行循环、以及最容易被忽略的测试评估系统。为什么要这么较真因为只有把每一层都摸过一遍你才能在出问题的时候知道该去哪一层排查。我见过一个团队用了一个很成熟的Agent框架业务跑起来看着很顺结果某天AI开始疯狂调用某个工具所有人都懵了最后发现是框架内置的反思机制和他们自定义的工具描述冲突了。如果不懂底层的执行逻辑这种问题只能靠瞎猜。1.2 项目要解决的核心痛点在开始动手之前我先梳理了一下当时最频繁遇到的痛点一共有四类提示词写不好同一个问题换个说法模型输出质量波动大到不可接受。上下文管理混乱对话一长就丢失关键信息或者把无关历史塞给模型导致“知识污染”。Agent编排不可控多步骤任务执行时模型经常在各个步骤间跑偏。评估完全靠感觉没有量化指标不知道改版到底是变好了还是变差了。[\table] 痛点领域 | 典型表现 | 工程化手段 提示词 | 输出不稳定格式随机 | 结构化提示词、少量示例、输出Schema约束 上下文 | 长对话失效信息覆盖 | 上下文窗口管理、摘要压缩、关键信息抽取 Agent编排 | 执行跑偏工具误用 | 状态机约束、工具白名单、人工确认节点 测试评估 | 改版无依据回归靠肉眼 | 自动化评测集、指标量化、版本对比[/table]这四个痛点是相互关联的。提示词问题影响的是单轮输出的质量上下文问题影响的是多轮对话的连贯性Agent编排问题影响的是复杂任务的可靠性评估问题则决定了你能不能持续改进前三个问题。所以项目路线图也是按照这个顺序来拆的。1.3 技术选型的关键考量关于技术选型我的原则是三句话主流程要短、扩展要容易、换模型要方便。主流程短意思是核心逻辑的代码路径不要太绕出问题能快速定位扩展容易指新加一个工具、新接一个数据源的成本要低换模型方便是因为这个领域迭代太快今天用的模型明天可能就有更好的替代品所以API抽象层必须做。基于这三点我当时没有选那种包揽一切的Agent框架而是选了一个极简的编排核心自己写每个模块只做一件事模块之间用清晰的数据结构对接。具体方案在下一节展开这里先把结论放出来简单不等于简陋恰恰是简单的东西在出问题的时候最好修。2. 核心细节解析与实操要点2.1 提示词工程从玄学到工程学提示词工程是这个项目的基础设施。很多人在这一步犯的错是把它当成“写作文”以为把需求描述得越详细越好。其实提示词工程的核心不是文字功底而是结构设计。我最终落地的提示词模板强制包含六个部分角色定义模型以什么身份来处理这个任务。任务描述一句话说清楚要做什么禁止让模型自己猜。输入格式说明什么是数据数据长什么样。输出格式约束用JSON还是Markdown字段名是什么。边界条件哪些情况是模型必须拒绝或特殊处理的。少量示例给两到三个输入输出对比任何抽象描述都管用。这里最容易被忽略的是边界条件。我踩过一个很典型的坑让模型做文本分类结果它遇到一个模棱两可的输入时自己脑补了一个“其他”类别但这个类别不在我指定的枚举值里。后来我在模板里明确写了“如果无法确定类别必须返回unknown禁止自行发明新类别”这个问题就彻底消失了。少量示例的选择也很有讲究。示例不是随便给的应该刻意覆盖三种类型最典型的正常情况、最容易混淆的边界情况、需要触发特殊规则的例外情况。三个示例基本够用再多反而会让模型过于依赖示例的模式。2.2 上下文工程决定AI“记忆力”的关键上下文管理是AI工程里最容易被低估的部分。你给模型喂什么历史信息、喂多少、按什么顺序喂直接决定了它在长对话里的表现。我在项目里用了三层上下文结构第一层是系统提示词包含角色定义、全局规则、任务边界。这层内容在每次请求时固定存在相当于AI的“世界观”。第二层是会话摘要把之前对话的核心信息压缩成结构化的要点。这个摘要不是简单地把历史对话截断而是用模型自己提炼关键信息。举个例子如果用户在10轮前说了“我喜欢简洁的回答风格”这个信息必须保留在摘要里哪怕对话已经转到了完全不同的主题。第三层是最近对话保留最近几轮的完整对话记录因为摘要会丢失细节模型需要有足够近的原始信息来做准确响应。这套三层结构解决了两个问题一是长对话的“记忆衰减”问题摘要保证了关键信息不丢二是token成本问题不把全部历史无脑塞给模型能省下大量上下文空间。我做过一个有趣的实验同一组对话测试用单层完整历史的方式跑长对话到第30轮就开始前后矛盾换成三层结构后跑满100轮依然能准确引用用户早期提到的偏好。这个差距是决定性的。2.3 Agent编排从自由发挥到流程受控Agent是这个项目里最复杂也最坑的一环。我一开始用了非常“自由”的设计——让模型自己决定调什么工具、按什么顺序调。结果就是灾难性的它经常在无关任务之间跳来跳去或者在一个简单任务上反复尝试各种工具不回来。后来我把Agent的执行模式从“完全自主”改成了“状态机约束”局面才彻底改观。具体做法是预先定义好任务的每个阶段每个阶段允许调用哪些工具、需要输出什么字段、什么时候可以推进到下一阶段。模型只能在给定的自由度里做决策不能越过边界。比如一个“查资料并写摘要”的任务状态机是理解需求 - 搜索资料 - 筛选结果 - 撰写摘要 - 格式检查。在“搜索资料”阶段模型只能调用搜索工具不能跳去写摘要在“撰写摘要”阶段模型不能再去搜索新资料除非它明确标记“信息不足需要补充搜索”并得到人工确认。这套设计的价值在于它把AI的创造性用在了正确的地方——在给定步骤内如何做得更好而不是让它决定整个流程的走向。我管这叫“给了自由但圈了边界”。2.4 工具调用的协议设计工具调用是Agent能力的延伸但工具描述写不好模型就不会用。工具描述不是写给用户看的是写给模型看的所以格式特别重要。我的工具描述模板包含这么几个字段工具名称简短、语义明确模型会通过名称理解工具的用途。功能描述用两到三句话说清楚工具是做什么的适合什么场景。参数Schema每个参数的名称、类型、是否必填、取值范围。返回值格式工具返回什么数据结构怎么判断成功或失败。使用注意事项哪些情况下不要用这个工具比如“仅当用户明确要求查询天气时才使用”。第五点是我后期加上的效果异常显著。之前模型经常会“顺手”调用一个工具比如用户问了一句“今天能发货吗”模型就去查快递物流但其实用户只是在问电商平台的一般政策。加上负面使用条件后这种误调用频率降低了八成。还有一个细节工具返回结果过长时要先做摘要再喂给模型。否则工具调用一多上下文就爆炸了而且模型容易被大量噪声信息干扰。我在工具返回层加了一个“返回结果压缩”的环节所有工具输出先过一遍清洗逻辑抓取关键字段然后才进入模型上下文。2.5 测试评估体系把“感觉”变成“指标”大部分做AI应用的人评估方式是“我试了几条用例感觉效果还行”。这种评估方式在项目早期可以一旦进入迭代阶段就完全不够用了——你不知道这次改提示词到底比上次好还是差也不知道会不会在某些场景下全面退步。我搭的评估体系分三个层次第一层是单轮输出质量评测。准备一组带标准答案的测试集每次改动后用这组测试集批量跑一遍算准确率、召回率、格式合规率等指标。这组测试集必须是固定的、不回传的否则没法做回归对比。第二层是多轮任务成功率评测。模拟真实的完整任务流程衡量Agent能不能从头到尾把任务做完中间有没有跑偏有没有工具调用失败但没恢复。这个指标比单轮准确率更能反映真实体验。第三层是线上日志复盘。把生产环境的请求日志定期抽样人工标注“用户是否满意”用这个数据来补全测试集的盲区。我在实际运行中发现第一层和第二层的指标经常出现“打架”的情况单轮输出准确率提高了但多轮任务成功率反而下降了。根源是模型的单轮能力增强后其行为模式在Agent流程里反而变得更“自信”、更容易忽略流程约束。这个发现直接推动我在Agent状态机里加了“不确定时必须暂停请求确认”的规则让多轮成功率重新拉回来。3. 实操过程与核心环节实现3.1 第一步搭建最小可运行系统我的习惯是万事开头先搭一个最小闭环一个输入框、一次模型调用、一个输出展示。这不是为了炫技而是先把整个链条跑通确认每个环节的API、鉴权、数据格式都没问题。这个最小系统的代码结构大概是这个样子的# minimal_pipeline.py # 一个最简的AI应用闭环示例 from openai import OpenAI client OpenAI(api_keyyour-api-key) system_prompt 你是一个简洁的回答助手回答不超过200字。 def generate_answer(user_input: str) - str: response client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: system_prompt}, {role: user, content: user_input} ], temperature0.3 ) return response.choices[0].message.content if __name__ __main__: while True: user_input input(请输入你的问题输入exit退出) if user_input.lower() exit: break result generate_answer(user_input) print(AI回答:, result)这里面的关键参数是temperature。很多人不理解这个参数的意义它控制模型输出的随机性——值越低输出越保守、确定值越高输出越多样、有创意。做工程化应用我建议默认设在0.2到0.5之间除非你明确需要创意发散。把temperature调成0并不会让输出100%确定但能让每次输出的结构稳定性大幅提升。跑通这个最小系统后你就算完成“AI工程从零开始”的第一步了。接下来所有的复杂度都是在这个基础上一步一个脚印加进来的。3.2 第二步设计结构化提示词与输出约束第一次改进要做的是把“裸调”变成“结构化调用”。我在这一步做了三件事给提示词加结构、给输出加格式约束、给调用加错误处理。输出格式约束这一步很关键。让模型直接返回一段自然语言你会发现下游解析的活特别难干。正确的做法是让模型返回严格的JSON结构然后用代码解析。# structured_prompt.py # 结构化提示词 JSON输出示例 import json from openai import OpenAI client OpenAI(api_keyyour-api-key) structured_prompt 你是一个电商客服意图分类器。 任务判断用户问题的意图类别。 输入用户提问文本 输出JSON对象包含两个字段 - intent: 意图类别只能从[查订单, 退换货, 咨询商品, 投诉, 其他]中选择 - confidence: 置信度0到1之间的小数 - reasoning: 一句话说明判断依据 约束条件 1. 如果无法确定意图intent返回other 2. 禁止返回列表之外的意图类别 3. 如果用户问题包含多个意图以第一个出现的意图为准 示例 输入我的订单显示已发货但三天没到 输出{intent: 查订单, confidence: 0.95, reasoning: 用户描述的是订单状态查询问题} 输入这个衣服能换大一号吗 输出{intent: 退换货, confidence: 0.9, reasoning: 用户询问换货相关操作} 输入你叫什么名字 输出{intent: other, confidence: 0.8, reasoning: 问题与业务无关} def classify_intent(user_input: str) - dict: response client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: structured_prompt}, {role: user, content: user_input} ], temperature0.1, response_format{type: json_object} ) raw_content response.choices[0].message.content # 解析JSON如果解析失败则走兜底逻辑 try: result json.loads(raw_content) except json.JSONDecodeError: return {intent: other, confidence: 0.0, reasoning: 模型输出非JSON走兜底} return result这中间最大的坑是JSON解析失败。即使你要求模型返回JSON它偶尔还是会输出些乱码。所以我建议所有模型调用的下游都要套一层try-except并准备好兜底返回值。这套防御性编程虽然不优雅但能在生产环境里救你很多次。3.3 第三步实现上下文管理模块上下文管理从这一步开始变成一个独立模块不再跟业务代码混在一起。我的实现方案是这样的对话历史存到Redis里键值对结构是session_id - {summary, recent_messages}。每次新请求进来从Redis读取该session的摘要和最近消息然后把系统提示词、摘要、最近消息拼接成完整的消息列表再发给模型。# context_manager.py # 三层上下文管理模块 import redis import json r redis.Redis(hostlocalhost, port6379, decode_responsesTrue) def get_session_context(session_id: str): raw r.get(fsession:{session_id}) if raw: return json.loads(raw) return {summary: , recent_messages: []} def update_context(session_id: str, user_msg: str, ai_msg: str): ctx get_session_context(session_id) # 追加最新对话 ctx[recent_messages].append({role: user, content: user_msg}) ctx[recent_messages].append({role: assistant, content: ai_msg}) # 只保留最近5轮对话 ctx[recent_messages] ctx[recent_messages][-10:] # 如果最近对话超过阈值触发摘要压缩 if len(ctx[recent_messages]) 10: ctx[summary] summarize_context(ctx[summary], ctx[recent_messages]) ctx[recent_messages] [] r.set(fsession:{session_id}, json.dumps(ctx)) def build_messages(session_id: str, system_prompt: str, user_input: str): ctx get_session_context(session_id) messages [{role: system, content: system_prompt}] if ctx[summary]: messages.append({role: system, content: f之前的对话摘要{ctx[summary]}}) messages.extend(ctx[recent_messages]) messages.append({role: user, content: user_input}) return messages摘要压缩函数我没有在这里贴代码因为实现思路本身就比代码重要。摘要不等于简单截断而是要提炼出“和后续对话相关”的关键信息。比如用户说“我不喜欢太长的回答”这个偏好需要留在摘要里而用户说“今天天气不错”这种一次性信息就无需保留。3.4 第四步编排Agent状态机Agent状态机的实现是整个项目里最体现工程功底的部分。我的做法是把任务流程定义成一个明确的状态图每个状态有自己的处理器函数。# agent_state_machine.py # 一个简单的Agent状态机示例 from enum import Enum class AgentState(Enum): UNDERSTAND understand # 理解需求 SEARCH search # 搜索资料 DRAFT draft # 撰写内容 REVIEW review # 质量检查 COMPLETE complete # 完成 class ResearchAgent: def __init__(self): self.state AgentState.UNDERSTAND self.search_results [] self.draft_content def run(self, user_query: str): # 主循环只有状态为COMPLETE时才退出 while self.state ! AgentState.COMPLETE: if self.state AgentState.UNDERSTAND: self.handle_understand(user_query) elif self.state AgentState.SEARCH: self.handle_search() elif self.state AgentState.DRAFT: self.handle_draft() elif self.state AgentState.REVIEW: self.handle_review() def handle_understand(self, user_query: str): # 用LLM解析用户需求的要点并判断需要搜索哪些信息 self.search_keywords extract_keywords(user_query) self.state AgentState.SEARCH def handle_search(self): # 在工具白名单中只允许调用搜索工具 for kw in self.search_keywords: result call_search_tool(kw) self.search_results.append(result) self.state AgentState.DRAFT def handle_draft(self): # 根据搜索结果撰写内容 self.draft_content generate_draft(self.search_results) self.state AgentState.REVIEW def handle_review(self): # 质量检查检查格式、长度、是否有明显错误 if quality_check(self.draft_content): self.state AgentState.COMPLETE else: # 质量不过关则重新撰写但最多重试两次 self.retry_count 1 if self.retry_count 2: self.state AgentState.COMPLETE # 强制完成防止死循环 else: self.state AgentState.DRAFT状态机最核心的工程价值是“可观测性”和“可控性”。每执行一步我都能知道Agent当前在哪一步、下一步会去哪、可以调用什么工具。一旦出错我可以在任何状态介入直接修改数据后重放后续步骤。Agent从黑盒变成了灰盒这个转变对调试体验来说是量变到质变。3.5 第五步搭起自动化评测流水线评测流水线是整个项目的“质检关卡”它的价值在长期迭代中会越来越明显。评测数据集是核心资产我建议从上线第一天就开始积累。每次线上用户反馈“回答不对”的case确认无误后就进评测集每次产品争议比较大的case也进评测集。半年下来这个数据集就是评估AI系统好坏的黄金标准。# eval_pipeline.py # 自动化评测流水线示例 import json import numpy as np def run_eval_pipeline(model_shorthand: str, eval_cases): total_cases len(eval_cases) correct 0 format_compliance 0 for case in eval_cases: # 逐条跑测试用例 output call_model(model_shorthand, case[input]) # 判断业务正确性 is_correct judge_output(output, case[expected]) # 判断格式合规性 is_formatted check_format(output, case[expected_format]) correct int(is_correct) format_compliance int(is_formatted) return { accuracy: correct / total_cases, format_compliance: format_compliance / total_cases, total_cases: total_cases, failed_cases: [ c for c in eval_cases if not judge_output(call_model(model_shorthand, c[input]), c[expected]) ] } # 评测结果示例 # {accuracy: 0.92, format_compliance: 0.98, total_cases: 120, failed_cases: [...]}评测流水线一定要在每次改动提示词或模型后立刻运行形成“改动-评测-决策”的闭环。我在机器上配了一条命令改完提示词后跑一个脚本几分钟内就能看到指标变化。没有这条流水线你每次改版都在裸奔。4. 常见问题与排查技巧实录4.1 模型输出不稳定怎么办这是被问得最多的一个问题。同样的输入模型今天给的结果和昨天不同甚至连续两次调用结果都不同。排查顺序建议如下先确认temperature是否设得太高。大于0.7就会有明显的随机性工程应用建议降到0.3以下。再检查提示词是否有“确定性锚点”。即使temperature偏低如果提示词太模糊、没给示例输出依然会漂。加一个标准示例的输出格式稳定性立刻提升。最后看模型版本是否有变化。有些模型服务商会悄悄更新底层模型导致行为差异。[\table] 现象 | 排查点 | 常用解法 同一输入多次输出不同 | temperature过高 | 调低到0.2~0.3 输出结构忽对忽错 | 没有输出Schema约束 | 使用response_format强制JSON 格式全对但内容变差 | 模型版本更新 | 固定模型版本升级前跑评测集 特定输入必出错 | 提示词边界条件缺失 | 补充边界规则和反面示例[/table]4.2 Agent循环卡死或者跑偏怎么破Agent进入死循环是状态机设计最容易踩的坑。模型可能在一个状态下反复调用同一个工具或者在两个状态之间来回跳。我的解决方案有三层第一层是设置最大重试次数。每个状态只允许失败重试N次超过就强制进入兜底流程——通常是直接结束任务并告诉用户“当前任务无法自动完成”。第二层是行为模式检测。记录最近10次状态转移如果出现A-B-A-B这样的循环模式就中断并走人工确认。第三层是状态转移白名单。在状态机里定义“允许的转移矩阵”模型无法直接跳过一个不该跳的步骤。这一层最有效但也需要你预先想清楚流程的边界。4.3 长对话记忆总丢怎么排查和解决这个问题的根源前面提到过大部分是上下文管理没做好。排查步骤是第一确认你发送给模型的messages里历轮对话和系统提示词是否拼接正确。第二确认摘要压缩是否保留了关键信息。把摘要拿给人看一遍如果人都觉得摘要漏了重点模型也会漏。第三确认关键信息是否被截断。长对话时有些关键信息在第20轮开头出现过现在已经被挤出最近窗口了如果摘要里也没这部分就彻底丢了。解决办法是“关键信息锚定”机制在对话过程中实时识别用户提到的偏好和重要事实单独存到结构化字段里每个请求都带上。这个字段不随轮次滚动对话再长也不会丢。对比一下这个方案比单纯依赖摘要要可靠很多。4.4 评测集和真实场景脱节怎么办评测准确率很高但上线后体验很差这个现象也有很多人碰到。核心原因是评测集太“干净”了覆盖不到真实场景里的长尾问题。我的做法是用“视频回放式”补充评测集。每次线上出现问题把真实的输入输出记录成一份case加入评测集。这些case往往带着真实用户的随意表达方式——句子不通顺、信息不全、有多余语气词——这些都是评测集里最珍贵的数据。所以我会定期清理评测集确保里面至少有20%的case来自真实场景而不是全部来自手动编写。示例真实场景case vs 手工写case的差异 手工写的case 输入查一下订单123456的物流信息 期望输出该订单已于昨天发出预计后天到达 真实场景case 输入我那个暗色衣服发货了没啊一直没消息 期望输出查询到订单456789于今天下午发出请留意物流更新第二个case才是模型在真实世界每天面对的东西评测集里没有这种case模型就永远学不会处理日常化的表达。4.5 关于工具误调用的经典案例分析最后分享一个我实际处理过的工具误调用案例。我们的Agent接了一个“查询天气”的工具某天有用户问“明天适合去爬山吗”Agent直接调用了天气工具查了明天的天气。这个行为看起来没错但问题是当时Agent的系统提示里规定“必须优先调用天气工具查询信息”所以它忽略了用户的真实请求其实是“爬山适合性建议”而不是简单的天气查询。修复方案很典型我给工具描述加了一个“使用前提”——“仅当用户明确要求查询天气数据时使用。用户索要建议或评估时不应使用此工具应基于用户提供的信息给出建议。若缺少关键信息如地点、时间必须主动询问用户。”同时把系统提示改成“先分析用户意图再决定是否调用工具禁止一上来就调用工具”。这两个改动合起来工具误调用率直接降低了七成。这个案例给我们的教训是Agent工具的误用大概率不是模型能力不足而是工具本身的“使用说明书”写得不够好。把工具描述当成API文档认真写加上负面条件和使用边界模型的决策质量会明显上一个台阶。5. 一些实操中的体会与建议走到这整个“ai-engineering-from-scratch”的主干就清晰了。我最后再分享几个实操体会都是每次踩坑换来的。第一AI工程最值钱的部分不是模型调用而是围绕模型建立的工程体系。同样一个模型有人用起来比另一个人好差距往往在提示词设计、上下文管理、评估闭环这些“看不见的地方”。这些地方才是你需要亲手从零开始搭建的核心资产。第二稳定性优于一切。做AI应用和做传统软件的差异在于传统软件只要写对逻辑就稳定AI应用则永远存在不确定性。所以工程目标不是消灭不确定性而是把不确定性隔离在可控范围内——用状态机控流程用Schema控输出用评测集控质量。第三一定要建立“改动即评测”的习惯。AI系统的回归问题非常隐蔽今天改一个提示词可能让一个周后的某个场景变差。没有自动化评测就没有安全的迭代速度。项目做出来以后我又陆续往里加了多Agent协作、工作流可视化、线上行为回放等功能但底层那套从零搭建的骨架始终没有换。我建议你也从最底层的东西开始搭搭过一遍你踩过的每一个坑都会沉淀成未来快速排障的直觉。这个项目的价值说实话不在于跑通了多少功能而在于你把AI应用从“能跑”推进到了“可控、可测、可迭代”的状态——这才是AI工程师真正的手艺。