Why: Downstream products must extend the base through a public package and host API instead of editing internal code. Evidence: go test ./... passed, including plugin v2 assets, sidecar startup, migration v13, pending confirms, HITL timeout, hash embedder, command pairs, and the extension surface snapshot. Impact: apiVersion 2.0-experimental rejects empty network allowlists for declarative HTTP. Hash embeddings stay opt-in via models.embedding.provider=hash. Plugin secrets resolve per owner at execution time. Refs: docs/proposals/2026-10-01-plugin-contract-v2.md Co-authored-by: Cursor <cursoragent@cursor.com>
72 lines
1.6 KiB
Go
72 lines
1.6 KiB
Go
package rag
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"hash/fnv"
|
||
"unicode/utf8"
|
||
)
|
||
|
||
const hashDim = 32
|
||
|
||
// HashEmbedder 是零依赖的本地嵌入档。语义弱于真实模型,只保证同文本稳定、近文本可比较。
|
||
// 由 settings models.embedding.provider=hash 显式打开;未设置时仍按 D23 退化为全文。
|
||
type HashEmbedder struct{}
|
||
|
||
func (HashEmbedder) Embed(_ context.Context, texts []string) ([][]float32, error) {
|
||
out := make([][]float32, len(texts))
|
||
for i, text := range texts {
|
||
out[i] = hashVec(text)
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
func hashVec(text string) []float32 {
|
||
v := make([]float32, hashDim)
|
||
for _, r := range text {
|
||
h := fnv.New32a()
|
||
var buf [4]byte
|
||
n := utf8.EncodeRune(buf[:], r)
|
||
_, _ = h.Write(buf[:n])
|
||
idx := int(h.Sum32() % hashDim)
|
||
v[idx] += 1
|
||
}
|
||
var norm float32
|
||
for _, x := range v {
|
||
norm += x * x
|
||
}
|
||
if norm == 0 {
|
||
return v
|
||
}
|
||
inv := float32(1)
|
||
// 粗略归一,避免余弦被长度主导。
|
||
for norm > 1 {
|
||
norm = norm / 4
|
||
inv = inv / 2
|
||
}
|
||
for i := range v {
|
||
v[i] *= inv
|
||
}
|
||
return v
|
||
}
|
||
|
||
// SwitchingEmbedder 在 provider=hash 时走本地档,否则走 HTTP。
|
||
type SwitchingEmbedder struct {
|
||
HTTP *HTTPEmbedder
|
||
Provider func() string
|
||
}
|
||
|
||
func (e SwitchingEmbedder) Embed(ctx context.Context, texts []string) ([][]float32, error) {
|
||
if e.Provider != nil && e.Provider() == "hash" {
|
||
return HashEmbedder{}.Embed(ctx, texts)
|
||
}
|
||
if e.HTTP == nil {
|
||
return nil, fmt.Errorf("rag: embedding 模型未配置(settings.models.embedding)")
|
||
}
|
||
return e.HTTP.Embed(ctx, texts)
|
||
}
|
||
|
||
func (e SwitchingEmbedder) Offline() bool {
|
||
return e.Provider != nil && e.Provider() == "hash"
|
||
}
|