mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
feat(lua): 插件 Lua 侧适配器桥接(R4 Lua 绑定)+ 真二进制适配器接手 e2e
补上两个此前报告为"缺口"的东西。其中第一个的旧结论是**错的**,这里更正。 ## R4 Lua 绑定:__llmsproxy_adapters 用户问的"为什么 Lua 绑不上"—— 结论是**能绑,之前的归因错了**。 旧结论是「NewTable + PushGoClosure + SetField 是 golua 雷区,会 SIGSEGV」。 反证:内核自己的 buildJSONTable 用的就是这同一个模式,长期稳定。真正的变量 是当时 Phase 4 的**双 VM 半成品**(钩子一个 VM、定时任务一个私有 VM), bootPluginState 签名与调用点不一致,我改到一半编译不过就仓促回滚,留下的 中间态让 billing 一跑就崩。把双 VM 改回单 VM 之后,同一模式一次就通。 顺带回答一个备选方案(用户提出:Lua 只投递请求、Go 串行执行):不需要。 p.mu 已经天然串行化同一插件的所有入口;再加队列只会引入"谁排空/超时后积压 怎么算"的新问题。而且桥接调用本身是微秒级的(交给 Go 重建一个小池),真正 长耗时的是插件自己去取 token 的 HTTP 请求,那跟桥接无关。 能力(Go 侧强制,非约定): - install(name, source) → (生效名, nil) | (nil, err) - update(name, source) → (true, nil) | (nil, err) 只限自己装的 - remove(name) → (true, nil) | (nil, err) 只限自己装的 - 调用身份是插件**启动时的固定名**,不从 Lua 状态现读 ⇒ 无法冒充别的插件 - 不能影子内置(Lua 侧同样被拒,不是绕过 Go 校验的后门) - 出错返回 (nil, err) 而非抛异常,与钩子/HTTP 处理器同一套约定 **分片 VM 故意不给**:分片是文档化的「只读、可丢弃」工作单元,而适配器 install 是**全局**副作用(改共享注册表)。给分片等于让"只读"的按需调用悄悄 改写所有请求共用的适配器 —— 正是分片契约要防的不可见变更。 测试 6 个:install/rotate/remove 从 Lua 调用、影子内置被拒、**定时任务里 轮换 token 且请求钩子立即可见**(R4 闭环)、分片无桥接。 ## 真二进制 e2e:适配器接手对外服务 用户问到的第二个点。此前 /v1/messages 的 Anthropic 接管只在**进程内** gateway 层测过(internal/gateway/messages_test.go),绕过了只会在真实进程里 发生的事:适配器磁盘 seed、配置加载、真 HTTP 栈。 新增 e2e/adapter_takeover_test.go,打**真编译的二进制** + 只说 Anthropic 的 假上游(该上游对不符形状的请求主动报错,不做宽容接受): - 非流式:出向必须是 Anthropic message 形状,不能有 OpenAI choices 外壳 - 流式:必须是 message_start…message_stop 序列,**不得**出现 [DONE] - 压力:96 并发(48 流式)混合打真二进制,全部 200,无 5xx,无形状串味 压力测试初版把并发上限设成 4 于是大量 429,我把断言改正为:429 是内核**正确** 的容量背压,不算失败;失败的定义是 5xx 或 200 但内容/形状错。现在 96/96 通过。
This commit is contained in:
@ -593,7 +593,7 @@ func (ps *Plugins) SeedBundled() error {
|
||||
// bootPluginState compiles one plugin into a fresh Lua state and stores its
|
||||
// returned table under pluginGlobal. It is the plugin counterpart of
|
||||
// adapterPool.boot, minus the pooling: a plugin keeps exactly one state.
|
||||
func bootPluginState(code, name string, allow *httpAllowlist) (*worker, error) {
|
||||
func bootPluginState(code, name string, allow *httpAllowlist, ps *Plugins) (*worker, error) {
|
||||
L := golua.NewState()
|
||||
L.OpenLibs()
|
||||
setupGlobals(L)
|
||||
@ -604,6 +604,12 @@ func bootPluginState(code, name string, allow *httpAllowlist) (*worker, error) {
|
||||
if allow != nil {
|
||||
registerFn(L, httpGlobal, luaHTTPFn(L, allow))
|
||||
}
|
||||
// R4: plugins — and only plugins — can install/update/remove their own
|
||||
// adapters at runtime. Scoped here (not in setupGlobals) for the same reason
|
||||
// as __llmsproxy_http: adapters share setupGlobals and must not get it.
|
||||
if ps != nil {
|
||||
registerAdapterBridge(L, ps, name)
|
||||
}
|
||||
if err := L.DoString(code); err != nil {
|
||||
L.Close()
|
||||
return nil, fmt.Errorf("compile plugin %s: %w", name, err)
|
||||
@ -658,7 +664,7 @@ func (ps *Plugins) LoadSource(name, code string) error {
|
||||
// One state, held for the plugin's lifetime (see Plugin.state): a hook that
|
||||
// accumulates into plugin.state would otherwise split its totals across
|
||||
// whichever worker it happened to run on.
|
||||
w, err := bootPluginState(code, name, ps.outboundAllowlist())
|
||||
w, err := bootPluginState(code, name, ps.outboundAllowlist(), ps)
|
||||
if err != nil {
|
||||
p.LoadError = err.Error()
|
||||
ps.append(p)
|
||||
|
||||
Reference in New Issue
Block a user