新闻详情

新闻详情

首页 / 资讯中心 / 详情

从零手搓AI工程:上下文管理、重试降级与缓存设计实战

发布时间:2026/9/30 4:44:43来源:尧图网络
从零手搓AI工程:上下文管理、重试降级与缓存设计实战
1. 从零搭建AI工程能力为什么“手搓”比调包更值得投入这两年AI应用层的工具链成熟得吓人一个下午就能用现成框架拼出一个能跑通的demo。但如果你真在团队里带过人、接过需求就会发现一个尴尬的现实能调通API的人一抓一大把能把一个AI功能稳定扛住线上流量、控制住成本、还能持续迭代的人少得可怜。ai-engineering-from-scratch这个标题戳中的正是这个断层——它讲的不是“怎么用某个库”而是从零开始把AI工程这条链路自己走一遍把每个环节的“为什么”搞清楚。我自己是从传统后端转过来的早期也走过弯路一开始觉得模型调用嘛不就是发个请求解析个返回。直到线上出现超时雪崩、上下文被截断、成本一个月翻三倍才意识到AI工程和普通业务开发在工程约束上完全是两码事。这个项目适合三类人一是想从“会调API”进阶到“能扛系统”的开发者二是需要给团队搭建AI能力底座的技术负责人三是对底层原理好奇、不想永远当黑盒使用者的学习者。它解决的核心问题就一个——把AI应用从“能跑”变成“能扛、能控、能迭代”。下面我会按我实际落地时的思路把整条链路拆开讲整体架构怎么设计、核心环节怎么实现、参数怎么算、坑怎么避。所有内容都是基于常见工程实践补全的你照着抄作业基本能复现一套属于自己的最小可用系统。2. 整体架构设计与技术选型思路2.1 为什么先定边界再选工具从零做AI工程最容易犯的错是一上来就纠结用哪个框架。我的经验是先把边界画清楚这个系统要处理的是在线实时请求还是离线批量任务输入是短文本还是长文档对延迟的容忍度是几百毫秒还是几秒这几个问题决定了后面所有选型。举个具体的例子。如果是在线对话场景延迟敏感那模型推理就必须考虑流式输出和首token时间如果是离线文档处理那吞吐量和成本才是第一优先级批处理、并发控制、失败重试的设计完全不同。我见过有人拿离线那套同步阻塞的写法直接搬到在线服务上结果QPS一上来整个服务卡死。所以第一步不是写代码是画一张数据流图标清楚每个环节的输入输出、延迟预算、失败后的行为。提示延迟预算要提前分配。比如端到端要求2秒那模型推理最多给1.2秒检索给0.3秒前后处理各留0.25秒。不提前分后面优化时你根本不知道瓶颈在哪。2.2 分层架构把易变的和稳定的隔开我采用的是一种朴素但极其有效的分层接入层、编排层、能力层、基础设施层。接入层负责协议转换、鉴权、限流编排层负责流程控制、上下文管理、重试降级能力层是模型调用、检索、工具执行这些具体能力基础设施层是缓存、日志、监控、配置。这么分的好处是模型供应商换了、检索方案换了只动能力层编排逻辑不用重写。我早期把所有逻辑揉在一个函数里后来想换个模型做A/B测试改得痛不欲生。分层之后能力层做成统一接口换实现就是换个类的事。层级职责易变程度典型技术点接入层协议、鉴权、限流低网关、令牌桶编排层流程、上下文、降级中状态机、重试策略能力层模型、检索、工具高适配器模式、接口抽象基础设施缓存、日志、监控低连接池、埋点2.3 从零实现的价值到底在哪有人会问这些框架不都帮你做了吗我的回答是框架帮你做了但出问题时你得能定位。我遇到过缓存穿透导致模型被疯狂调用、遇到过重试策略写错导致请求翻倍、遇到过上下文拼接把token撑爆。这些问题的根因都在框架的“黑盒”里你不自己实现一遍排查时就是盲人摸象。从零实现还有一个隐性收益你会对成本有肌肉记忆。自己算过token、自己统计过调用量做容量规划时心里才有数。我现在的习惯是任何AI功能上线前先手算一遍最坏情况下的单次成本再乘以预估峰值QPS得出一个上限这个数字直接决定我要不要加缓存、要不要做降级。3. 核心环节拆解与实操要点3.1 上下文管理AI工程最容易翻车的地方上下文管理听起来简单做起来全是细节。核心要解决三个问题放什么进去、放多少、怎么放。放什么进去取决于任务。对话场景要保留历史轮次但要剔除无关的寒暄文档问答要放检索到的片段但要按相关性排序。我的做法是维护一个“上下文预算”比如模型窗口是8K token我预留2K给输出剩下6K给输入其中系统提示占500历史对话占2000检索片段占3500。每个部分都有独立的裁剪策略。放多少就是token计算。不同模型的tokenizer不一样中文和英文的切分差异很大。我实测下来中文大致是1个汉字对应1到1.5个token英文是1个单词对应1.3个token左右。但这个只能估算精确计算必须用对应模型的tokenizer。我一般会在编排层做一个token计数中间件每次拼接完上下文先算一遍超了就按优先级裁剪。怎么放涉及顺序和格式。经验是最重要的信息放开头和结尾中间容易被模型忽略。系统指令放最前面当前问题放最后面检索片段按相关性从高到低排列。格式上用清晰的分隔符比如用三个短横线隔开不同片段模型对结构化输入的遵循度明显更高。def build_context(system_prompt, history, retrieved_docs, current_query, max_tokens): # 优先级系统提示 当前问题 检索片段 历史 budget max_tokens parts [] sys_tokens count_tokens(system_prompt) parts.append(system_prompt) budget - sys_tokens query_tokens count_tokens(current_query) budget - query_tokens # 检索片段按相关性排序后填充 for doc in sorted(retrieved_docs, keylambda d: d.score, reverseTrue): doc_tokens count_tokens(doc.text) if doc_tokens budget: parts.append(doc.text) budget - doc_tokens else: break # 剩余预算给历史从最近往远填 for turn in reversed(history): turn_tokens count_tokens(turn) if turn_tokens budget: parts.insert(1, turn) budget - turn_tokens else: break parts.append(current_query) return \n---\n.join(parts)注意裁剪历史时一定要从最旧的开始丢但系统提示和当前问题永远不能丢。我见过有人把当前问题裁掉了模型答非所问排查半天才发现是裁剪逻辑写反了。3.2 模型调用层重试、超时、降级一个都不能少模型调用是外部依赖外部依赖就会失败。失败分几种网络超时、限流、服务端错误、返回内容异常。每种的处理策略不一样。超时要设两层连接超时和读取超时。连接超时短一点比如2秒读取超时长一点因为模型生成需要时间流式的话可以设30秒。我早期只设了一个总超时结果连接阶段卡住把整个请求拖死。重试要区分错误类型。网络抖动、限流这种可以重试但要注意指数退避不然重试风暴会把下游打垮。我用的策略是第一次失败等1秒第二次等2秒第三次等4秒最多重试3次。服务端5xx错误可以重试4xx错误重试没意义直接降级。降级方案要提前设计。模型不可用时是返回缓存结果、返回兜底话术、还是走规则引擎我的做法是分级降级一级降级走缓存二级降级走轻量模型三级降级返回预设话术。每级降级都要有监控告警不然降级了没人知道问题被掩盖。import time import random def call_model_with_retry(payload, max_retries3): for attempt in range(max_retries): try: resp model_client.call(payload, timeout(2, 30)) if resp.status_code 200: return resp elif 500 resp.status_code 600: raise ServerError(resp.status_code) else: # 4xx 不重试直接抛 raise ClientError(resp.status_code) except (Timeout, ServerError) as e: if attempt max_retries - 1: return fallback(payload) # 指数退避 抖动 wait (2 ** attempt) random.uniform(0, 0.5) time.sleep(wait) return fallback(payload)3.3 缓存设计省下的都是真金白银AI调用贵缓存是性价比最高的优化。但缓存什么、怎么失效有讲究。缓存粒度上我分三层完全相同的请求直接返回缓存语义相近的请求走向量缓存相似度超过阈值就复用检索结果单独缓存因为检索比模型调用便宜但比内存贵。失效策略上对话类场景缓存命中率低因为上下文一直在变这时候缓存意义不大。但问答类、知识库类场景问题重复率高缓存能省一大半成本。我实测过一个客服问答场景加了语义缓存后模型调用量降了六成。向量缓存的阈值要调。设太高命中率低设太低答非所问。我的经验是从0.95开始往下试观察命中率和用户反馈找到一个平衡点。一般0.92到0.95之间比较稳。提示缓存key的生成要包含模型版本和关键参数。我踩过坑换了模型但缓存没失效返回的还是旧模型的结果排查了很久。4. 完整实操流程与关键参数计算4.1 环境准备与依赖管理从零搭建环境要干净。我用的是Python依赖管理用虚拟环境加锁定文件。核心依赖就几个HTTP客户端、tokenizer、向量库客户端、监控SDK。不要一上来装一堆框架先把最小依赖跑通。python -m venv venv source venv/bin/activate pip install httpx tiktoken numpy pip freeze requirements.txt版本锁定很重要。我遇到过tokenizer库升级后切分规则变了导致token计数全错上下文频繁超限。锁定版本升级前先在测试环境验证。4.2 参数计算并发数、超时、重试的量化并发数不是拍脑袋定的。假设模型服务单实例QPS是10你有3个实例那理论并发是30。但你要留余量实际按70%算也就是21。再考虑你的服务本身能扛多少并发取两者最小值。超时计算P99延迟是2秒那读取超时设3秒比较合理留50%余量。连接超时按网络RTT的3倍算一般2秒够用。重试次数假设单次失败率1%重试3次后失败率降到百万分之一。但重试会增加下游压力所以重试次数和退避时间要一起算。我的公式是总重试时间 退避基数 × (2^重试次数 - 1)这个时间不能超过上游的总超时预算。参数计算依据推荐值连接超时网络RTT×32秒读取超时P99延迟×1.53秒最大重试失败率与压力平衡3次退避基数下游恢复时间1秒并发数min(下游容量×0.7, 自身容量)动态调整4.3 监控埋点没有度量就没有优化监控要埋四个维度延迟、错误率、token消耗、缓存命中率。延迟分P50、P95、P99错误率分类型token消耗分输入输出缓存命中率分完全匹配和语义匹配。我用的埋点方式是在编排层统一拦截每个环节进出都打点。这样不管能力层怎么换监控数据是连续的。埋点数据要能下钻比如发现P99延迟高能快速定位是模型慢还是检索慢。import time class MetricsMiddleware: def __init__(self, next_handler): self.next next_handler def handle(self, request): start time.time() try: result self.next.handle(request) self.record(success, time.time() - start, result.tokens) return result except Exception as e: self.record(error, time.time() - start, 0, type(e).__name__) raise4.4 上线前的压测与容量规划上线前必须压测。压测不是简单打满QPS要模拟真实流量分布长短请求混合、缓存命中与未命中混合、正常与异常混合。我一般用阶梯加压从10%峰值开始每5分钟加10%观察各项指标。容量规划的核心是找到拐点。当QPS增加但吞吐不再增加、延迟开始飙升时就是拐点。实际容量取拐点的70%。这个数字要写进容量文档扩容时按这个基准算。注意压测环境要和线上环境尽量一致尤其是网络延迟和下游服务的限流策略。我见过压测时好好的上线就崩因为压测环境没开限流。5. 常见问题与排查技巧实录5.1 超时与雪崩的排查思路超时雪崩的典型表现是一开始只是少量超时然后重试导致请求翻倍下游压力更大超时更多形成正反馈。排查时先看重试次数和退避策略再看下游容量。我的处理步骤第一步临时关闭重试切断正反馈第二步看下游监控确认是下游问题还是自身问题第三步如果是下游问题降级如果是自身问题限流。恢复后要复盘重试策略是不是太激进超时设置是不是太短。5.2 上下文超限的定位方法上下文超限报错通常很模糊只说超了不说哪超了。我的做法是在拼接前打印各部分token数超限时直接看日志。常见原因历史没裁剪、检索片段太多、系统提示太长。排查顺序先看系统提示是不是写了一大堆没用的再看检索片段是不是top_k设太大了最后看历史是不是没设上限。我一般把top_k设在3到5历史轮次设在5到10系统提示控制在500token以内。5.3 成本异常的快速定位成本突然涨先看调用量再看单次token。调用量涨可能是缓存失效、重试过多、被刷单次token涨可能是上下文变长、输出变长。我遇到过一次成本翻倍排查发现是缓存key里包含了时间戳导致缓存永远不命中。还有一次是重试策略写错失败后无限重试。所以成本监控要细到每个环节不能只看总数。问题现象可能原因排查动作解决方向延迟飙升下游慢/重试风暴看重试次数、下游监控降级、限流、调退避成本翻倍缓存失效/重试过多看命中率、调用量修缓存key、调重试答非所问上下文裁剪错/检索差看拼接日志、检索相关性调裁剪优先级、调top_k频繁超限历史未裁剪/top_k过大看各部分token数设上限、减top_k5.4 独家避坑经验第一个坑不要用同步阻塞的方式调模型。我早期用requests同步调QPS一上来线程池就满了。后来换成异步吞吐量翻了好几倍。第二个坑日志不要打全量上下文。上下文里有用户数据打全量既占空间又有隐私风险。我一般只打token数和hash值需要排查时再按请求ID捞。第三个坑降级方案要定期演练。我见过降级代码写了但从来没跑过真到降级时发现报错等于没降。现在我每个月手动触发一次降级确保链路是通的。第四个坑模型版本要固定。供应商悄悄升级模型是常事行为可能变化。我在请求里显式指定版本号升级前先做回归测试。6. 从能跑到能扛我的落地体会这套东西我从零搭过两遍第一遍踩坑无数第二遍顺畅很多。最大的体会是AI工程的难点不在AI在工程。模型能力是供应商给的但稳定性、成本、可观测性是你自己的。把上下文管理、重试降级、缓存、监控这几块做扎实系统就能扛住大部分场景。还有一个心得是不要追求一步到位。我第一版就上了全套监控和降级结果复杂度太高自己都维护不动。后来改成先跑通主链路再逐步加缓存、加重试、加监控每加一块都验证一块反而更稳。从零开始的价值就是你知道每一块砖是怎么砌上去的哪块松了你一眼就能看出来。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Vue3动态菜单与路由权限实战:基于RuoYi的完整落地指南 2026/9/30 5:40:34

