type
Post
status
Published
date
Nov 23, 2025
slug
gateway010
summary
tags
gateway
category
icon
password
摘要:LLM Access Gateway完成了从模型 API 转发器到治理型接入网关的进化
我一开始也容易把它讲成“统一一下模型接口”。再往下做我才发现,最能把这个项目和普通转发层拉开的,根本不是多了几个端点,而是共享模型接入以后,谁来兜流式边界、准入、回退、用量和排障。
所以到这个阶段,我更愿意这样定义它:
这一路做下来,我真正做成的,不是一个把模型 API 包起来的壳子,而是把原本会散落在业务侧的接入、治理、流式边界、故障处理和观测责任,一点点收回到中间这一层。
这句话也是我写 010 的原因。前面几篇看起来像在做不同的事,现在回头看,它们都在给这一句补证据。
一、这个项目最该收的是责任
最开始最像需求的,确实只是“给调用方一个统一入口”。可统一入口只解决了路径长什么样,没有解决语义到底谁来兜。多个应用一旦共用这一层,请求是谁发的、能不能进、流什么时候算开始、失败以后怎么收、调用结束后怎么算账,这些事就会一起涌过来。共享模型接入一旦出现,责任就会跟着集中。
如果只挑一个最能说明“它已经不是转发器”的责任,我会选 stream。原因很直接:首个 chunk 一旦发给客户端,这条响应就已经开始生效了。后面上游再断,网关已经不能随便换一个上游把结果补完整了。这个边界要是还留给业务侧自己处理,很快就会变成每个服务都有自己的一套流式语义。
我现在再看这张图,最重要的已经不是“中间多了一层”,而是这层开始承接一组原来会散在外面的共享责任。也正因为这样,这个项目才会从“把请求转出去”慢慢长成一个更像平台后端的作品。网关这层一旦开始承接这些责任,它的性质就已经变了。
二、这些责任不是并排摆着,它们会串成一条请求链
这一节要把这些责任落到一条真实请求上。
这张表比文章目录更能说明这个项目到底是什么:
收回来的责任 | 仓库里的落点 | 这层现在多承担了什么 |
统一入口 | internal/api/router.go 里的 /v1/chat/completions、/v1/models、/v1/usage | 调用方先面对一套固定入口 |
身份和准入 | requireAPIKey、internal/service/governance/service.go 里的 BeginRequest | 请求先有主体,再有 rpm / tpm / token_budget 准入 |
上游适配和选路 | internal/provider/openai/chat.go、internal/provider/router/chat.go、route_rules | 请求不只是发出去,还要知道这次走到谁 |
流式边界 | awaitFirstStreamEvent、wrapStream | 首包前还能回退,首包后只能暴露中断 |
账本和请求链 | request_usages、internal/obs/tracing/tracing.go、provider events | 请求结束后还能知道发生了什么 |
交付和验收 | deployments/docker/、deployments/k8s/、smoke / verify 脚本 | 这层不只我自己会跑,别人也能固定拉起来 |
如果只拿一条 POST /v1/chat/completions 来看,这些责任其实会连成一条很具体的链:请求先经过 router.go 里的 requireAPIKey,Bearer key 会被解析成 principal;接着进 BeginRequest,先估 prompt tokens,再检查 budget / rpm / tpm,同时把 started 写进 request_usages;然后 internal/provider/router/chat.go 再结合已配置的上游、route_rules 和当前健康状态决定这次走到谁;如果是流式请求,awaitFirstStreamEvent 还会先判断这次是不是还在回退窗口里;最后由 chat.go 这类适配层把请求真正发出去,请求结束后再回写 succeeded / failed,同时落日志、trace 和指标。这一条请求已经不只是“发出去再回来”,它中间已经挂上了一整套共享责任。
前面真实上游验证时,同一条 non-stream 请求最后能一路对到这几行日志:
INFO trace span finished backend=openai-primary attempt=1 trace_id=1776817310913566000-3 request_id=1776817310913566000-3 span_name=provider.backend.create status=ok
INFO provider event type=provider_request_succeeded operation=create backend=openai-primary attempt=1
INFO http request completed request_id=1776817310913566000-3 trace_id=1776817310913566000-3 method=POST path=/v1/chat/completions status=200
我关注的是它们把三个问题一起接上了:这条请求是谁、这次打到了哪条上游、最后是不是成功返回。这条请求已经能被一路讲清楚。
三、我的网关和“模型 API 转发器”的差别在哪
这一节说明“它到底多承担了什么”。
差别在请求前、请求中、请求后这三段是不是都有人兜。转发器通常只管“收到了就发出去”;现在这层已经在这三段都开始承责了。这个差别是运行时责任变了,不是表面接口变了。
只做转发时 | 现在这层在做的事 |
请求来了就往上游送 | 先识别主体,再做准入和初始化账本 |
流式返回只管把字节往外转 | 要区分首包前回退窗口、首包后中断语义、[DONE] 完整性 |
上游波动了就把错误原样抛回来 | 还要处理选路、回退、cooldown、/readyz 和 /debug/providers |
请求结束就结束了 | 还要留下 usage 生命周期、request id、trace、provider event 和最小验收动作 |
我现在再说“治理型接入网关”,指的就是这张对照表。调用方从这里拿走的,不只是一个统一 API,还包括一套统一的进入方式、失败语义、流式边界、记账口径和排障入口。
四、这个项目的边界
写到这一步,我更愿意把它定义成“模型服务接入和治理层”。它关心的是请求怎么进来、怎么识别主体、怎么选上游、怎么处理流式边界、怎么回退、怎么记账、怎么暴露状态、怎么让别人稳定拉起来。这个项目现在最清楚的位置,就是模型服务前面的接入层。
还有一层我现在也收得比较清楚:这套系统已经不只是 demo,但我也不会把它提前写成“完整平台”。它已经把统一入口、流式边界、准入和账本、回退和健康状态、请求链、最小交付这些问题接进来了;更重的生产话题,比如大规模容量规划、跨地域容灾、成熟计费结算、多团队权限体系,还没有被这套仓库收实。
五、总结
我希望你记住的不是“我会接 OpenAI-compatible API”,而是“我知道共享模型接入一旦成立,哪些责任必须回收到中间层,而且我已经把其中最关键的几块做成了代码、日志、状态面和交付动作”。
现在我会把它介绍成:一个带统一入口、上游适配、流式边界、身份和配额治理、选路回退、健康状态、请求链以及最小交付能力的 LLM Access Gateway。
LLM Access Gateway v1 完结撒花,感谢支持!
分享
