mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-06 01:07:35 +00:00
fix(agent): 安全装饰器吃掉 provider 的模态能力声明(see_image 失效根因)
## 现象
调用 see_image 工具是通的、图片也确实注入了,但内核回:
当前模型不支持图片,且没有可用的视觉回退源
而配置明明是 `core.llm.sources.llmsproxy.vision = true`,后端也确实支持看图。
用户以为是配置没热生效、以为要重启,实际都不是。
## 根因:一条完整的静默失效链
vision=true(配置是对的)
→ LuaAdaptedProvider.Vision = true
→ ProviderManager.Register 把它包成 *secureProviderDecorator
(security.Global() != nil 时无条件包装)
→ ProviderSupportsVision(p) 做 p.(ModalProvider) 断言
→ ★ 装饰器只实现了 Name/MaxContextTokens/Chat/ChatStream,
**漏了 ModalProvider 的 SupportsVision/SupportsAudio**
→ 断言失败 ⇒ 返回 false
→ prepareToolBlocks 判定"主模型不支持图片"
→ see_image 的 image_url 被当成需要回退的块
→ resolveModalFallback 找不到可用视觉源
→ 「没有可用的视觉回退源」
装饰器只该**改写请求内容**(明文→别名防泄漏),不该改变能力声明。
## 修法
透传给内层:内层实现了 ModalProvider 就照实转,否则 false(不 panic)。
## 判据(4 条)
`internal/agent/api/provider_security_modal_test.go`:
vision 透传 / audio 透传 / vision=false 不得被污染成 true / 无 ModalProvider 不 panic
★ 我第一版判据是**假绿**的,值得记下来:
`WrapProviderForSecurity(p, nil)` 在 store==nil 时**直接返回原对象**
(见其实现),所以根本没包装,测的是未包装的 stub ⇒ 删掉透传也不红。
已改为构造真实 security.Store,并加**包装前置断言**
(`_, ok := wrapped.(*secureProviderDecorator)`),防止再次假绿。
变异 2/2 咬住:
· 删掉 SupportsVision 透传 → TestRegisterPreservesVisionCapability 红
· 一律返回 true → TestRegisterPreservesNonVisionCapability 红
## 实测(完整生产装配路径)
源 llmsproxy 类型=*api.secureProviderDecorator vision=true audio=false
默认源 "llmsproxy": ProviderSupportsVision=true
重启本机实例后:22 插件建链 / 0 panic。
This commit is contained in:
@ -44,6 +44,45 @@ func WrapProviderForSecurity(p Provider, store *security.Store) Provider {
|
||||
func (d *secureProviderDecorator) Name() string { return d.inner.Name() }
|
||||
func (d *secureProviderDecorator) MaxContextTokens() int { return d.inner.MaxContextTokens() }
|
||||
|
||||
// ★★ 模态能力必须**透传**给被包装的 provider。
|
||||
//
|
||||
// ## 缺陷(2026-10-17 生产实测)
|
||||
//
|
||||
// `ProviderManager.Register` 在 security 启用时把每个 provider 包成
|
||||
// secureProviderDecorator。而本装饰器原先只实现了 Name/MaxContextTokens/
|
||||
// Chat/ChatStream —— **漏了 ModalProvider 的两个方法**。
|
||||
//
|
||||
// 后果是一条完整的静默失效链:
|
||||
//
|
||||
// 配置 core.llm.sources.llmsproxy.vision = true (本来就是对的)
|
||||
// → LuaAdaptedProvider.Vision = true
|
||||
// → Register 把它包成 *secureProviderDecorator
|
||||
// → ProviderSupportsVision(p) 做 p.(ModalProvider) 断言
|
||||
// → ★ 装饰器没有 SupportsVision ⇒ 断言失败 ⇒ 返回 false
|
||||
// → prepareToolBlocks 判定"主模型不支持图片"
|
||||
// → see_image 的 image_url 被当成需要回退的块
|
||||
// → resolveModalFallback 找不到可用视觉源 ⇒ 回「没有可用的视觉回退源」
|
||||
//
|
||||
// 症状极具误导性:模型是能看图的(配置对、后端支持),但内核一口咬定
|
||||
// 不支持,用户以为是模型配置问题、以为是缓存没热生效、以为要重启。
|
||||
//
|
||||
// ⇒ 装饰器只负责**改写请求内容**,不该改变能力声明。所有 ModalProvider
|
||||
//
|
||||
// 方法一律透传。
|
||||
func (d *secureProviderDecorator) SupportsVision() bool {
|
||||
if mp, ok := d.inner.(ModalProvider); ok {
|
||||
return mp.SupportsVision()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *secureProviderDecorator) SupportsAudio() bool {
|
||||
if mp, ok := d.inner.(ModalProvider); ok {
|
||||
return mp.SupportsAudio()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// scrubRequest 逐条消息改写(**原地**改,因为 req 是要发出去的那一份)。
|
||||
func (d *secureProviderDecorator) scrubRequest(req *CompletionRequest) {
|
||||
if req == nil {
|
||||
|
||||
Reference in New Issue
Block a user