模型选择、多模型路由与隐私
正在读取学习记录…
模型路由是在发送请求之前,根据任务、数据要求和可用服务选择目标模型。可以把它想成分流员:先读工单的分类标签,再把普通问题交给适合的服务,不让模型收到请求后才决定自己该不该看。
例如,给“物流到哪里了”这条客户留言做分类,先从具备分类能力的服务中挑一个预算够用的模型。如果留言包含受限制的客户资料,就按本例规则改用本地模型。运行下面的路由程序,status 告诉你有没有选出合适的模型,model 告诉你选中了谁。再让本地模型发生故障,观察还有没有符合要求的候选。
1. 从候选模型里挑一个合适的
想象给工单安排处理人:先看谁能接触这份资料,再看谁会处理这种问题,最后考虑成本。模型路由做的是同样的筛选。
程序先列出可调用的服务,也就是候选模型。随后按三项条件缩小名单:数据允许发到哪里、服务具备哪些能力、费用是否在预算内。这些条件组成路由规则。例如,普通分类可以交给符合要求的低成本服务;受限资料则按组织政策筛选获准服务。
服务故障时,重新从合格候选里挑一个,叫作回退。它沿用刚才的三项条件,所以故障处理与正常选择使用同一套筛选规则。
进一步核对供应商的数据保存与使用政策
隐私边界规定原始数据能否离开本机或组织环境。供应商是否用数据训练、是否保存请求、保存多久,是不同问题。OpenAI 数据控制文档分别说明训练、滥用监测日志和应用状态。接入前按所用端点和合同核对这些条件。
在 Agent 中,路由器位于“任务分类/敏感度标记”之后、“模型请求”之前。它不负责判断模型回答正确,也不负责授权工具写入。
| 输入条件 | 选择 | 理由与禁止动作 |
|---|---|---|
| 含受限个人信息,本地服务可用且能力够 | 本地 | 原文不得发送云端;需继续核对本地日志与进程权限 |
| 含受限信息,本地服务不可用 | 拒绝或人工脱敏 | 不能以“可用性优先”为理由自动云端回退 |
| 公开文本、简单分类、低预算 | 合格的低成本候选 | 只比较已登记的能力与上限,不宣称模型质量排名 |
| 公开文本、需要长上下文或指定能力 | 满足能力的候选 | 超预算则拒绝或先缩小任务,不能猜测费用 |
2. 按任务需求选择模型
下图把“正常选择”和“故障回退”分开。云服务超时只可回退到仍满足同一隐私与能力约束的候选;否则返回 no_safe_route。这对应 Anthropic 的 Agent 工程文章提醒的取舍:增加动态步骤会增加成本、延迟和错误组合风险,所以规则要能观测、能停止。
使用本地服务时,核对日志同步、监听地址和模型来源。Ollama 的生成 API 文档说明本地服务的请求字段和响应字段,但不替你完成组织的隐私评估。
3. 跟着做:不联网的路由实验
文件清单:routing_lab.py是完整程序;test_routing_lab.py是正常、拒绝与边界测试。把这两个文件按下方代码逐个保存到自己电脑的同一新文件夹,在资源管理器打开该文件夹并从地址栏输入 powershell。核对文件并运行:
macOS:先在 Python 篇的学习文件夹中打开终端,执行 source .venv/bin/activate。再输入 cd ,把当前例子的文件夹拖入终端并回车,运行下面的命令。
保存为 routing_lab.py:
"""根据资料敏感性、任务能力和预算,从模型列表中选择一个。"""
from dataclasses import dataclass
from typing import FrozenSet, List, Optional
@dataclass(frozen=True)
class Model:
name: str
local: bool
capabilities: FrozenSet[str]
cost_units: int
@dataclass(frozen=True)
class Request:
sensitive: bool
capability: str
max_units: int
# classify 表示分类,long 表示长文本处理;cost_units 是本例用于比较的成本分数。
MODELS = [
Model("cloud-basic", False, frozenset({"classify"}), 1),
Model("cloud-long", False, frozenset({"classify", "long"}), 4),
Model("local-small", True, frozenset({"classify"}), 2),
]
def route(request: Request, models: List[Model] = MODELS,
excluded: FrozenSet[str] = frozenset()) -> dict:
if not request.capability or request.max_units < 0:
return {"status": "invalid_request", "model": None}
# 依次筛掉已失败、数据去向不合适、能力不够和超出预算的候选模型。
eligible = [m for m in models if m.name not in excluded]
eligible = [m for m in eligible if not request.sensitive or m.local]
eligible = [m for m in eligible if request.capability in m.capabilities]
eligible = [m for m in eligible if m.cost_units <= request.max_units]
if not eligible:
return {"status": "no_safe_route", "model": None}
selected = min(eligible, key=lambda m: (m.cost_units, m.name))
return {"status": "selected", "model": selected.name}
def failover(request: Request, failed: str) -> dict:
return route(request, excluded=frozenset({failed}))
if __name__ == "__main__":
for label, req in [
("public", Request(False, "classify", 2)),
("sensitive", Request(True, "classify", 2)),
("too_long", Request(True, "long", 4)),
]:
print(label, route(req))
print("sensitive_failover", failover(Request(True, "classify", 2), "local-small"))完整代码:test_routing_lab.py
# 核对模型选择、多模型路由与隐私的正常结果和错误分支;断言失败时会显示对应的测试。
import unittest
from routing_lab import Request, failover, route
class RoutingTests(unittest.TestCase):
def test_public_chooses_cheapest_eligible(self):
self.assertEqual(route(Request(False, "classify", 2))["model"], "cloud-basic")
def test_sensitive_stays_local(self):
self.assertEqual(route(Request(True, "classify", 2))["model"], "local-small")
def test_sensitive_failover_does_not_go_cloud(self):
self.assertEqual(failover(Request(True, "classify", 2), "local-small")["status"], "no_safe_route")
def test_budget_boundary(self):
self.assertEqual(route(Request(False, "classify", 0))["status"], "no_safe_route")
def test_invalid_input(self):
self.assertEqual(route(Request(False, "", 2))["status"], "invalid_request")
if __name__ == "__main__":
unittest.main()ls
python routing_lab.py
python -m unittest -v test_routing_lab.py候选里的 local 表示本地服务,capabilities 是登记的能力集合,cost_units 是教学预算单位。route 先筛隐私、能力、预算,筛空统一返回 no_safe_route;它不区分是哪一道门筛空。failover 排除故障目标后重用同一规则。剩多个候选时,按费用最小、同费按名称排序选一个:
eligible = [m for m in models if not request.sensitive or m.local]
eligible = [m for m in eligible if request.capability in m.capabilities]
eligible = [m for m in eligible if m.cost_units <= request.max_units]
if not eligible:
return {"status": "no_safe_route", "model": None}
selected = min(eligible, key=lambda m: (m.cost_units, m.name))
return {"status": "selected", "model": selected.name}“公开分类”应选择 cloud-basic;敏感请求应选择 local-small;若本地候选不可用,应返回 no_safe_route。cost_units 是实验中的预算单位。找不到文件时先用 dir 查看当前目录;找不到 python 时回看 Python 入门。
| 请求条件 | 当前输出 | 下一步 |
|---|---|---|
| 公开分类、预算充足 | selected:cloud-basic | 将请求交给选定客户端 |
| 敏感分类、本地候选可用 | selected:local-small | 在本地处理受限内容 |
| 敏感分类、本地候选不可用 | no_safe_route | 延后处理或转人工 |
沿一次敏感工单走完整条决策链
假设工单正文含有身份证号,产品的数据政策把它标为“受限”。上游完成敏感信息识别后,向路由器传入 sensitive=True;路由器还接收所需能力 classify、预算上限,以及候选模型的登记资料。敏感信息识别本身需要单独评测,不能仅凭一个正则表达式判断。
第一道门过滤非本地候选。即使云端模型更快、单价更低,也在这一步出局。第二道门检查保留下来的本地候选是否声称具备分类能力;第三道门检查教学预算单位。如果本地候选不可用,回退函数只能在仍获准的候选里找,不能重新把云端模型放回池子。找不到候选时返回 no_safe_route,交由上层决定脱敏、延后或人工处理。路由器本身没有执行自动脱敏,也没有证明本地候选真能准确分类。
| 改变一个条件 | 预期路由变化 | 原因 | 应当记录 |
|---|---|---|---|
| 敏感标签从 false 改 true | 云端候选出局 | 数据边界先于费用 | 标签来源与规则版本 |
| 本地候选故障 | 敏感请求无安全目标 | 故障不放宽边界 | 候选故障与拒绝原因 |
| 所需能力改为未登记能力 | 全部候选可能被排除 | 能力必须显式匹配 | 能力需求与候选版本 |
| 预算上限降到所有候选以下 | 拒绝或等待 | 不能假装“免费调用” | 预算口径和估计依据 |
这三道门只处理已登记的条件。图左侧的“敏感”标签来自上游,不是路由器识别出来的;图右侧的能力和费用也仍须用真实任务、当前合同和实际账单核实。
用源码手动跟踪这三次名单变化:公开分类、预算2时,云基础与本地候选留下,按费用选择云基础;改为敏感后,只留下本地;再排除本地故障目标,名单为空。脚本返回结果,不打印这份名单。
先不编辑文件,直接做一次只改敏感度的对照;两行请求的能力 classify 与预算 2 完全相同:
python -c "from routing_lab import Request,route; print(route(Request(False,'classify',2))); print(route(Request(True,'classify',2)))"预期先得到 selected/cloud-basic,后得到 selected/local-small。然后在同目录运行精确的故障回退调用:
python -c "from routing_lab import Request,failover; print(failover(Request(True,'classify',2),'local-small'))"预期 {'status': 'no_safe_route', 'model': None}。这是排除候选的模拟,未启动或关闭真实端点。三次都用能力 classify、预算2;真正变动的是敏感标签和故障名单。
4. 在实际项目中配置路由
先做真实数据分类和脱敏规则,核对供应商数据控制及本地日志,再对候选模型做同一任务集的质量/延迟/费用实测。记录模型版本、区域、端点、请求摘要、规则版本和拒绝原因;日志记录请求摘要和脱敏后的必要信息。临时超时可重试,但路由重选仍要经过三道门。请求没有合适候选时,保留待处理状态并交给人。
评测上游分类与候选模型
敏感工单被误标成公开时,后面的筛选会在错误前提下执行。给上游分类器单独测误漏标,对不确定的高风险类别保守处理。候选表的能力来自登记,需用同一任务集测字段合法率、业务正确率、拒绝率、延迟和费用,并在模型更新后复核。
小结
路由像派单:先确定谁可以接这份资料,再看谁有能力、谁在预算内,最后从合格者里选择。公开分类选择 cloud-basic,敏感分类选择 local-small;排除后者时返回 no_safe_route。把三次调用放在一起,指出每次改变了哪项条件,就能解释选择依据。
参考资料
- OpenAI API 数据控制:了解训练、日志及端点存储政策。
- Anthropic:Building effective agents:复杂度、延迟和成本取舍。
- Ollama Generate API:本地服务的请求与响应格式。
现有练习
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
objective · 模型选择、多模型路由与隐私
围绕 L1-routing 说明一个可验证的工程做法。
对应知识点:L1-routing
short_answer · 模型选择、多模型路由与隐私
围绕 L1-routing 说明一个可验证的工程做法。
对应知识点:L1-routing
coding · 模型选择、多模型路由与隐私
围绕 L1-routing 说明一个可验证的工程做法。
对应知识点:L1-routing
scenario · 模型选择、多模型路由与隐私
围绕 L1-routing 说明一个可验证的工程做法。
对应知识点:L1-routing
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。