mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-22 18:08:04 +00:00
healthcheck_kernel 此前没有任何「ONNX 模型是否在用」的信息,只报「向量可用/不可用」, 分不清「统一多模态空间已加载」与「退回到词嵌入/TF-IDF 路径」;人格卡要求 「版本以运行时快照为准」,也缺一个可查字段(build.version 早就在,但没人知道)。 - KernelStatus 新增 onnx 段:enabled / provider / dim / fingerprint / modalities / reason。 判据取 Loaded()(provider 真正打开且元数据合法),**不是**「配置里写了 provider」 —— 后者在模型缺失 / 运行时缺失时也为真,报出去就是假绿。 - 未启用时 reason 给**具体原因**:未配置(说明会走回退路径)/ 打开失败的具体错误。 homed 把「配置的 provider 名」与「打开失败原因」透传给 Agent,仅供状态报告。 - ProviderAdapter 新增 Modalities()(可选能力,按接口断言取用,不改公开契约)。 - healthcheck_kernel 的工具描述同步说明它回答这两件事。 **顺带修一个真实 panic**:collectKernelStatus 的 knowledge 是**接口**参数, (*knowledge.Store)(nil) 塞进接口后 `ks != nil` 仍为真 → 调 List() 直接 panic, 而 healthcheck_kernel 正是走这条路径(panic 发生在工具 goroutine 里)。 GetKernelStatus 改为先按具体指针判空、再赋给接口;并加刻画测试钉住这个成因 (一旦不再 panic 说明参数形状已变,守卫与该测试应同步删除)。 验证:单测 4 例(已启用 / 打开失败 / 未配置 / 未加载)+ 刻画测试; 隔离实例 E2E 7/7:正例 provider=chineseclip → enabled=true、dim=512、模态 2; 反例 provider=nonexistent → enabled=false 且 reason 含具体错误与 provider 名, 真实对话仍通。
96 lines
2.5 KiB
Go
96 lines
2.5 KiB
Go
package vector
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
|
||
"gitcode.com/JianFeeeee/HomeAgent/pkg/embedding"
|
||
)
|
||
|
||
// ProviderAdapter translates the public model-neutral embedding.Provider SPI
|
||
// to the small internal interface used by the existing memory consumers.
|
||
// Model selection, media decoding, preprocessing, and runtime details remain
|
||
// entirely inside the selected provider.
|
||
type ProviderAdapter struct {
|
||
provider embedding.Provider
|
||
info embedding.Info
|
||
|
||
mu sync.RWMutex
|
||
closed bool
|
||
}
|
||
|
||
// AdaptProvider validates and wraps a public provider for internal memory use.
|
||
func AdaptProvider(provider embedding.Provider) (*ProviderAdapter, error) {
|
||
info := provider.Info()
|
||
if err := embedding.ValidateInfo(info); err != nil {
|
||
return nil, err
|
||
}
|
||
return &ProviderAdapter{provider: provider, info: info}, nil
|
||
}
|
||
|
||
func (a *ProviderAdapter) VectorizeDense(text string) ([]float64, error) {
|
||
return a.embed(embedding.Input{
|
||
Modality: embedding.ModalityText,
|
||
Purpose: embedding.PurposeQuery,
|
||
Text: text,
|
||
})
|
||
}
|
||
|
||
func (a *ProviderAdapter) EmbedImageDense(data []byte, mime string) ([]float64, error) {
|
||
return a.embed(embedding.Input{
|
||
Modality: embedding.ModalityImage,
|
||
Purpose: embedding.PurposeDocument,
|
||
Data: data,
|
||
MIME: mime,
|
||
})
|
||
}
|
||
|
||
func (a *ProviderAdapter) embed(input embedding.Input) ([]float64, error) {
|
||
a.mu.RLock()
|
||
closed := a.closed
|
||
a.mu.RUnlock()
|
||
if closed {
|
||
return nil, context.Canceled
|
||
}
|
||
vec, err := a.provider.Embed(context.Background(), input)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := embedding.ValidateVector(vec, a.info.Dimension); err != nil {
|
||
return nil, err
|
||
}
|
||
return vec, nil
|
||
}
|
||
|
||
func (a *ProviderAdapter) Fingerprint() string { return a.info.Fingerprint }
|
||
func (a *ProviderAdapter) Dim() int { return a.info.Dimension }
|
||
|
||
// Modalities 报告该空间支持的输入模态(text/image/...)。
|
||
//
|
||
// 模态是**可选能力**:MultimodalEmbedder 契约里没有它,状态查询按接口断言取用,
|
||
// 所以这里既不改公开接口,也不影响其它实现(核心也不硬编码任何模型名)。
|
||
func (a *ProviderAdapter) Modalities() []string {
|
||
out := make([]string, 0, len(a.info.Modalities))
|
||
for _, m := range a.info.Modalities {
|
||
out = append(out, string(m))
|
||
}
|
||
return out
|
||
}
|
||
|
||
func (a *ProviderAdapter) Loaded() bool {
|
||
a.mu.RLock()
|
||
defer a.mu.RUnlock()
|
||
return !a.closed
|
||
}
|
||
|
||
func (a *ProviderAdapter) Close() {
|
||
a.mu.Lock()
|
||
if a.closed {
|
||
a.mu.Unlock()
|
||
return
|
||
}
|
||
a.closed = true
|
||
a.mu.Unlock()
|
||
a.provider.Close()
|
||
}
|