我终于看懂了个人网站里的 AI 调用链路
接入大模型不难,真正重要的是能不能把一次模型调用拆成可配置、可失败、可定位、可继续改造的工程链路。
从“能跑通”到“能说清楚”
以前我以为“接入大模型”最重要的是会不会调用 API。学完第一周后,我发现真正难的不是把请求发出去,而是能不能说清楚:一个用户在个人网站 AI 助手里输入问题之后,这个请求经过了哪些层,用了哪些配置,在哪里可能失败,以及失败以后前端为什么能收到一个可识别的错误码。
这周我没有从零写一个 Demo,而是回到自己的 my-world 个人网站,拆了一遍已经存在的 AI 助手调用链路。这个站点当前接入的是百炼大模型,并且通过 OpenAI-compatible API 调用。也就是说,项目不是把“百炼调用逻辑”散落在业务接口里,而是在中间加了一层 Provider,把模型服务商的调用细节封装起来。
这个变化让我对 AI 应用工程化的理解变得具体了很多:不是多写一个 prompt,也不是只把模型返回结果展示到页面上,而是让每一次模型调用都有稳定入口、稳定配置、稳定响应和稳定排查路径。
一个聊天请求背后并不只是聊天
在 my-world 里,前端 AI 助手发起请求时,会把几个关键字段交给后端:
字段 | 作用 |
|---|---|
message | 用户当前输入的问题 |
history | 当前会话里的历史消息 |
pagePath | 用户提问时所在页面,用于辅助上下文判断 |
sessionId | 匿名会话 ID,用于基础限流 |
后端接口收到请求后,不会立刻调用模型。它会先检查 AI 助手是否启用,再解析请求体,清洗 message、history、pagePath 和 sessionId。如果 message 缺失或为空,会直接返回 INVALID_MESSAGE;如果输入过长,会返回 MESSAGE_TOO_LONG。这些错误都发生在模型调用之前,不会消耗模型额度。
通过基础校验后,后端还会做限流、构建助手上下文,并经过 guardrail 判断。如果问题不适合交给模型,后端可以直接返回安全回答。只有这些步骤都通过,才会创建 Provider,读取模型配置,然后真正调用大模型。
这条链路让我意识到,AI 聊天功能表面上是“用户问,模型答”,但工程上更像是一条带边界的请求管道。每一层都要尽量早地挡住不合法输入,尽量晚地消耗模型调用成本。
Provider 是我这周真正看懂的一层
我以前对 Provider 的理解比较模糊,只知道它大概是“封装一下第三方接口”。这周看完调用链路后,我对它的定位清楚了很多。
Provider 不是 OpenAI-compatible API 本身,而是项目内部的大模型服务适配层。OpenAI-compatible API 是外部服务商尽量兼容的一套接口风格,通常包括请求路径、请求字段、响应字段和错误结构。但不同服务商不可能完全一样,模型名、错误码、参数支持、usage 统计、流式协议都可能有差异。
所以业务接口不应该到处直接写百炼调用逻辑。业务层最好只表达一件事:我要生成一次助手回复。至于 baseURL 怎么拼、API key 怎么放请求头、model 怎么传、模型响应怎么解析、错误怎么归一化,都应该集中在 Provider 层处理。
在当前链路里,Provider 会请求百炼的 /chat/completions 接口,传入 model、messages、temperature、max_tokens 等参数。模型服务商返回后,Provider 会解析响应。如果返回不是合法 JSON、HTTP 状态失败,或者没有有效回答,就抛出统一错误;如果成功,就整理成内部统一结果,包含回答内容、token 用量和供应商请求 ID。
这就是 Provider 的价值:它把业务逻辑和模型服务商之间隔开,让未来切换供应商、补流式输出、补重试、补日志时,不至于把业务接口改得一团乱。
五个配置项决定调用能否稳定
第一周我重点梳理了 baseURL、API key、model、timeout 和 retry。它们看起来只是配置项,但每一个都对应一种真实的故障。
baseURL 是模型服务商 API 的基础地址,可以理解为请求要走哪扇门。当前百炼 Provider 有默认地址,通用 openai-compatible 分支则需要显式配置。配错以后,可能出现网络错误、404、401,或者返回结构和项目预期不一致。
API key 是服务端调用模型服务的密钥。它不能放在浏览器,因为前端代码和网络请求都可能被查看。一旦泄露,别人就可以消耗模型账号额度,甚至触发风控。在当前项目里,API key 只从服务端环境变量读取,并作为 Authorization: Bearer ... 发送给模型服务商。
model 是服务商侧的模型标识,不是本地代码里加载的模型。当前助手模型来自后台 ai-settings.assistantDefaultModel,如果后台读取失败或配置不合法,会回退到代码里的内置默认值 qwen-plus。这点很重要,因为模型名不是随便写一个字符串就能用,它必须是当前服务商支持的模型。
timeout 决定后端最多等模型多久。AI 请求通常比普通接口慢,但不能无限等待。当前 Provider 使用 AbortController 做超时中断,超过时间后会返回 MODEL_TIMEOUT。这能保护接口不被长时间占用,但如果设置得过短,也会误杀正常生成。
retry 是当前最值得补的一项。聊天 Provider 目前还没有真正的自动重试逻辑。理论上,网络抖动、供应商 5xx、部分 timeout 可以有限重试;但参数错误、API key 错误、模型不存在、余额不足、内容安全拦截这类确定性失败不应该重试。否则只是把一次明确失败变成多次浪费。
故意失败,比只看成功更能证明理解
这一周最有帮助的练习,不是把正常请求跑通,而是故意制造失败。
我先把后台模型配置成当前厂家不支持的模型。结果后端返回 MODEL_PROVIDER_ERROR,message 是 Model not exist.。这个结果说明,请求已经到达 Provider,并且 Provider 已经调用了第三方模型接口;失败来自模型服务商的非成功响应,最后被统一包装成业务侧可识别的错误结构。
然后我去掉本地 ASSISTANT_API_KEY。这次返回的是 ASSISTANT_INTERNAL_ERROR,message 是 AI 助手 API Key 未配置。。这说明错误并不是第三方返回的 401,而是在 Provider 构造阶段就发现服务端配置缺失,根本没有真正请求模型服务。
再比如去掉 message 字段,返回的是 INVALID_MESSAGE。这个失败发生在业务路由的参数清洗阶段,还没有构建上下文,也没有调用 Provider。把超时时间设置成 1ms,则会触发 MODEL_TIMEOUT,说明 Provider 能够中断本地等待,不会无限卡住。
我还验证了两个更偏工程保护的场景:当 ASSISTANT_ENABLED=false 时,接口会在最前面的功能开关处拦截;当输入超过 ASSISTANT_MAX_INPUT_LENGTH 时,会在 sanitizeMessage 阶段拦截。这两个验证都说明,好的 AI 接口应该尽早判断能不能继续,而不是把所有问题都丢给大模型。
这些失败验证让我第一次比较清楚地知道:同样是“AI 助手不可用”,背后可能是完全不同的层级在失败。
现象 | 大概发生层级 | 优先排查 |
|---|---|---|
模型不存在 | Provider 已请求第三方模型服务 | 后台模型配置、服务商支持模型、provider/baseURL/key 是否匹配 |
API key 缺失 | Provider 构造阶段 | 环境变量是否配置、服务是否重启 |
message 缺失 | 后端参数校验阶段 | 前端请求体字段和类型 |
请求超时 | Provider 等待模型响应阶段 | timeout 配置、模型服务状态、网络情况 |
功能关闭 | 后端入口最前面 | ASSISTANT_ENABLED 配置 |
输入过长 | 后端参数清洗阶段 | 输入长度限制和前端提示 |
看懂链路以后,下一步不是急着加功能
第一周结束时,我没有把问题清单写得很长,只保留了两个当前最影响接单交付的点。
第一是 AI 调用日志没有持久化记录。现在主要依赖 console.info 和 console.warn,能看到一些基础信息,但如果以后用户反馈某次失败,只靠临时日志很难稳定关联请求参数、模型、错误码、耗时、token、供应商请求 ID 和最终响应。真正适合交付的 AI 应用,应该有一张调用记录表或集合,让排查人员只看记录就能初步判断问题发生在哪一层,而不是为了复现反复消耗模型额度。
第二是模型调用缺少有限重试机制。临时网络抖动、供应商 5xx、部分 timeout 现在可能直接变成失败,前端看到失败的概率会更高。这个能力最好补在 Provider 层,并且要明确重试边界:只重试临时性错误,不重试参数错误、API key 错误、模型不存在、余额不足和内容安全拦截。
这两个问题也让我看清楚下一阶段要补的能力:结构化输出、schema 校验、失败重试、调用日志、成本统计、生产级限流、RAG 和工具调用。它们不是“AI 高级功能”的堆叠,而是让 AI 应用从能跑通走向能交付的工程闭环。
第一周对我来说最重要的收获,是把“我接入了一个大模型”拆成了更具体的判断标准:
- 我能说清楚请求从前端到模型服务商经过哪些层。
- 我能说清楚
baseURL、API key、model、timeout、retry分别影响什么。 - 我能通过失败返回判断问题大概发生在哪一层。
- 我知道当前功能离接单交付还差哪些工程化能力。
这比单纯跑通一次模型调用更重要。因为客户项目里,真正考验人的往往不是“第一次成功”,而是失败发生后,你能不能快速定位、稳定修复,并把这个问题沉淀成下一次不会重复踩的系统能力。
有想法或建议? 联系我