news 2026/9/29 18:51:30

从零构建AI工程:提示词、工具调用与本地部署实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零构建AI工程:提示词、工具调用与本地部署实战

1. 项目缘起:为什么我要从零再造一个AI工程

我给自己定的这个项目代号是ai-engineering-from-scratch,字面意思就是“从零开始搞AI工程”。身边不少人问我,现在现成的AI框架、低代码平台和开源项目一抓一大把,为什么要费劲从空白目录写起?我当时的想法很简单:用了半年别人的AI应用,调过不少API,也玩过各种Agent框架,但总觉得有个地方不对劲——出了问题,我不知道该从哪一层去排查。

这不是一个虚荣的“造轮子”项目,而是一个很实际的基本功补课计划。我需要从模型接口怎么调用、提示词怎么组织、工具调用协议怎么设计、上下文怎么管理,到整个应用怎么打包部署,亲手走一遍。只有把这条路走过一遍,后面拿现成框架的时候才知道它替你做了什么、没做什么,踩坑的时候也才找得到方向。

这个项目最适合两类人。一类是刚入门AI应用开发,想弄明白大模型应用的真实工程链路的新手;另一类是已经在用现成AI框架,但总觉得“受制于人”、想真正掌握底层逻辑的开发者。文章里提到的所有步骤、决策和坑,都是我实际跑出来的,不是从文档里抄的。

2. 整体架构与思路拆解

2.1 先想清楚:AI工程到底在工程什么

很多人对AI工程的第一反应是“训练模型”“调参”,但实际做应用开发时,大部分时间和精力花在了模型外围的工程问题上。一个大模型应用,真正的核心链路可以拆成四段:输入处理、模型交互、输出校验、工具执行。输入处理是把用户说的话整理成模型能理解的提示词结构;模型交互涉及选接口、传参数、处理流式返回;输出校验是防止模型生成乱七八糟的格式;工具执行则是让模型能调用外部功能,比如查数据库、调计算器、发请求。

我最初犯的错误是恨不得一步到位,直接搭一个复杂的Agent系统。后来被现实教育了:基础模块没打牢,Agent的行为就完全不可控。所以我把项目从最简模型接口调用开始,一层一层向上加。整个过程就像盖房子,先打地基,再砌墙,最后才装修。那些看起来酷炫的“自动规划”“多智能体协作”,本质上都是建立在稳定可靠的模型交互和工具调用协议之上的。

2.2 核心选型:托管API和本地部署怎么挑

在模型接入方式上,我做了个比较长期的对比。托管API方案,比如常见的OpenAI兼容接口、国内各大云厂商的大模型接口,优势是接入快、不需要自己准备显卡、模型更新也及时,按量付费,前期成本低。缺点是数据要传到第三方服务,部分业务场景会有隐私顾虑,而且网络延迟和限流策略不可控。本地部署方案,比如用Ollama或llama.cpp跑开源模型,优势是数据完全在自己手里,离线也能跑,一次投入后调用没有额外费用,而且可以针对场景微调。缺点是硬件成本高,需要一个容量足够的GPU设备,模型效果也不一定追得上商业大模型。

对比维度托管API本地部署
接入速度快,十几分钟搞定较慢,要下载模型、配置环境
硬件要求无额外要求GPU显存8GB起步,越大越好
数据隐私数据出域,需评估合规性完全本地,隐私可控
单次成本按token计费只有电费和硬件折旧
模型效果通常更强取决于显存和量化等级
运维复杂度低需要处理环境依赖、版本管理

我的最终选择是两边都接,但在架构上做抽象。写一个统一的模型接口层,上层业务不关心背后是API还是本地模型,换模型只是改配置。这种设计在后期帮我省了非常多事,模型一升级,我只用改一行配置就能切换验证。

3. 核心细节解析:工程中最容易翻车的三个关节

3.1 提示词工程:不是“写段话”那么简单

Prompt Engineering是我在这个项目里花时间最多的地方。网上很多人把提示词说得神乎其神,好像一句话就能让模型变聪明。实际上提示词更像是一份操作手册,你要让模型清楚自己的角色、任务目标、工作步骤、输出格式,以及遇到异常时怎么办。我第一次写提示词时就吃了大亏:只写了“你是一个AI助手,请回答用户问题”,结果模型一会儿给我输出Markdown表格,一会儿带一堆免责声明,格式毫无稳定性。

