AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

部署、API 与模型网关

我的学习 →

正在读取学习记录…

资料助手在个人电脑上运行后,下一步是让多个用户通过同一个入口使用。部署是把程序及其配置放到持续运行的服务环境;API是调用者与服务交换请求、响应的契约;模型网关是应用与模型服务之间的集中控制位置,负责路由、限额、超时和观测。

正常请求 /ask 带着经过验证的Alice身份和问题“报销上限?”,通过输入检查、就绪检查和并发限制后,选择 local-model 适配器,返回状态200、答案与模型标识。适配器将应用的统一请求转换为具体供应商接口;应用业务层便能保持同一套错误处理。

一次请求走过哪些位置

部署、API 与模型网关机制图
从路由与身份到模型适配器逐层检查;401、400、503、429在调用前退出,504发生在调用后。

图从左到右表示处理顺序。入口层验证身份,业务层验证字段,调用层控制配额与截止时间;日志用请求ID串起这些环节。真实服务的身份应来自验证过的会话或令牌,不能信任客户端随意写入的 principal 字段。

位置检查内容提前退出后的结果
路由是否存在/ask404,无模型调用
身份是否验证成功401,无模型调用
输入问题为非空字符串且不超过100字符400,无模型调用
就绪是否接收新业务503,无模型调用
并发在途数是否小于2429,无模型调用
下游是否在1000毫秒内完成504,已有1次调用

存活探针询问“进程是否还能回答”,就绪探针询问“现在能否接收业务”。Kubernetes 探针文档说明就绪失败会影响服务流量接入,存活失败用于判断重启;启动探针则为慢启动过程留出时间。把外部模型短暂不可用直接绑定进程存活,可能引起反复重启。

运行一个网关决策器

建立新文件夹 l8-deployment,分别保存 gateway.py 和 test_gateway.py。请求和响应在本地通过字典传递,downstream_ms 是给定耗时,calls 是调用轨迹。local_model 是实际执行的本地固定答案函数,函数进入时才把 local-model 写入轨迹;它作为模型适配器的教学替身,便于看清每个分支。接入HTTP服务器时,将这里的输入校验与返回结构放进处理函数,并由服务器验证真实身份。

代码按照路径、身份、输入、就绪、并发、下游顺序执行。先观察正常问答,再比较其他状态码的调用次数。

先在 PowerShell 创建并进入目录,再保存下方两份文件:

PowerShell
New-Item -ItemType Directory -Force l8-deployment
cd l8-deployment

macOS 终端:

Bash
mkdir -p l8-deployment
cd l8-deployment
Python
"""用请求字典演示 API 网关的处理顺序与错误映射。"""
READY = True
LIMIT = 2

def local_model(text, calls):
    # 本地固定答案适配器:函数执行时才记录调用,不接远端模型。
    calls.append("local-model")
    return {"answer": "报销上限200元", "model": "local-model"}

def gateway(request, *, ready=True, active=0, downstream_ms=200):
    path = request.get("path")
    # 健康探针独立于业务请求;存活表示进程能回答,就绪表示可接业务。
    if path == "/live":
        return 200, {"live": True}, []
    if path == "/ready":
        return (200 if ready else 503), {"ready": ready}, []
    if path != "/ask":
        return 404, {"error": "route"}, []
    # 身份来自网关可信验证结果,示例不把模型输出当身份。
    if request.get("principal") != "alice":
        return 401, {"error": "identity"}, []
    text = request.get("text")
    if not isinstance(text, str) or not text.strip() or len(text) > 100:
        return 400, {"error": "input"}, []
    if not ready:
        return 503, {"error": "not_ready"}, []
    if active >= LIMIT:
        return 429, {"error": "concurrency"}, []
    # 调用次数可观测,超时后不自动重试,避免扩大下游压力。
    calls = []
    body = local_model(text, calls)
    if downstream_ms > 1000:
        return 504, {"error": "deadline"}, calls
    return 200, body, calls

def main():
    samples = [
        ("存活", {"path": "/live"}, {}),
        ("就绪", {"path": "/ready"}, {}),
        ("问答", {"path": "/ask", "principal": "alice", "text": "报销上限?"}, {}),
        ("缺少身份", {"path": "/ask", "text": "报销上限?"}, {}),
        ("并发已满", {"path": "/ask", "principal": "alice", "text": "报销上限?"}, {"active": 2}),
        ("下游超时", {"path": "/ask", "principal": "alice", "text": "报销上限?"}, {"downstream_ms": 1200}),
    ]
    for label, request, options in samples:
        status, body, calls = gateway(request, ready=READY, **options)
        print(f"{label}: {status} / {body} / 调用{len(calls)}")

if __name__ == "__main__":
    main()

测试文件 test_gateway.py:

