Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/aliuyanfeng_happytools.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
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.
2026-08-07 12:53:47 +08:00

1 line
5.4 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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}"}}}}