后来我固定了一套自己的提示词模板,包含四个部分:角色设定、任务描述、约束条件、输出格式定义。角色设定告诉模型站在什么角度思考问题;任务描述把用户需求翻译成清晰的动作指令;约束条件限制回答范围、字数、语气;输出格式定义则要求模型按JSON或指定结构返回结果。这套模板不一定适合所有场景,但它的价值在于可复用,遇到新任务,我只是替换任务描述和输出格式,其他部分沿用即可。

实测中还有一个反直觉的规律:模型不是越听话越好,它会“过度服从”指令。有时候我在约束条件里写了“不要解释你的推理过程”,结果模型干脆连重要信息都不给;我写了“只能使用以下工具”,它遇到超出工具范围的需求就直接拒绝。所以提示词里的约束一定要精确,不能有歧义,而且要经过实测验证,不能想当然。

3.2 工具调用与AI Agent编排:给模型装上手和脚

在基础模型接口跑通之后,我给项目加入了工具调用能力,也就是让模型可以申请调用我预设的外部函数。这一步是整个项目复杂度上升的转折点,也是AI Agent概念的落地核心。我之前用过的很多框架会隐式处理工具调用的细节,但我要从零实现一遍,必须理解背后的函数定义协议和解析逻辑。

我把工具调用拆成了三个环节:工具注册、意图映射、结果反馈。工具注册是在系统里维护一个工具清单,每个工具包含名称、描述、输入参数结构;意图映射是模型根据用户问题和工具描述,决定该调用哪个工具、填什么参数;结果反馈则是把工具执行后的结果重新塞回模型,让模型根据结果组织最终回复。这三步形成一个循环,就是Agent最基本的工作流。

调试过程中遇到最多的问题是模型返回的工具参数和我的JSON Schema对不上,或者参数名大小写不一致。后来我总结了一个稳定的解法:在提示词里给每个工具附上一个示例参数,并且在解析模型的输出时,不要期望百分百严格的JSON,先用正则把JSON片段提取出来,再用对抗式校验,解析失败就提示模型重新生成。这个方法让我的工具调用成功率从八成多提升到了接近满分。

3.3 上下文管理:窗口再大也有装满的一天

模型的上下文窗口是有限的,但真实对话是会一直增长的。刚开始我做了一个很蠢的设计:把对话历史全部塞给模型。等对话进行到第20轮,单次请求的token数量暴涨,响应速度变得很慢,而且模型开始“忘记”最开始的指令。后来我专门研究了上下文管理策略,简单来说就是设定一个最大token阈值,超过阈值后采用滑动窗口+摘要压缩的方式处理。

具体做法是:新消息永远占最新份额,旧消息中比较关键的几条保留原文,更早的消息丢给模型做一次摘要,用摘要代替原始长文本。摘要这一步看似浪费一次模型调用,实际上能大幅降低后续请求的token消耗,多轮对话下稳赚不赔。还有一种情况是业务需要“长期记忆”,那就要引入向量数据库做检索增强,这个我放在后面的扩展计划里,但当前阶段用摘要已经能解决大部分问题。

注意:上下文管理的目标是保证模型在任意时刻接收到的信息都能被有效利用。宁可少给一些历史,也不要让模型处理一堆语义重复的旧数据,那只会让它分心。

4. 实操过程:从零到可运行的全流程记录

4.1 环境准备与项目脚手架

先交代一下我的开发环境:一台Windows机器加上WSL2里的Ubuntu环境,Python版本用3.11,GPU方面有一块8GB显存的本地显卡。项目开始前,我先确认了一件事——Git是否就绪。运行git --version看到版本号是2.23.0.windows.1,略老,但基础功能没问题。我顺手把它升级到了新版,避免后续分支操作遇到老版本的不兼容问题。

目录结构我一开始就想好,避免后期重构。根目录下面建了app/放主逻辑,tools/放工具函数,config/放配置文件,tests/放单元测试,scripts/放启动和部署脚本。项目管理上我用了一个虚拟环境,把依赖写在requirements.txt里。这一步不是走形式,虚拟环境能帮你隔离项目依赖,否则不同项目对同一个包的不同版本要求会把你折磨到崩溃。

在模型接入之前,我用一个假的MockModel跑通了整个调用链路。这个做法强烈推荐——把模型接口抽象成一个类,注入一个返回固定文本的假模型,就能在没有任何真实模型的情况下先把代码逻辑调试完。等真实模型接入后,只需要专注于验证模型的能力表现,而不是同时排查代码bug和提示词问题。

4.2 最小可运行Demo:一个带工具调用的问答系统

