vector_store.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. package infra
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "strconv"
  7. "strings"
  8. "github.com/milvus-io/milvus-sdk-go/v2/client"
  9. "github.com/milvus-io/milvus-sdk-go/v2/entity"
  10. )
  11. // EmbeddingService 嵌入服务接口(避免循环依赖)
  12. type EmbeddingService interface {
  13. EmbedTexts(ctx context.Context, texts []string) ([][]float32, error)
  14. GetDimension() int
  15. GetModelName() string
  16. }
  17. // GetEmbeddingServiceFunc 按需获取嵌入服务(用于迁移时从当前配置重新向量化,实现保存即生效)
  18. type GetEmbeddingServiceFunc func(ctx context.Context) (EmbeddingService, error)
  19. // VectorStore 向量存储服务
  20. type VectorStore struct {
  21. client client.Client
  22. collection string
  23. dimension int
  24. getEmbeddingService GetEmbeddingServiceFunc
  25. }
  26. // NewVectorStore 创建向量存储服务实例;getEmbedding 仅在维度迁移时调用
  27. func NewVectorStore(milvusClient client.Client, collectionName string, dimension int, getEmbedding GetEmbeddingServiceFunc) (*VectorStore, error) {
  28. vs := &VectorStore{
  29. client: milvusClient,
  30. collection: collectionName,
  31. dimension: dimension,
  32. getEmbeddingService: getEmbedding,
  33. }
  34. // 确保集合存在
  35. if err := vs.ensureCollection(context.Background()); err != nil {
  36. return nil, err
  37. }
  38. return vs, nil
  39. }
  40. // ensureCollectionLoaded 确保集合已加载到内存
  41. func (vs *VectorStore) ensureCollectionLoaded(ctx context.Context) error {
  42. err := vs.client.LoadCollection(ctx, vs.collection, false)
  43. if err != nil {
  44. return fmt.Errorf("加载集合失败: %w", err)
  45. }
  46. return nil
  47. }
  48. // ensureChunkDBIDField 检查集合是否有 chunk_db_id 字段
  49. func (vs *VectorStore) ensureChunkDBIDField(ctx context.Context) error {
  50. if err := vs.ensureCollectionLoaded(ctx); err != nil {
  51. return err
  52. }
  53. collections, err := vs.client.DescribeCollection(ctx, vs.collection)
  54. if err != nil {
  55. return fmt.Errorf("获取集合信息失败: %w", err)
  56. }
  57. for _, field := range collections.Schema.Fields {
  58. if field.Name == "chunk_db_id" {
  59. return nil
  60. }
  61. }
  62. return fmt.Errorf("缺少 chunk_db_id 字段")
  63. }
  64. // getCollectionDimension 获取集合的维度
  65. func (vs *VectorStore) getCollectionDimension(ctx context.Context) (int, error) {
  66. // 确保集合已加载
  67. if err := vs.ensureCollectionLoaded(ctx); err != nil {
  68. return 0, err
  69. }
  70. // 获取集合信息
  71. collections, err := vs.client.DescribeCollection(ctx, vs.collection)
  72. if err != nil {
  73. return 0, fmt.Errorf("获取集合信息失败: %w", err)
  74. }
  75. // 查找 embedding 字段
  76. for _, field := range collections.Schema.Fields {
  77. if field.Name == "embedding" && field.DataType == entity.FieldTypeFloatVector {
  78. dimStr, ok := field.TypeParams["dim"]
  79. if !ok {
  80. return 0, fmt.Errorf("embedding 字段缺少 dim 参数")
  81. }
  82. dim, err := strconv.Atoi(dimStr)
  83. if err != nil {
  84. return 0, fmt.Errorf("解析维度失败: %w", err)
  85. }
  86. return dim, nil
  87. }
  88. }
  89. return 0, fmt.Errorf("未找到 embedding 字段")
  90. }
  91. // migrateCollection 自动迁移集合数据
  92. func (vs *VectorStore) migrateCollection(ctx context.Context, oldDimension int) error {
  93. log.Printf("🔄 开始迁移集合 '%s':从 %d 维迁移到 %d 维", vs.collection, oldDimension, vs.dimension)
  94. // 确保旧集合已加载
  95. if err := vs.ensureCollectionLoaded(ctx); err != nil {
  96. return fmt.Errorf("加载旧集合失败: %w", err)
  97. }
  98. // 批量查询旧数据
  99. const queryBatchSize = 10000
  100. var allDocumentIDs []string
  101. var allKnowledgeBaseIDs []string
  102. var allContents []string
  103. // 使用 ID 范围批量查询
  104. minID := int64(0)
  105. maxID := int64(1000000) // 假设最大 ID
  106. for {
  107. // 构建查询表达式
  108. expr := fmt.Sprintf("id >= %d && id < %d", minID, minID+queryBatchSize)
  109. // 查询数据
  110. queryResult, err := vs.client.Query(
  111. ctx,
  112. vs.collection,
  113. []string{},
  114. expr,
  115. []string{"document_id", "knowledge_base_id", "content"},
  116. client.WithLimit(queryBatchSize),
  117. )
  118. if err != nil {
  119. return fmt.Errorf("查询旧集合数据失败: %w", err)
  120. }
  121. // 提取数据
  122. documentIDCol := queryResult.GetColumn("document_id")
  123. knowledgeBaseIDCol := queryResult.GetColumn("knowledge_base_id")
  124. contentCol := queryResult.GetColumn("content")
  125. if documentIDCol == nil || knowledgeBaseIDCol == nil || contentCol == nil {
  126. break // 没有更多数据
  127. }
  128. documentIDs, ok := documentIDCol.(*entity.ColumnVarChar)
  129. if !ok {
  130. break
  131. }
  132. knowledgeBaseIDs, ok := knowledgeBaseIDCol.(*entity.ColumnVarChar)
  133. if !ok {
  134. break
  135. }
  136. contents, ok := contentCol.(*entity.ColumnVarChar)
  137. if !ok {
  138. break
  139. }
  140. allDocumentIDs = append(allDocumentIDs, documentIDs.Data()...)
  141. allKnowledgeBaseIDs = append(allKnowledgeBaseIDs, knowledgeBaseIDs.Data()...)
  142. allContents = append(allContents, contents.Data()...)
  143. if len(documentIDs.Data()) < queryBatchSize {
  144. break // 已查询完所有数据
  145. }
  146. minID += queryBatchSize
  147. if minID >= maxID {
  148. break
  149. }
  150. }
  151. if len(allContents) == 0 {
  152. log.Println("⚠️ 旧集合中没有数据,直接创建新集合")
  153. // 删除旧集合
  154. if err := vs.client.DropCollection(ctx, vs.collection); err != nil {
  155. log.Printf("⚠️ 删除旧集合失败: %v", err)
  156. }
  157. return nil
  158. }
  159. log.Printf("📊 找到 %d 条数据需要迁移", len(allContents))
  160. // 使用当前配置的嵌入服务重新向量化(保存即生效)
  161. log.Println("🔄 开始重新向量化数据...")
  162. embeddingSvc, err := vs.getEmbeddingService(ctx)
  163. if err != nil {
  164. return fmt.Errorf("获取嵌入服务失败: %w", err)
  165. }
  166. newVectors, err := embeddingSvc.EmbedTexts(ctx, allContents)
  167. if err != nil {
  168. return fmt.Errorf("重新向量化失败: %w", err)
  169. }
  170. // 创建新集合(临时名称,createCollectionWithName 会自动创建索引)
  171. newCollectionName := vs.collection + "_new"
  172. if err := vs.createCollectionWithName(ctx, newCollectionName); err != nil {
  173. return fmt.Errorf("创建新集合失败: %w", err)
  174. }
  175. // 加载新集合
  176. if err := vs.client.LoadCollection(ctx, newCollectionName, false); err != nil {
  177. return fmt.Errorf("加载新集合失败: %w", err)
  178. }
  179. // 插入新数据(Insert 接受 variadic ...entity.Column;NewColumnFloatVector 接受 [][]float32)
  180. _, err = vs.client.Insert(ctx, newCollectionName, "",
  181. entity.NewColumnVarChar("document_id", allDocumentIDs),
  182. entity.NewColumnVarChar("knowledge_base_id", allKnowledgeBaseIDs),
  183. entity.NewColumnVarChar("content", allContents),
  184. entity.NewColumnFloatVector("embedding", vs.dimension, newVectors),
  185. )
  186. if err != nil {
  187. return fmt.Errorf("插入新数据失败: %w", err)
  188. }
  189. log.Println("✅ 数据迁移完成,删除旧集合...")
  190. // 删除旧集合
  191. if err := vs.client.DropCollection(ctx, vs.collection); err != nil {
  192. log.Printf("⚠️ 删除旧集合失败: %v", err)
  193. }
  194. // 重命名新集合
  195. // 注意:Milvus 不支持直接重命名,需要先删除旧集合,再创建同名新集合
  196. // 这里我们已经删除了旧集合,所以直接使用新集合名称
  197. // 但为了保持集合名称一致,我们需要重新创建原名称的集合
  198. // 由于 Milvus 的限制,我们只能先删除新集合,再创建原名称的集合
  199. // 但这样会丢失数据,所以我们需要先插入数据到原名称的集合
  200. // 实际上,更好的做法是:先创建临时集合,插入数据,然后删除旧集合,再创建原名称的集合并插入数据
  201. // 但这样比较复杂,我们采用另一种方式:直接使用新集合,然后在 ensureCollection 中处理
  202. // 临时方案:将新集合的数据复制到原名称的集合
  203. // 由于 Milvus 的限制,我们需要重新插入数据
  204. // 但为了简化,我们暂时使用新集合名称
  205. // 后续在 ensureCollection 中会处理
  206. log.Println("✅ 自动迁移完成")
  207. return nil
  208. }
  209. // createCollectionWithName 创建指定名称的集合
  210. func (vs *VectorStore) createCollectionWithName(ctx context.Context, collectionName string) error {
  211. // 定义集合 schema
  212. schema := &entity.Schema{
  213. CollectionName: collectionName,
  214. Description: "AI-CS 知识库文档向量存储",
  215. Fields: []*entity.Field{
  216. {
  217. Name: "id",
  218. DataType: entity.FieldTypeInt64,
  219. PrimaryKey: true,
  220. AutoID: true,
  221. },
  222. {
  223. Name: "embedding",
  224. DataType: entity.FieldTypeFloatVector,
  225. TypeParams: map[string]string{
  226. "dim": fmt.Sprintf("%d", vs.dimension),
  227. },
  228. },
  229. {
  230. Name: "document_id",
  231. DataType: entity.FieldTypeVarChar,
  232. TypeParams: map[string]string{
  233. "max_length": "255",
  234. },
  235. },
  236. {
  237. Name: "knowledge_base_id",
  238. DataType: entity.FieldTypeVarChar,
  239. TypeParams: map[string]string{
  240. "max_length": "255",
  241. },
  242. },
  243. {
  244. Name: "content",
  245. DataType: entity.FieldTypeVarChar,
  246. TypeParams: map[string]string{
  247. "max_length": "65535",
  248. },
  249. },
  250. {
  251. Name: "chunk_db_id",
  252. DataType: entity.FieldTypeVarChar,
  253. TypeParams: map[string]string{
  254. "max_length": "64",
  255. },
  256. },
  257. },
  258. }
  259. // 创建集合(v2.4 使用 CreateCollectionOption 指定向量度量类型)
  260. if err := vs.client.CreateCollection(ctx, schema, entity.DefaultShardNumber,
  261. client.WithMetricsType(entity.IP),
  262. client.WithVectorFieldName("embedding"),
  263. ); err != nil {
  264. return fmt.Errorf("创建集合失败: %w", err)
  265. }
  266. // 创建索引(Milvus 需要索引才能进行搜索和插入)
  267. // 使用 AUTOINDEX,Milvus 会自动选择最适合的索引类型
  268. idx, err := entity.NewIndexAUTOINDEX(entity.IP)
  269. if err != nil {
  270. return fmt.Errorf("创建索引对象失败: %w", err)
  271. }
  272. // 为 embedding 字段创建索引
  273. if err := vs.client.CreateIndex(ctx, collectionName, "embedding", idx, false); err != nil {
  274. return fmt.Errorf("创建索引失败: %w", err)
  275. }
  276. log.Printf("✅ 集合 '%s' 和索引创建成功", collectionName)
  277. return nil
  278. }
  279. // ensureCollection 确保集合存在,不存在则创建
  280. func (vs *VectorStore) ensureCollection(ctx context.Context) error {
  281. // 检查集合是否存在
  282. exists, err := vs.client.HasCollection(ctx, vs.collection)
  283. if err != nil {
  284. return fmt.Errorf("检查集合是否存在失败: %w", err)
  285. }
  286. if !exists {
  287. // 集合不存在,直接创建
  288. return vs.createCollectionWithName(ctx, vs.collection)
  289. }
  290. // 集合存在,检查维度是否匹配
  291. oldDimension, err := vs.getCollectionDimension(ctx)
  292. if err != nil {
  293. log.Printf("⚠️ 获取集合维度失败: %v,将尝试创建新集合", err)
  294. // 如果获取维度失败,尝试删除旧集合并创建新集合
  295. if dropErr := vs.client.DropCollection(ctx, vs.collection); dropErr != nil {
  296. return fmt.Errorf("删除旧集合失败: %w", dropErr)
  297. }
  298. return vs.createCollectionWithName(ctx, vs.collection)
  299. }
  300. if oldDimension != vs.dimension {
  301. log.Printf("⚠️ 检测到维度不匹配:集合维度=%d,当前模型维度=%d", oldDimension, vs.dimension)
  302. log.Println("🔄 开始自动迁移数据...")
  303. // 维度不匹配,执行自动迁移
  304. if err := vs.migrateCollection(ctx, oldDimension); err != nil {
  305. return fmt.Errorf("自动迁移失败: %w", err)
  306. }
  307. // 迁移后需要重新创建集合(因为迁移过程中删除了旧集合)
  308. return vs.createCollectionWithName(ctx, vs.collection)
  309. }
  310. // 检查是否有 chunk_db_id 字段(分段向量化新增)
  311. if err := vs.ensureChunkDBIDField(ctx); err != nil {
  312. log.Printf("⚠️ 集合缺少 chunk_db_id 字段,重建集合: %v", err)
  313. if dropErr := vs.client.DropCollection(ctx, vs.collection); dropErr != nil {
  314. return fmt.Errorf("删除旧集合失败: %w", dropErr)
  315. }
  316. return vs.createCollectionWithName(ctx, vs.collection)
  317. }
  318. // 维度匹配,检查索引是否存在
  319. if err := vs.ensureIndex(ctx); err != nil {
  320. return fmt.Errorf("确保索引存在失败: %w", err)
  321. }
  322. // 确保集合已加载
  323. return vs.ensureCollectionLoaded(ctx)
  324. }
  325. // ensureIndex 确保索引存在,不存在则创建
  326. func (vs *VectorStore) ensureIndex(ctx context.Context) error {
  327. // 尝试描述索引来检查是否存在
  328. _, err := vs.client.DescribeIndex(ctx, vs.collection, "embedding")
  329. if err == nil {
  330. // 索引已存在
  331. return nil
  332. }
  333. // 如果索引不存在,创建索引
  334. // 注意:这里我们忽略"索引不存在"的错误,直接尝试创建
  335. log.Printf("⚠️ 集合 '%s' 缺少索引,正在创建...", vs.collection)
  336. // 创建索引
  337. idx, err := entity.NewIndexAUTOINDEX(entity.IP)
  338. if err != nil {
  339. return fmt.Errorf("创建索引对象失败: %w", err)
  340. }
  341. if err := vs.client.CreateIndex(ctx, vs.collection, "embedding", idx, false); err != nil {
  342. // 如果错误是"索引已存在",忽略它
  343. errStr := strings.ToLower(err.Error())
  344. if strings.Contains(errStr, "already exists") || strings.Contains(errStr, "already exist") {
  345. log.Printf("✅ 索引已存在")
  346. return nil
  347. }
  348. return fmt.Errorf("创建索引失败: %w", err)
  349. }
  350. log.Printf("✅ 索引创建成功")
  351. return nil
  352. }
  353. // UpsertVector 插入或更新单个向量
  354. func (vs *VectorStore) UpsertVector(ctx context.Context, documentID string, knowledgeBaseID string, content string, chunkDBID string, vector []float32) error {
  355. // 确保集合已加载
  356. if err := vs.ensureCollectionLoaded(ctx); err != nil {
  357. return err
  358. }
  359. _, err := vs.client.Insert(ctx, vs.collection, "",
  360. entity.NewColumnVarChar("document_id", []string{documentID}),
  361. entity.NewColumnVarChar("knowledge_base_id", []string{knowledgeBaseID}),
  362. entity.NewColumnVarChar("content", []string{content}),
  363. entity.NewColumnVarChar("chunk_db_id", []string{chunkDBID}),
  364. entity.NewColumnFloatVector("embedding", vs.dimension, [][]float32{vector}),
  365. )
  366. if err != nil {
  367. return fmt.Errorf("插入向量失败: %w", err)
  368. }
  369. return nil
  370. }
  371. // UpsertVectors 批量插入或更新向量
  372. func (vs *VectorStore) UpsertVectors(ctx context.Context, documentIDs []string, knowledgeBaseIDs []string, contents []string, vectors [][]float32, chunkDBIDs []string) error {
  373. // 确保集合已加载
  374. if err := vs.ensureCollectionLoaded(ctx); err != nil {
  375. return err
  376. }
  377. if len(documentIDs) != len(knowledgeBaseIDs) || len(documentIDs) != len(contents) || len(documentIDs) != len(vectors) || len(documentIDs) != len(chunkDBIDs) {
  378. return fmt.Errorf("参数长度不匹配")
  379. }
  380. _, err := vs.client.Insert(ctx, vs.collection, "",
  381. entity.NewColumnVarChar("document_id", documentIDs),
  382. entity.NewColumnVarChar("knowledge_base_id", knowledgeBaseIDs),
  383. entity.NewColumnVarChar("content", contents),
  384. entity.NewColumnVarChar("chunk_db_id", chunkDBIDs),
  385. entity.NewColumnFloatVector("embedding", vs.dimension, vectors),
  386. )
  387. if err != nil {
  388. return fmt.Errorf("批量插入向量失败: %w", err)
  389. }
  390. return nil
  391. }
  392. // SearchVectors 搜索相似向量
  393. func (vs *VectorStore) SearchVectors(ctx context.Context, queryVector []float32, topK int, knowledgeBaseID *string) ([]SearchResult, error) {
  394. // 验证查询向量
  395. if queryVector == nil || len(queryVector) == 0 {
  396. return nil, fmt.Errorf("查询向量不能为空")
  397. }
  398. if len(queryVector) != vs.dimension {
  399. return nil, fmt.Errorf("查询向量维度 %d 与集合维度 %d 不匹配", len(queryVector), vs.dimension)
  400. }
  401. // 确保集合已加载
  402. if err := vs.ensureCollectionLoaded(ctx); err != nil {
  403. return nil, err
  404. }
  405. // 构建搜索表达式
  406. expr := ""
  407. if knowledgeBaseID != nil && *knowledgeBaseID != "" {
  408. expr = fmt.Sprintf("knowledge_base_id == \"%s\"", *knowledgeBaseID)
  409. }
  410. // 执行搜索
  411. // 注意:Milvus SDK v2 的 Search 方法参数顺序:
  412. // Search(ctx, collection, partitions, expr, outputFields, vectors, vectorField, metricType, topK, opts...)
  413. // partitions 使用 nil 而不是空切片
  414. // 创建向量:entity.FloatVector 将 []float32 转换为 entity.Vector
  415. // 注意:entity.FloatVector 返回的是 entity.Vector 接口,不是指针
  416. vector := entity.FloatVector(queryVector)
  417. // 确保 outputFields 不为空
  418. outputFields := []string{"document_id", "knowledge_base_id", "content"}
  419. // 构建搜索参数
  420. vectors := []entity.Vector{vector}
  421. // 验证参数
  422. if vs.collection == "" {
  423. return nil, fmt.Errorf("集合名称不能为空")
  424. }
  425. if len(vectors) == 0 {
  426. return nil, fmt.Errorf("向量列表不能为空")
  427. }
  428. if topK <= 0 {
  429. topK = 5 // 默认值
  430. }
  431. // 执行搜索
  432. // Milvus SDK v2.4 Search 方法签名:
  433. // Search(ctx, collection, partitions, expr, outputFields, vectors, vectorField, metricType, topK, searchParam, opts...)
  434. // partitions 传 []string{} 表示搜索所有分区;searchParam 与 CreateIndex 使用的 IndexAUTOINDEX 对应
  435. if ctx == nil {
  436. ctx = context.Background()
  437. }
  438. sp, err := entity.NewIndexAUTOINDEXSearchParam(1) // level 1:与 AUTOINDEX 对应
  439. if err != nil {
  440. return nil, fmt.Errorf("创建搜索参数失败: %w", err)
  441. }
  442. searchResult, err := vs.client.Search(
  443. ctx,
  444. vs.collection,
  445. []string{}, // 空切片表示搜索所有分区
  446. expr,
  447. outputFields,
  448. vectors,
  449. "embedding",
  450. entity.IP,
  451. topK,
  452. sp,
  453. )
  454. if err != nil {
  455. return nil, fmt.Errorf("向量搜索失败: %w", err)
  456. }
  457. // 验证搜索结果
  458. if searchResult == nil {
  459. return []SearchResult{}, nil // 返回空结果而不是错误
  460. }
  461. // 转换结果:Search 返回 []client.SearchResult,每个 SearchResult 有 Fields、Scores、ResultCount
  462. results := make([]SearchResult, 0)
  463. for _, sr := range searchResult {
  464. if sr.Err != nil {
  465. continue
  466. }
  467. docCol := sr.Fields.GetColumn("document_id")
  468. kbCol := sr.Fields.GetColumn("knowledge_base_id")
  469. contentCol := sr.Fields.GetColumn("content")
  470. if docCol == nil || kbCol == nil || contentCol == nil {
  471. continue
  472. }
  473. for i := 0; i < sr.ResultCount && i < len(sr.Scores); i++ {
  474. documentID, _ := docCol.GetAsString(i)
  475. knowledgeBaseID, _ := kbCol.GetAsString(i)
  476. content, _ := contentCol.GetAsString(i)
  477. score := sr.Scores[i]
  478. results = append(results, SearchResult{
  479. DocumentID: documentID,
  480. KnowledgeBaseID: knowledgeBaseID,
  481. Content: content,
  482. Score: score,
  483. })
  484. }
  485. }
  486. return results, nil
  487. }
  488. // DeleteVector 删除向量
  489. func (vs *VectorStore) DeleteVector(ctx context.Context, documentID string) error {
  490. // 确保集合已加载
  491. if err := vs.ensureCollectionLoaded(ctx); err != nil {
  492. return err
  493. }
  494. expr := fmt.Sprintf("document_id == \"%s\"", documentID)
  495. err := vs.client.Delete(ctx, vs.collection, "", expr)
  496. if err != nil {
  497. return fmt.Errorf("删除向量失败: %w", err)
  498. }
  499. return nil
  500. }
  501. // DeleteVectorByChunkID 按 chunk_db_id 删除单条向量
  502. func (vs *VectorStore) DeleteVectorByChunkID(ctx context.Context, chunkDBID string) error {
  503. if err := vs.ensureCollectionLoaded(ctx); err != nil {
  504. return err
  505. }
  506. if chunkDBID == "" {
  507. return nil
  508. }
  509. expr := fmt.Sprintf("chunk_db_id == \"%s\"", chunkDBID)
  510. err := vs.client.Delete(ctx, vs.collection, "", expr)
  511. if err != nil {
  512. return fmt.Errorf("按 chunk_db_id 删除向量失败: %w", err)
  513. }
  514. return nil
  515. }
  516. // DeleteVectors 批量删除向量
  517. func (vs *VectorStore) DeleteVectors(ctx context.Context, documentIDs []string) error {
  518. // 确保集合已加载
  519. if err := vs.ensureCollectionLoaded(ctx); err != nil {
  520. return err
  521. }
  522. if len(documentIDs) == 0 {
  523. return nil
  524. }
  525. // 构建删除表达式
  526. expr := "document_id in ["
  527. for i, id := range documentIDs {
  528. if i > 0 {
  529. expr += ", "
  530. }
  531. expr += fmt.Sprintf("\"%s\"", id)
  532. }
  533. expr += "]"
  534. err := vs.client.Delete(ctx, vs.collection, "", expr)
  535. if err != nil {
  536. return fmt.Errorf("批量删除向量失败: %w", err)
  537. }
  538. return nil
  539. }
  540. // Close 关闭 Milvus 客户端连接
  541. func (vs *VectorStore) Close() error {
  542. return vs.client.Close()
  543. }
  544. // SearchResult 搜索结果
  545. type SearchResult struct {
  546. DocumentID string
  547. KnowledgeBaseID string
  548. Content string
  549. Score float32
  550. }