Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
5.4 KiB
JSON
1 line
5.4 KiB
JSON
{"repo": "aliuyanfeng/happytools", "files": {"backend/services/rag/embedding.go": {"c675859b1fab080caa4dd37bbd0947e10600cda7": {"t": 1786068376.390165, "n": 4460, "h": "31fe40c00102f848", "c": "/*\n * @Author: LiuYanFeng\n * @Date: 2026-07-22\n * @Description: RAG Embedding 服务 - 支持 API 和本地模型(纯 Go 推理)\n */\n\npackage rag\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// EmbeddingProvider Embedding 提供商类型\nconst (\n\tEmbeddingProviderAPI = \"api\" // 远程 API(如 DeepSeek)\n\tEmbeddingProviderLocal = \"local\" // 本地 HTTP 服务(Python sentence_transformers,已废弃)\n\tEmbeddingProviderLocalGo = \"local_go\" // 本地推理(Hugot/ONNX)\n)\n\n// EmbeddingService Embedding 服务 - 统一接口,支持 API 和本地推理\ntype EmbeddingService struct {\n\tprovider string // api, local, local_go\n\tapiURL string // API 模式:API 地址\n\tapiKey string // API 模式:API Key\n\tmodel string // API 模式:模型名称\n\tclient *http.Client // API 模式:HTTP 客户端\n\tlocalEngine *LocalEmbeddingEngine // 本地 Go 模式:纯 Go 推理引擎\n}\n\n// NewEmbeddingService 创建 API 模式的 Embedding 服务\nfunc NewEmbeddingService(apiURL, apiKey, model string) *EmbeddingService {\n\tif apiURL == \"\" {\n\t\tapiURL = \"https://api.deepseek.com\"\n\t}\n\tif model == \"\" {\n\t\tmodel = \"deepseek-v4-flash\"\n\t}\n\treturn &EmbeddingService{\n\t\tprovider: EmbeddingProviderAPI,\n\t\tapiURL: strings.TrimRight(apiURL, \"/\"),\n\t\tapiKey: apiKey,\n\t\tmodel: model,\n\t\tclient: &http.Client{Timeout: 60 * time.Second},\n\t}\n}\n\n// NewLocalGoEmbeddingService 创建纯 Go 本地推理模式的 Embedding 服务\nfunc NewLocalGoEmbeddingService(engine *LocalEmbeddingEngine) *EmbeddingService {\n\treturn &EmbeddingService{\n\t\tprovider: EmbeddingProviderLocalGo,\n\t\tlocalEngine: engine,\n\t}\n}\n\n// Embed 生成文本的向量嵌入\nfunc (s *EmbeddingService) Embed(texts []string) ([][]float64, error) {\n\tif len(texts) == 0 {\n\t\treturn nil, fmt.Errorf(\"文本列表为空\")\n\t}\n\n\tswitch s.provider {\n\tcase EmbeddingProviderLocalGo:\n\t\treturn s.embedLocalGo(texts)\n\tcase EmbeddingProviderAPI, EmbeddingProviderLocal:\n\t\treturn s.embedAPI(texts)\n\tdefault:\n\t\treturn s.embedAPI(texts)\n\t}\n}\n\n// EmbedSingle 生成单个文本的向量嵌入\nfunc (s *EmbeddingService) EmbedSingle(text string) ([]float64, error) {\n\tembeddings, err := s.Embed([]string{text})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(embeddings) == 0 {\n\t\treturn nil, fmt.Errorf(\"未返回嵌入向量\")\n\t}\n\treturn embeddings[0], nil\n}\n\n// IsLocalGo 是否使用纯 Go 本地推理\nfunc (s *EmbeddingService) IsLocalGo() bool {\n\treturn s.provider == EmbeddingProviderLocalGo\n}\n\n// GetProvider 获取当前提供商类型\nfunc (s *EmbeddingService) GetProvider() string {\n\treturn s.provider\n}\n\n// embedAPI 通过 HTTP API 生成嵌入\nfunc (s *EmbeddingService) embedAPI(texts []string) ([][]float64, error) {\n\tembedURL := s.apiURL + \"/v1/embeddings\"\n\n\treqBody := EmbeddingRequest{\n\t\tModel: s.model,\n\t\tInput: texts,\n\t}\n\n\tdata, err := json.Marshal(reqBody)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"序列化请求失败: %w\", err)\n\t}\n\n\treq, err := http.NewRequest(\"POST\", embedURL, bytes.NewReader(data))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"创建请求失败: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tif s.apiKey != \"\" {\n\t\treq.Header.Set(\"Authorization\", \"Bearer \"+s.apiKey)\n\t}\n\n\tresp, err := s.client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"请求 Embedding API 失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"Embedding API 返回错误 (%d): %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar embedResp EmbeddingResponse\n\tif err := json.Unmarshal(body, &embedResp); err != nil {\n\t\treturn nil, fmt.Errorf(\"解析响应失败: %w\", err)\n\t}\n\n\tembeddings := make([][]float64, len(embedResp.Data))\n\tfor i, item := range embedResp.Data {\n\t\tembeddings[i] = item.Embedding\n\t}\n\n\treturn embeddings, nil\n}\n\n// embedLocalGo 通过纯 Go 本地推理生成嵌入\nfunc (s *EmbeddingService) embedLocalGo(texts []string) ([][]float64, error) {\n\tif s.localEngine == nil {\n\t\treturn nil, fmt.Errorf(\"本地 Embedding 引擎未初始化\")\n\t}\n\tif !s.localEngine.IsLoaded() {\n\t\treturn nil, fmt.Errorf(\"本地 Embedding 模型未加载,请先加载模型\")\n\t}\n\treturn s.localEngine.Embed(texts)\n}\n\n// CosineSimilarity 计算两个向量的余弦相似度\nfunc CosineSimilarity(a, b []float64) float64 {\n\tif len(a) != len(b) {\n\t\treturn 0\n\t}\n\n\tvar dotProduct, normA, normB float64\n\tfor i := range a {\n\t\tdotProduct += a[i] * b[i]\n\t\tnormA += a[i] * a[i]\n\t\tnormB += b[i] * b[i]\n\t}\n\n\tif normA == 0 || normB == 0 {\n\t\treturn 0\n\t}\n\n\treturn dotProduct / (sqrt(normA) * sqrt(normB))\n}\n\nfunc sqrt(x float64) float64 {\n\tif x <= 0 {\n\t\treturn 0\n\t}\n\t// Newton's method\n\tz := x\n\tfor i := 0; i < 10; i++ {\n\t\tz = (z + x/z) / 2\n\t}\n\treturn z\n}"}}}} |