我的第一版Demo只做了一件事:用户问一个计算题,模型调用一个计算器工具,把结果返回给用户。这个看似简单的流程覆盖了前面说的所有核心环节:提示词构造、模型调用、工具注册、解析模型回复、执行工具、把结果再喂回模型、输出最终回答。下面是当时模型请求的核心代码结构,简化过后大概长这样:

import json import requests def call_model(messages, functions=None): payload = { "model": "your-model", "messages": messages, "functions": functions or [], "temperature": 0.2 } resp = requests.post(API_URL, json=payload, headers={"Authorization": f"Bearer {API_KEY}"}) data = resp.json() return data def run_agent(user_input): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_input} ] response = call_model(messages, functions=TOOL_SCHEMAS) if response.get("function_call"): # 进入工具执行分支 func_name = response["function_call"]["name"] func_args = json.loads(response["function_call"]["arguments"]) result = execute_tool(func_name, func_args) messages.append(response) messages.append({"role": "function", "name": func_name, "content": json.dumps(result)}) final_response = call_model(messages) return final_response return response

这个版本跑通的那天,我印象很深,因为虽然功能简单,但它验证了我对整个工程的理解是正确的。工具调用不是模型自己直接执行函数,而是模型只负责决定“要做什么”,真正干活的还是我自己的代码。这个边界清晰以后,整个系统的安全性就好控制了,我不可能让模型直接访问系统,只能是模型提交参数,我的代码决定这个调用是否合法。

4.3 本地部署路线:把自己的模型跑起来

接到私有数据场景的需求之后,我开始研究本地部署AI的完整链路。这里选择的开源模型是一个7B参数级别的量化版本,用Ollama作为运行时。Ollama的优势在于安装简单、命令直观,一条ollama run就能把模型拉起来,而且它提供了兼容OpenAI格式的本地接口,意味着我之前写的模型调用层只需要改一行base_url就能切换到本地模型。

部署过程中遇到的一个典型挫折是模型下载速度慢,以及模型占用的磁盘空间比预期大得多。量化版本虽然能跑,但推理速度在8GB显存上依然有点保守,大约是每秒10个token左右,对于对话场景够用,但要批量处理大文本就会急死人。我的建议是:先明确自己的场景对时延和吞吐的要求,再决定本地部署还是托管API。纯粹为了“本地”而本地,没有必要。

4.4 测试与迭代:靠人工验证永远不够

Demo跑通之后,我给自己加了任务:写一套自动化测试来保证后续迭代不破坏原有功能。AI应用的测试和传统测试差别很大,因为模型输出有随机性,不能断言“输出等于某值”,只能断言“输出包含某关键字段”或者“输出满足某种约束”。通常有两种办法做这件事。第一种是把模型输出先做一次结构解析,再对解析后的字段做断言;第二种是引入一个“裁判模型”来判断输出质量。我在小范围内先用第一种,正则和JSON Schema解析就能覆盖大部分场景。

测试之外,我把每次调优提示词的结果记录在一个实验笔记里。某条提示词在哪些用例上通过了,哪些上失败了,记录得很详细。这个习惯后来帮我避了很多坑,因为模型的迭代会突然改变某些行为,如果我没有历史记录,根本判断不了是提示词的问题还是模型升级导致的问题。

5. 常见问题与排查心得

5.1 AI工程新手最容易踩的五个坑

把这些坑整理成一张速查表,每一个都是真实项目里验证过的,不是从网上抄的经验。

问题现象根本原因解决方案
模型返回结果经常“答非所问”上下文太长,关键信息被淹没缩短对话历史,启用摘要机制
工具调用参数解析失败模型生成的JSON格式不标准用正则提取JSON片段后再解析,失败后重试一次
相同提示词,结果时好时坏温度参数设置太高把temperature调到0.1~0.3,降低随机性
请求频繁超时或限流单次请求携带的token太多通过估算token数控制请求体大小,批量任务加退避重试
本地部署后推理速度慢模型量化等级选择不当或显存不足换用更小规模的模型,或用GGUF量化等级压缩模型体积

5.2 独家心得:先从“不智能”的骨架做起

AI工程最吸引人的地方在于“智能”,但工程上最扎实的做法是先做一个“不智能”的骨架。我所谓的“不智能”骨架,是先把输入输出链路、工具调用、错误处理、日志记录全部用假数据或者一个简单模型跑通,确保工程结构稳定。这就像拍电影之前先做分镜脚本,演员还没到位也能把机位、灯光全部定死。有了稳定骨架,后续替换真模型、加场景,都只是增量改动,而不是推倒重来。

