b536672000
- PostgreSQL 切换 pgvector/pgvector:pg17 镜像;迁移 000024 建 vector 扩展、 knowledge_chunks.embedding vector(1024) + HNSW 余弦索引,retrieval_mode 放宽三态 - OllamaEmbedder 本地 bge-m3 批量嵌入,404 惰性 pull 重试,维度/超时校验,可整体关闭 - SemanticRetriever/HybridRetriever + NewRetriever 按 retrieval_mode 分发,缺 embedder 回退 FTS - 文档入库同步批量向量化;Ollama 故障降级入库 + embedding_failed 事件 - 修复 pgx CopyFrom 对 vector 列二进制编码误读:COPY 基础列后同事务 unnest 批量回填 - 修复降级路径 embeddings=nil 索引越界 panic(Add 与 Reprocess) - 知识库列表 vectorized_chunk_count + 前端三态检索模式选择与向量化覆盖率 - 单测 embedder/retrievers + 集成 TestKnowledgeVectorLifecycle 全绿 Co-Authored-By: Claude <noreply@anthropic.com>
79 lines
2.0 KiB
Go
79 lines
2.0 KiB
Go
package workbench
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
platformid "aigateway.local/core/internal/platform/id"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
type Service struct {
|
|
pool *pgxpool.Pool
|
|
embedder Embedder
|
|
}
|
|
|
|
func NewService(pool *pgxpool.Pool) *Service { return &Service{pool: pool} }
|
|
|
|
// SetEmbedder 注入向量化器(M8 P2)。为 nil 时知识库退回纯 FTS,不生成 embedding。
|
|
func (s *Service) SetEmbedder(embedder Embedder) { s.embedder = embedder }
|
|
|
|
// Embedder 返回当前向量化器(可能为 nil)。
|
|
func (s *Service) Embedder() Embedder { return s.embedder }
|
|
|
|
func newUUID() (string, error) { return platformid.NewUUID() }
|
|
|
|
func emit(ctx context.Context, tx pgx.Tx, eventType, aggregateType, aggregateID, actorID string, values map[string]any) error {
|
|
eventID, err := newUUID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if values == nil {
|
|
values = make(map[string]any)
|
|
}
|
|
values[aggregateType+"_id"] = aggregateID
|
|
values["actor_id"] = actorID
|
|
payload, err := json.Marshal(values)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = tx.Exec(ctx, `INSERT INTO gateway.outbox_events(event_id,event_type,event_version,aggregate_type,aggregate_id,payload) VALUES($1,$2,1,$3,$4,$5)`, eventID, eventType, aggregateType, aggregateID, payload)
|
|
return err
|
|
}
|
|
|
|
func normalizeStrings(values []string, maximum int) ([]string, error) {
|
|
seen := make(map[string]struct{}, len(values))
|
|
result := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
continue
|
|
}
|
|
if len(value) > 255 {
|
|
return nil, errors.New("列表项过长")
|
|
}
|
|
if _, ok := seen[value]; ok {
|
|
continue
|
|
}
|
|
seen[value] = struct{}{}
|
|
result = append(result, value)
|
|
}
|
|
if len(result) > maximum {
|
|
return nil, fmt.Errorf("列表最多允许 %d 项", maximum)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func mapNotFound(err error) error {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return ErrNotFound
|
|
}
|
|
return err
|
|
}
|
|
|
|
func rollback(ctx context.Context, tx pgx.Tx) { _ = tx.Rollback(ctx) }
|