Vue3动态菜单与路由权限实战:基于RuoYi的完整落地指南

1. 动态菜单不是“加个数组就行”,而是权限体系落地的第一道关卡在 Vue3 后台管理系统开发中,我见过太多团队把“动态菜单”简单理解成“后端返回一个菜单数组,前端 for 循环渲染一下”。结果上线后问题不断:用户明明有权限访问某…

阅读更多 →
自动标注流水线实战:三工具串联,实例分割效率翻倍 2026/9/30 5:40:33

自动标注流水线实战:三工具串联,实例分割效率翻倍

标注这个词,做CV的人听了都头疼。我前阵子接了一个实例分割项目,两千多张图,每张图里少说三五个目标对象,复杂一点的要标出遮挡、边缘、轮廓。按传统方式走,熟练标注员一张图也得两三分钟打底,算下来就是四…

阅读更多 →
I2C通信故障排查全攻略:从万用表到示波器再到ACK逐层定位 2026/9/30 5:40:33

I2C通信故障排查全攻略:从万用表到示波器再到ACK逐层定位

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
系统门窗跟普通门窗有何区别?主流品牌参考 2026/9/30 5:40:33

系统门窗跟普通门窗有何区别?主流品牌参考

最近在看门窗,才发现系统门窗和普通门窗真不是一回事。它讲究的是型材、五金、密封和玻璃整套匹配,隔音隔热安全这些性能才更完整。挑牌子不能光看名气,得看硬实力:有没有自有工厂、参没参与国标起草、工程案例大不大。像派雅、皇…

阅读更多 →
Windows更新错误代码详解:从定位到一键修复的完整排查指南 2026/9/30 5:40:33

Windows更新错误代码详解:从定位到一键修复的完整排查指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Coze接入自定义模型:Ace Data Cloud对接OpenAI兼容API的完整指南 2026/9/30 5:40:27

Coze接入自定义模型:Ace Data Cloud对接OpenAI兼容API的完整指南

想把 Coze(扣子)里的 Bot 能力从“内置模型”扩展到自定义模型,最省事的方式不是等平台把千奇百怪的模型都接好,而是直接找到一条兼容 OpenAI Chat Completions 协议的 API 通道。Ace Data Cloud 正好提供这种接口。这篇分享就记录…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