还有一个心得是日志一定要从第一天就认真打。AI应用里的“隐性失败”非常多,比如模型偶尔返回一个格式错误、工具调用步骤里出现一个空值,这些如果不记录日志,后期排查问题会像大海捞针。我给每个请求都分配了一个trace_id,从请求进入系统到模型返回、工具执行,全部关联起来,哪怕一时没时间做可视化面板,光是grep日志也能定位问题。

5.3 工程化扩展:工作流、检索增强和多智能体

当前项目跑通以后,我很自然地开始考虑扩展。首先是AI工作流的引入,把多个工具调用串成可编排的流程。以前一个Agent只处理一个任务,现在可以定义“任务A完成的结果作为任务B的输入”,这个阶段可以考虑引入官方的Agent编排框架,但有了之前的从零实现经验,我能读懂它们的配置语法在底层做了什么。

其次是检索增强生成(RAG)的落地。上下文管理提到过,模型不认识训练之后的数据,所以需要把业务文档切成向量存到向量数据库里,在每次对话前根据用户问题做检索,把相关片段拼进提示词。这个方案对很多知识库问答场景都适用,也是目前把大模型落地到企业内部最实用的路径之一。

多智能体协作我研究得还不深,但我的理解是:不要把多个Agent吹得过于神秘,本质上就是不同角色用各自的提示词和工具集去处理子任务,再用一个调度逻辑汇总结论。调度路由可以是规则,也可以交给一个主Agent来决策。无论哪种方式,底层仍然需要我在之前阶段打牢的工具调用和输出校验能力。

说到底,ai-engineering-from-scratch这个项目给我带来最大的收获,不是“我亲手写了一个AI应用”,而是我终于能回答一个看起来很基础、却很少有人能真正说明白的问题:大模型应用从输入到输出的每一毫秒里,代码和人脑分别做了什么。

我个人在实际操作中的体会是:如果你也有兴趣在AI领域长期积累,第一件事不是囤一堆新框架,也不是搜集几十个“独家提示词”,而是花两周时间,像我一样把一个最简单的AI问答流程亲手写起来。用假模型通链路,用真模型调效果,用测试守住回归。这条路走完,你再回头看那些眼花缭乱的AI产品,会发现它们只是在这个骨架上装饰了不同的皮囊而已。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 18:50:31

跨平台联机如何成为游戏标配?从技术难题到实战避坑

2016 年前后,Epic 的 CEO 公开抛出一个判断:PS4 与 Xbox One 的跨平台联机是“不可避免”的。当时很多玩家觉得这就是厂商在画饼,毕竟在那个年代,主机平台之间几乎处于“老死不相往来”的状态,索尼有 PSN,微…

作者头像 李华
网站建设 2026/9/29 18:50:25

AgentScope:面向生产环境的智能体操作系统

1. AgentScope不是又一个LLM框架,而是面向工程落地的Agent操作系统最近在几个技术群里被反复问到:“AgentScope到底值不值得投入?是不是又一个玩具级Demo框架?”——这个问题我去年也问过自己。当时手头正卡在一个金融风控场景的多…

作者头像 李华
网站建设 2026/9/29 18:49:31

NU1680在TWS耳机无线充电仓中的Qi协议适配与I2C调压实战解析

干这行快十年了,做过的无线充电项目没二十个也有十五个,但NU1680这颗芯片在我心里的地位一直很特殊。TWS耳机充电仓的无线化方案我前后试过好几套,有的方案外围电路复杂得能塞下半块木板,有的方案协议栈封装得太死,想调…

作者头像 李华
网站建设 2026/9/29 18:47:53

LightRAG构建中药知识图谱:六种检索模式效能对比与调优实践

1. 项目缘起与整体设计思路中药知识体系有个很麻烦的特点:概念之间关系极其密集,而且很多关系是“多对多”的。比如一味黄芪,它同时涉及补气、固表、利水、托毒等多个功效维度,每个功效又关联到不同的方剂、证候、药材配伍。传统的…

作者头像 李华
网站建设 2026/9/29 18:47:17

RS485总线乱码排查:偏置电阻方向错误导致A下拉B上拉的修复实录

一块自研的USB转RS485主站板,带三条从机总线,现场一上电,串口助手就开始间歇性刷0x00字节流;主站轮询从机,十次里总有那么两三次应答是错乱的,换过晶振、查过电源、换过USB转485模块,问题依旧。…

作者头像 李华