Python
"""检查在调用前拒绝、阈值边界与健康探针的不同语义。"""
import unittest
from gateway import gateway, local_model

ASK = {"path": "/ask", "principal": "alice", "text": "报销上限?"}

class GatewayTests(unittest.TestCase):
    def test_adapter_records_execution(self):
        calls = []
        body = local_model("报销上限?", calls)
        self.assertEqual(calls, ["local-model"])
        self.assertEqual(body["answer"], "报销上限200元")

    def test_normal_and_invalid(self):
        self.assertEqual(gateway(ASK)[0], 200)
        self.assertEqual(gateway({**ASK, "text": " "}),
                         (400, {"error": "input"}, []))

    def test_identity_before_call(self):
        self.assertEqual(gateway({**ASK, "principal": "bob"})[2], [])

    def test_readiness_and_liveness(self):
        self.assertEqual(gateway({"path": "/live"}, ready=False)[0], 200)
        self.assertEqual(gateway({"path": "/ready"}, ready=False)[0], 503)
        self.assertEqual(gateway(ASK, ready=False)[2], [])

    def test_slot_boundary(self):
        self.assertEqual(gateway(ASK, active=1)[0], 200)
        self.assertEqual(gateway(ASK, active=2)[0], 429)

    def test_deadline_boundary(self):
        self.assertEqual(gateway(ASK, downstream_ms=1000)[0], 200)
        self.assertEqual(gateway(ASK, downstream_ms=1001)[0], 504)

    def test_unknown_route(self):
        self.assertEqual(gateway({"path": "/missing"})[0], 404)

if __name__ == "__main__":
    unittest.main()

文件保存后,在同一窗口核对文件名并运行:

Bash
ls
python -X utf8 gateway.py
python -X utf8 -m unittest -v test_gateway.py

dir应显示这两个文件。运行程序后得到下面输出;测试末尾应显示 OK。

程序输出:

TXT
存活: 200 / {'live': True} / 调用0
就绪: 200 / {'ready': True} / 调用0
问答: 200 / {'answer': '报销上限200元', 'model': 'local-model'} / 调用1
缺少身份: 401 / {'error': 'identity'} / 调用0
并发已满: 429 / {'error': 'concurrency'} / 调用0
下游超时: 504 / {'error': 'deadline'} / 调用1

服务尚未就绪时,哪些请求还能回答

在 gateway.py 找到 READY = True,改为 READY = False,保存并运行:

Bash
python -X utf8 gateway.py

“存活”仍返回200;“就绪”返回503。“问答”“并发已满”“下游超时”都在就绪检查处提前结束,返回503且调用0次;“缺少身份”仍为401,因为身份检查更早。恢复 READY = True 后再次运行,得到上面的正常输出。

这说明状态码对应程序实际走到的分支。同一请求同时违反多个条件时,先检查的条件决定返回结果。测试还验证1000毫秒恰好通过、1001毫秒触发超时,避免把阈值写成模糊描述。

放入实际部署流程

构建阶段将应用、依赖清单与配置打成确定的制品。启动阶段加载配置并检查必要资源,准备好后转为就绪。停止阶段先取消就绪,再等正在执行的请求结束,最后关闭进程;这称为优雅停机,可减少发布时中断请求。

网关对模型别名、供应商凭证和允许工具统一管理。凭证由服务端读取,日志记录请求ID、模型别名、耗时、令牌数量和错误类别,敏感输入按保留规则处理。供应商返回格式仍须在适配器处校验,避免将不完整答案当成功。

场景合适动作原因
某模型短暂不可用有预算和兼容性依据时切换适配器同时记录切换原因与实际费用
高峰并发耗尽有界排队或快速拒绝防止请求无限堆积
请求已经超时传播取消信号,核对下游状态用户超时后下游仍可能执行
写工具返回超时根据操作ID查询结果自动重试可能重复写入
更新配置审核、校验后发布配置会改变真实行为

本地分支能检查处理顺序;连接池、TLS、负载均衡、HTTP传输和真实模型响应需要接入服务后验证。先把输入、输出、调用轨迹及失败映射定清楚,再扩大部署规模。

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“部署、API 与模型网关”,哪种做法更稳妥?

对应知识点:L8-deployment

选择一个答案
练习 2 / 4 · v1.0.0

简答题 · 解释失败

写出一个失败输入并说明记录字段。

对应知识点:L8-deployment

练习 3 / 4 · v1.0.0

代码题 · 边界测试

补充正常、边界、失败三组输入和预期结果。

对应知识点:L8-deployment

练习 4 / 4 · v1.0.0

场景题 · 结果未知

证据不足时如何设计状态和人工接管?

对应知识点:L8-deployment

接着用容量、并发、缓存与FinOps估算入口限额与成本。

图片查看器
100%

文章配图 · 按 Esc 关闭