实战指南:AI调用成本降71%——利用“推理路由”告别大模型胡乱开销 大多数 AI 应用在刚开始时都会在代码中硬编码一个模型。对于原型开发来说这运行得很好但一旦单个端点需要处理多个复杂的任务类别这种模式就会分崩崩离析。分类、紧急程度评分、面向客户的草稿以及长篇总结这些任务由于成本、延迟或质量要求各不相同都能够从不同的模型选择中获益。支持工单的分流Support Triage就是最典型的例子。当用户输入“如何重置密码”时你为每个 Token 支付的费用与处理一个粘有海量日志、来自企业客户的多段落升级工单是一样的。你当然可以在应用代码中根据工单类型进行分支判断并为每个分支选择不同的模型但这样一来你的模型选择逻辑就混在了业务处理程序中你的兜底策略Fallback Strategy变成了try/except代码块而且每次价格变动都意味着需要重新部署应用。其后果就是你可能会用一个 70B 的模型去对只有几个词的工单进行分类当该模型变慢时没有任何兜底方案以及每次价格调整时都必须重新部署代码。在本教程中我们将使用通过 DigitalOcean 推理路由器Inference Router实现的无服务器推理Serverless Inference轻松快速地构建一个 FastAPI 支持工单分流端点一次性解决所有这些问题。到最后你将能够自动将分类、紧急程度评分、客户回复和升级总结路由到最适合的模型上——这一切都是自动完成的内置了兜底机制且应用代码中不会出现任何一个具体的模型名称。最终你将拥有一个生产级别的 API其成本比在单一前沿模型上运行所有任务要便宜 71%。构筑目标让我们构建一个单一的端点POST /triage它接收工单数据并返回分类Classification问题类别账单、Bug、指南、账户等紧急程度 情感Urgency Sentiment严重程度评分以及对客户情绪的解读回复草稿Drafted Reply简短的、面向客户的回复升级总结Escalation Summary为人工客服提供的结构化简报仅在工单足够复杂、需要升级时才会生成架构演变如下应用 → 硬编码模型一个模型处理所有任务转变为应用 → 通过推理路由器进行无服务器推理 → 每个任务匹配最合适的模型正是这个推理路由器使得第二种架构成为可能而你的应用甚至不需要知道存在哪些模型。无服务器推理与 DigitalOcean 推理路由器推理路由器 允许你定义任务Tasks和模型池Model Pools然后根据这些任务定义和选择策略将输入的提示词Prompts路由到最合适的模型。任务是一个带有描述的命名工作例如classify_ticket。模型池是路由器可以为该任务挑选的候选模型集合由选择策略最低成本、最低延迟、手动设置的排名或兜底顺序来控制。你只需在路由器层配置一次应用就会调用路由器而不是任何特定的模型。无服务器推理让你无需创建 AI 智能体Agent或操心基础设施管理即可向模型发送 API 请求。这能让你快速上手无需管理推理端点背后的任何组件。该 API 接口与 OpenAI 兼容。基础 URLBase URL为https://inference.do-ai.run/v1/单个模型访问密钥Model Access Key即可同时覆盖基座模型和路由器。项目初始化要继续后续步骤你需要 Python 3.10、一个启用了无服务器推理的 DigitalOcean 账户 以及一个模型访问密钥。为了方便起见我们已经在这个 GitHub 仓库 中配置好了完整的项目。但建议你跟随接下来的章节一起动手构建自己的 API 版本以此了解我们为该 API 做出特定选择的原因。注如果你是新注册DigitalOcean的用户无服务器推理中的Claude、OpenAI 暂时无法使用可以直接与卓普云aidroplet.com联系申请开通这两种商业模型的权限。项目布局刻意保持得非常精简support-triage/ ├── main.py ├── sample_tickets.json ├── requirements.txt └── .envmain.py存放应用代码requirements.txt包含所需的依赖包sample_tickets.json是用于测试路由器的样本数据而.env则存放必需的密钥、访问凭证和基础 URL 值。首先将仓库克隆到你的本地机器上并在终端中粘贴以下代码来安装所有内容git clone https://github.com/Jameshskelton/triage_app cd triage_app python3 -m venv venv_triage source venv_triage/bin/activate pip install -r requirements.txtOpenAI SDK 可以直接用于 DigitalOcean 的无服务器推理你只需要将base_url和api_key指向 DigitalOcean而不是 OpenAI。步骤 1基准线——直接调用模型在开始接触路由器之前我们先写一个大多数开发者最先会写出来的版本硬编码一个模型让它处理所有四项工作。接下来的几个步骤章节概述了我们构建应用演示所做的工作。如果你只想测试最终版本可以直接查看我们存放该项目的仓库。首先我们创建main.pyimport os import re from openai import OpenAI from fastapi import FastAPI from pydantic import BaseModel from dotenv import load_dotenv load_dotenv() client OpenAI( base_urlos.environ[DO_INFERENCE_BASE_URL], api_keyos.environ[DO_MODEL_ACCESS_KEY], ) MODEL llama3.3-70b-instruct # 一个模型处理所有任务 app FastAPI() class Ticket(BaseModel): subject: str body: str def call_model(system: str, user: str) - str: resp client.chat.completions.create( modelMODEL, messages[ {role: system, content: system}, {role: user, content: user}, ], ) return resp.choices[0].message.content.strip() app.post(/triage) def triage(ticket: Ticket): text fSubject: {ticket.subject}\n\n{ticket.body} category call_model( Classify this support ticket into one of: billing, bug, how-to, account, other. Reply with one word., text, ) urgency call_model( Score urgency from 1 (low) to 5 (critical) and note sentiment. Reply as score: N, sentiment: X., text, ) reply call_model( Write a short, professional reply to this customer. Maximum 4 sentences., text, ) summary call_model( Summarize this ticket for a human agent. Include the problem, whats been tried, and recommended next steps., text, ) return { category: category, urgency: urgency, reply: reply, escalation_summary: summary, }如果我们正确设置了包含相应 API 密钥和数值的.env文件就可以使用以下代码运行它uvicorn main:app --reload让我们用两个工单一个简单的一个复杂的来测试它并审计其结果。测试输入 1curl -X POST localhost:8000/triage -H Content-Type: application/json -d { subject: Password reset, body: How do I reset my password? }测试输出 1{ category: account, urgency: score: 1, sentiment: neutral, reply: You can reset your password by selecting the Forgot password link on the sign-in page and following the email instructions. If you do not receive the reset email, check your spam folder or contact support for help., escalation_summary: The customer is asking how to reset their password. No signs of account compromise, outage, or escalation risk. Recommended next step: provide standard password reset instructions. }测试输入 2curl -X POST localhost:8000/triage -H Content-Type: application/json -d { subject: Production outage on enterprise account, body: Our team has been unable to access the dashboard since 09:14 UTC. We have ~200 internal users blocked. Attached are logs showing 502s from the API gateway... }这将为我们返回类似如下的测试输出 2{ category: bug, urgency: score: 5, sentiment: frustrated, reply: Thank you for reporting this. We understand that a production dashboard outage affecting around 200 users is urgent, and we are escalating this to our engineering team immediately. Please continue to share any relevant logs or timestamps while we investigate., escalation_summary: Enterprise customer reports a production dashboard outage beginning at 09:14 UTC. Approximately 200 internal users are blocked. Logs indicate 502 responses from the API gateway. Recommended next steps: escalate to engineering, inspect gateway and upstream service health, correlate errors around 09:14 UTC, and provide the customer with frequent status updates. }两个回复都很有用。这也正是为什么这种硬编码的基准方案非常具有诱惑力。但看看刚刚发生了什么同一个 70B 的模型处理了所有事情。模型将“如何重置我的密码”分类到一个简单的类别中给紧急程度打分起草了简短的回复还写了一份该工单根本不需要的升级总结。然后它处理了企业停机故障而在这种场景下使用较大的模型才真正合乎逻辑。这就是问题所在。简单的工单和生产环境的停机事故在成本、延迟和质量上有极大的不同但应用却将它们一视同仁。你正在为简单的工作支付昂贵的溢价如果模型变慢或不可用应用没有任何兜底方案而且任何模型选择的变更都意味着修改应用代码并重新部署。让我们来解决这个问题。步骤 2配置推理路由器在 DigitalOcean 控制面板 中使用左侧边栏导航至“Inference Router推理路由器”。然后创建一个新的推理路由器。为你的路由器命名并对其功能进行描述。例如我们将我们的路由器命名为triage-router并将其描述为“Demo Triage API for DO tutorial”。接着路由器需要创建四个任务每个任务都需要包含描述以及带有选择策略的模型池。具体配置如下表所示。如果你想复制它们来重现这个实验可以将这些值逐个复制并粘贴到路由器的各个任务中。这将产生与我们类似的概率性结果。任务名称描述输入给路由器模型池策略classify_ticket讲简短的支持消息归类为问题类型账单、Bug、指南、账户。最低成本 (Lowest cost)urgency_detection单次运行中检测严重程度、情感和升级风险。最低延迟 (Lowest latency)draft_customer_reply生成简短、专业的面向客户的回复。手动排名 (Manual ranking)escalate_complex_issue将复杂工单总结为供人工客服查看的结构化简报。手动排名 (Manual ranking)在创建描述、选择路由器优先级策略以及选择模型时我们需要考虑想要完成的确切任务以优化我们的结果。以下是几点值得注意的事项任务描述至关重要。路由器利用它们将传入的请求与正确的任务进行匹配。请具体描述任务的作用、期望接收什么样的输入以及输出的格式。每个模型池中至少放入两个模型。只有一个模型的模型池是单点故障。即使是你的“最低成本”池也应该有一个兜底模型以防主模型不可用。选择策略是在池内部执行 supply 的而不是跨池执行。“最低成本”指的是“该模型池中当前健康的最便宜模型”而不是“整个平台里最便宜的模型”。路由器保存成功后你将获得一个路由器 IDRouter ID。这就是你的应用后续要调用的目标。步骤 3重构应用以使用路由器现在到了让人身心愉悦的部分。将硬编码的MODEL常量替换为路由器 ID并通过请求传入任务名称。以下是一个具体实现的示例虽然这与我们最终发布的版本不完全相同但能很好地说明其工作原理。ROUTER your-router-id # 来自 DigitalOcean 控制面板 def parse_urgency(urgency_text: str) - int: 从 score: N, sentiment: X 中提取整数评分。如果无法解析默认返回 3。 match re.search(rscore:\s*(\d), urgency_text, re.IGNORECASE) return int(match.group(1)) if match else 3 def call_router(task: str, system: str, user: str) - dict: resp client.chat.completions.create( modelROUTER, messages[ {role: system, content: system}, {role: user, content: user}, ], extra_body{task: task}, # 路由器利用此字段来挑选模型池 ) return { content: resp.choices[0].message.content.strip(), served_by: resp.model, # 路由器实际挑选的模型 } app.post(/triage) def triage(ticket: Ticket): text fSubject: {ticket.subject}\n\n{ticket.body} category call_router( classify_ticket, Classify this support ticket into one of: billing, bug, how-to, account, other. Reply with one word., text, ) urgency call_router( urgency_detection, Score urgency from 1 (low) to 5 (critical) and note sentiment. Reply as score: N, sentiment: X., text, ) reply call_router( draft_customer_reply, Write a short, professional reply to this customer. Maximum 4 sentences., text, ) # 仅在紧急程度需要人工介入时才进行升级总结 urgency_score parse_urgency(urgency[content]) summary None if urgency_score 4: summary call_router( escalate_complex_issue, Summarize this ticket for a human agent. Include the problem, whats been tried, and recommended next steps., text, ) return { category: category[content], urgency: urgency[content], urgency_score: urgency_score, reply: reply[content], escalation_summary: summary[content] if summary else None, routing: { classify_ticket: category[served_by], urgency_detection: urgency[served_by], draft_customer_reply: reply[served_by], escalate_complex_issue: summary[served_by] if summary else None, }, }这就是全部的改动。我们在 GitHub 版本中已经为你处理好了这些因此无需你亲自动手手动修改。现在应用的任何地方都不再包含具体模型的名称。路由器会根据你配置的策略决定由哪个模型来处理每个任务。如果下个月你想更换draft_customer_reply底层的模型你只需要在路由器中操作而无需修改此文件。应用不再要求一个模型同时处理所有事情而是将一个工单拆分为更小的 AI 任务来进行分流。当你调用POST /triage时main.py会构建工单文本然后向路由器发送独立的调用classify_ticket决定工单类别如账单、Bug、指南、账户或其他。urgency_detection对严重程度进行 1 到 5 的评分并检测情感代码利用该评分来决定是否需要升级。draft_customer_reply编写一封简短的面向客户的回复。escalate_complex_issue在紧急程度评分达到 4 或 5 时触发升级总结较低的评分会直接跳过此步骤这也是大部分成本节省的来源。核心关键在于应用始终将.env中的 DigitalOcean 路由器 ID 作为“model”进行调用而由路由器去决定应该由哪个底层模型来处理每个提示词。步骤 4通过路由器运行混合工单将路由器接入后我们来测试一下。当向端点输入简单和复杂交织的混合示例时其展现出的行为非常有趣。以下是sample_tickets.json中包含的一小批涵盖简单到复杂的示例[ {subject: Password reset, body: How do I reset my password?}, {subject: Invoice question, body: Why was I charged twice on invoice INV-3382?}, {subject: This is ridiculous, body: Third time this week your dashboard has gone down during our standup. Were seriously evaluating alternatives.}, {subject: Dashboard weird, body: the dashboard is weird since yesterday}, {subject: Production outage, body: Our team has been unable to access the dashboard since 09:14 UTC. ~200 internal users blocked. Logs attached show 502s from the API gateway, traced to...}, {subject: Feature request complaint, body: Can you add bulk export? Also the existing export is too slow and crashes on 10k rows.}, {subject: API auth, body: Getting 401s after rotating my key. Following the docs at /auth/rotate but the new key returns invalid.} ]为了方便按顺序测试它们我们提供了run_batch.py来辅助测试。你可以运行以下命令来亲自执行python3 run_batch.py sample_tickets.json --json循环跑完这些数据你会看到路由器完美地各司其职。只有一行的“如何重置我的密码”在分类时触发了最低成本池并在处理紧急程度时匹配了一个小型、快速的模型。带有流失风险的愤怒消息会迅速被标记为高紧急度但由于该回复是要发给真实客户的因此生成的回复草稿来自更高质量的模型池。生产环境停机事故则被路由到高质量模型池来生成升级总结因为这份总结是给值班工程师在 UTC 09:15 分阅读的。因为call_router将resp.model作为served_by暴露了出来现在每个响应都会明确告诉你究竟是哪个模型处理了对应的任务。以下是生产环境停机工单返回的内容{ category: bug, urgency: score: 5, sentiment: frustrated, urgency_score: 5, reply: Thank you for reporting this..., escalation_summary: Enterprise customer reports a production dashboard outage..., routing: { classify_ticket: openai-gpt-5-nano, urgency_detection: anthropic-claude-haiku-4.5, draft_customer_reply: anthropic-claude-sonnet-4.6, escalate_complex_issue: anthropic-claude-opus-4.7 } }一次请求动用了四种不同的模型而在你的应用代码里不着一字没有一个模型名。便宜的分类器处理了单字分类的决定Haiku 快速单次跑出紧急程度评分Sonnet 起草了面向客户的回复而 Opus 生成了供值班工程师阅读的简报。如果你去跑密码重置工单routing.escalate_complex_issue字段返回的结果将是null—— 因为紧急度评分没有跨过阈值而这个null省下来的可是真金白银。推理路由究竟能为你省下多少钱让我们用数字说话。假设平均一张工单包含 300 个输入 Token输出 Token 因任务而异分类 40 个、紧急度 30 个、回复 150 个、升级总结 250 个。在我们的 7 个工单样本中有 2-3 个分数高到触发升级我们使用 20% 作为平稳状态下的预估比例。基于 DigitalOcean 官方公布的无服务器推理费率任务模型单张工单成本classify_ticketGPT-5 Nano$0.000031urgency_detectionClaude Haiku 4.5$0.000450draft_customer_replyClaude Sonnet 4.6$0.003150escalate_complex_issue约 20% 的工单触发Claude Opus 4.7$0.007750在每月 100,000 张工单的规模下三种策略的成本对比策略每月成本所有任务硬编码使用 Llama 3.3 70B$109路由器成本感知型$518所有任务硬编码使用 Claude Opus 4.7$1,775坦白来说路由器并不是最省钱的方案。硬编码使用 Llama 70B 才是。但代价是让 Llama 70B 去撰写你面对企业级停机事故时的客户回复。你之所以省了钱是因为你把一个具有流失风险的工单和密码重置工单放在了同等水平去对待。真正公平的对比应该和更务实的替代方案进行切磋一旦你判定 Llama 编写的客户回复质量达不到要求你的选择要么是“全盘皆用 Opus”要么是使用路由器。路由器比全盘使用 Opus 便宜了 71%它只在真正需要高昂成本的 Opus 4.7 模型的工单上才进行路由。在做出决定之前不妨先在自己的工单构成比例上算一算这笔账。简单工单与复杂工单的比例是影响结果的最大杠杆一个有 80% 密码重置工单的队列比一个有 80% 升级工单的队列能省下多得多的钱。生产环境清单在将此方案推向真实工单之前请确认以下事项记录每一次调用的任务类型、延迟、Token 使用量和所选模型。无法度量就无法调优没有针对每个任务的指标路由器的价值将变得不可见。为每个任务构建一个小型评估集。比如每个任务准备 20 个已知输出良好的工单。在更改模型池组合之前运行该评估集。路由器的核心意义就在于你可以在不改动代码的情况下更换模型但你依然需要知道这次更换是否带来了改进。确保每个模型池中至少保留一个兜底模型。只有一个模型的池直接让使用路由器的初衷折损了一半。使用直接模型调用来进行受控的基准测试。当你在测量某一特定模型的行为时你不希望路由器介入导致基准测试变得具有不确定性。每季度重新审视路由规则。模型的价格和质量一直在变。六个月前属于“最低成本”的池今天可能就不是了。将任务描述视为生产配置。对它们进行版本控制审查其变更绝不要在没有任何记录的情况下直接在 UI 界面中进行修改。结语你最终得到的应用并没有比刚开始时的庞大它反而变得更加精简因为模型选择的逻辑已经从代码中剥离移入到了路由器中。路由器承担了过去由match或if-else语句编写的工作将任务与模型进行匹配、在某些模型不可用时进行兜底并为你提供了一个更改策略的单一控制点。通过 DigitalOcean 推理路由器实现的无服务器推理为你的应用赋予了更高的灵活性和效率同时免去了硬编码架构带来的种种麻烦。从此出发接下来的几个自然延伸步骤包括将draft_customer_reply任务以流式Stream形式返回给客户端以便客服人员在文本生成结束前就能开始阅读将升级总结接入到你真实的工单系统中或者为另一个毫不相干的工作流启动第二个路由器并复用同一个访问密钥。完整的示例代码可在配套仓库中获取而在 DigitalOcean 控制面板 中配置好路由器大概只需要 5 分钟。如果遇到Claude、OpenAI大模型调用疑问或希望进一步了解DigitalOcean GPU 云服务器产品可联系卓普云aidroplet.com。