From 104a65ad2333ed56eea3432269d77395324d1013 Mon Sep 17 00:00:00 2001 From: begoniezhao Date: Fri, 8 Aug 2025 17:19:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=A4=9A=E6=A8=A1?= =?UTF-8?q?=E6=80=81=E5=8F=82=E6=95=B0=E5=8F=8A=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E4=BC=98=E5=8C=96=E5=9B=BE=E7=89=87=E4=B8=8E?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/example.go | 2 +- client/knowledge.go | 17 +- internal/application/service/knowledge.go | 287 +++++++++++++++------- internal/handler/knowledge.go | 20 +- internal/types/interfaces/knowledge.go | 3 +- 5 files changed, 237 insertions(+), 92 deletions(-) diff --git a/client/example.go b/client/example.go index cda4fa5..62833ae 100644 --- a/client/example.go +++ b/client/example.go @@ -64,7 +64,7 @@ func ExampleUsage() { "source": "local", "type": "document", } - knowledge, err := apiClient.CreateKnowledgeFromFile(context.Background(), createdKB.ID, filePath, metadata) + knowledge, err := apiClient.CreateKnowledgeFromFile(context.Background(), createdKB.ID, filePath, metadata, nil) if err != nil { fmt.Printf("Failed to upload knowledge file: %v\n", err) } else { diff --git a/client/knowledge.go b/client/knowledge.go index 04e14fb..8e773de 100644 --- a/client/knowledge.go +++ b/client/knowledge.go @@ -76,7 +76,7 @@ var ErrDuplicateFile = errors.New("file already exists") // CreateKnowledgeFromFile creates a knowledge entry from a local file path func (c *Client) CreateKnowledgeFromFile(ctx context.Context, - knowledgeBaseID string, filePath string, metadata map[string]string, + knowledgeBaseID string, filePath string, metadata map[string]string, enableMultimodel *bool, ) (*Knowledge, error) { // Open the local file file, err := os.Open(filePath) @@ -112,6 +112,13 @@ func (c *Client) CreateKnowledgeFromFile(ctx context.Context, return nil, fmt.Errorf("failed to copy file content: %w", err) } + // Add enable_multimodel field + if enableMultimodel != nil { + if err := writer.WriteField("enable_multimodel", strconv.FormatBool(*enableMultimodel)); err != nil { + return nil, fmt.Errorf("failed to write enable_multimodel field: %w", err) + } + } + // Add metadata to the request if provided if metadata != nil { metadataBytes, err := json.Marshal(metadata) @@ -162,13 +169,15 @@ func (c *Client) CreateKnowledgeFromFile(ctx context.Context, } // CreateKnowledgeFromURL creates a knowledge entry from a web URL -func (c *Client) CreateKnowledgeFromURL(ctx context.Context, knowledgeBaseID string, url string) (*Knowledge, error) { +func (c *Client) CreateKnowledgeFromURL(ctx context.Context, knowledgeBaseID string, url string, enableMultimodel *bool) (*Knowledge, error) { path := fmt.Sprintf("/api/v1/knowledge-bases/%s/knowledge/url", knowledgeBaseID) reqBody := struct { - URL string `json:"url"` + URL string `json:"url"` + EnableMultimodel *bool `json:"enable_multimodel"` }{ - URL: url, + URL: url, + EnableMultimodel: enableMultimodel, } resp, err := c.doRequest(ctx, http.MethodPost, path, reqBody, nil) diff --git a/internal/application/service/knowledge.go b/internal/application/service/knowledge.go index ccee9b3..b093cf5 100644 --- a/internal/application/service/knowledge.go +++ b/internal/application/service/knowledge.go @@ -29,6 +29,7 @@ import ( "github.com/Tencent/WeKnora/services/docreader/src/proto" "github.com/google/uuid" "go.opentelemetry.io/otel/attribute" + "golang.org/x/sync/errgroup" ) // Error definitions for knowledge service operations @@ -43,6 +44,8 @@ var ( ErrDuplicateFile = errors.New("file already exists") // ErrDuplicateURL is returned when trying to add a URL that already exists ErrDuplicateURL = errors.New("URL already exists") + // ErrImageNotParse is returned when trying to update image information without enabling multimodel + ErrImageNotParse = errors.New("image not parse without enable multimodel") ) // knowledgeService implements the knowledge service interface @@ -86,7 +89,7 @@ func NewKnowledgeService( // CreateKnowledgeFromFile creates a knowledge entry from an uploaded file func (s *knowledgeService) CreateKnowledgeFromFile(ctx context.Context, - kbID string, file *multipart.FileHeader, metadata map[string]string, + kbID string, file *multipart.FileHeader, metadata map[string]string, enableMultimodel *bool, ) (*types.Knowledge, error) { logger.Info(ctx, "Start creating knowledge from file") logger.Infof(ctx, "Knowledge base ID: %s, file: %s", kbID, file.Filename) @@ -203,7 +206,10 @@ func (s *knowledgeService) CreateKnowledgeFromFile(ctx context.Context, // Process document asynchronously logger.Info(ctx, "Starting asynchronous document processing") newCtx := logger.CloneContext(ctx) - go s.processDocument(newCtx, kb, knowledge, file) + if enableMultimodel == nil { + enableMultimodel = &kb.ChunkingConfig.EnableMultimodal + } + go s.processDocument(newCtx, kb, knowledge, file, *enableMultimodel) logger.Infof(ctx, "Knowledge from file created successfully, ID: %s", knowledge.ID) return knowledge, nil @@ -211,7 +217,7 @@ func (s *knowledgeService) CreateKnowledgeFromFile(ctx context.Context, // CreateKnowledgeFromURL creates a knowledge entry from a URL source func (s *knowledgeService) CreateKnowledgeFromURL(ctx context.Context, - kbID string, url string, + kbID string, url string, enableMultimodel *bool, ) (*types.Knowledge, error) { logger.Info(ctx, "Start creating knowledge from URL") logger.Infof(ctx, "Knowledge base ID: %s, URL: %s", kbID, url) @@ -285,7 +291,10 @@ func (s *knowledgeService) CreateKnowledgeFromURL(ctx context.Context, // Process URL asynchronously logger.Info(ctx, "Starting asynchronous URL processing") - go s.processDocumentFromURL(ctx, kb, knowledge, url) + if enableMultimodel == nil { + enableMultimodel = &kb.ChunkingConfig.EnableMultimodal + } + go s.processDocumentFromURL(ctx, kb, knowledge, url, *enableMultimodel) logger.Infof(ctx, "Knowledge from URL created successfully, ID: %s", knowledge.ID) return knowledge, nil @@ -383,33 +392,53 @@ func (s *knowledgeService) DeleteKnowledge(ctx context.Context, id string) error if err != nil { return err } + wg := errgroup.Group{} // Delete knowledge embeddings from vector store - tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant) - retrieveEngine, err := retriever.NewCompositeRetrieveEngine(tenantInfo.RetrieverEngines.Engines) - if err != nil { - logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete knowledge embedding failed") - } - embeddingModel, err := s.modelService.GetEmbeddingModel(ctx, knowledge.EmbeddingModelID) - if err != nil { - logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete knowledge embedding failed") - } - - if err := retrieveEngine.DeleteByKnowledgeIDList(ctx, []string{id}, embeddingModel.GetDimensions()); err != nil { - logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete knowledge embedding failed") - } - // Delete all chunks associated with this knowledge - if err := s.chunkService.DeleteChunksByKnowledgeID(ctx, id); err != nil { - logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete chunks failed") - } - // Delete the physical file if it exists - if knowledge.FilePath != "" { - if err := s.fileSvc.DeleteFile(ctx, knowledge.FilePath); err != nil { - logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete file failed") + wg.Go(func() error { + tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant) + retrieveEngine, err := retriever.NewCompositeRetrieveEngine(tenantInfo.RetrieverEngines.Engines) + if err != nil { + logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete knowledge embedding failed") + return err } - } - tenantInfo.StorageUsed -= knowledge.StorageSize - if err := s.tenantRepo.AdjustStorageUsed(ctx, tenantInfo.ID, -knowledge.StorageSize); err != nil { - logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge update tenant storage used failed") + embeddingModel, err := s.modelService.GetEmbeddingModel(ctx, knowledge.EmbeddingModelID) + if err != nil { + logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete knowledge embedding failed") + return err + } + if err := retrieveEngine.DeleteByKnowledgeIDList(ctx, []string{knowledge.ID}, embeddingModel.GetDimensions()); err != nil { + logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete knowledge embedding failed") + return err + } + return nil + }) + + // Delete all chunks associated with this knowledge + wg.Go(func() error { + if err := s.chunkService.DeleteChunksByKnowledgeID(ctx, knowledge.ID); err != nil { + logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete chunks failed") + return err + } + return nil + }) + + // Delete the physical file if it exists + wg.Go(func() error { + if knowledge.FilePath != "" { + if err := s.fileSvc.DeleteFile(ctx, knowledge.FilePath); err != nil { + logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete file failed") + } + } + tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant) + tenantInfo.StorageUsed -= knowledge.StorageSize + if err := s.tenantRepo.AdjustStorageUsed(ctx, tenantInfo.ID, -knowledge.StorageSize); err != nil { + logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge update tenant storage used failed") + } + return nil + }) + + if err = wg.Wait(); err != nil { + return err } // Delete the knowledge entry itself from the database return s.repo.DeleteKnowledge(ctx, ctx.Value(types.TenantIDContextKey).(uint), id) @@ -420,55 +449,70 @@ func (s *knowledgeService) DeleteKnowledgeList(ctx context.Context, ids []string if len(ids) == 0 { return nil } - tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant) // 1. Get the knowledge entry + tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant) knowledgeList, err := s.repo.GetKnowledgeBatch(ctx, tenantInfo.ID, ids) if err != nil { return err } + wg := errgroup.Group{} // 2. Delete knowledge embeddings from vector store - retrieveEngine, err := retriever.NewCompositeRetrieveEngine(tenantInfo.RetrieverEngines.Engines) - if err != nil { - logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete knowledge embedding failed") - return err - } - group := map[string][]string{} - for _, knowledge := range knowledgeList { - group[knowledge.EmbeddingModelID] = append(group[knowledge.EmbeddingModelID], knowledge.ID) - } - for embeddingModelID, knowledgeList := range group { - embeddingModel, err := s.modelService.GetEmbeddingModel(ctx, embeddingModelID) + wg.Go(func() error { + tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant) + retrieveEngine, err := retriever.NewCompositeRetrieveEngine(tenantInfo.RetrieverEngines.Engines) if err != nil { - logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge get embedding model failed") - continue - } - if err := retrieveEngine.DeleteByKnowledgeIDList(ctx, knowledgeList, embeddingModel.GetDimensions()); err != nil { logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete knowledge embedding failed") - continue + return err } - } - - // 3. Delete all chunks associated with this knowledge - if err := s.chunkService.DeleteByKnowledgeList(ctx, ids); err != nil { - logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete chunks failed") - } - - // 4. Delete the physical file if it exists - storageAdjust := int64(0) - for _, knowledge := range knowledgeList { - if knowledge.FilePath != "" { - if err := s.fileSvc.DeleteFile(ctx, knowledge.FilePath); err != nil { - logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete file failed") + group := map[string][]string{} + for _, knowledge := range knowledgeList { + group[knowledge.EmbeddingModelID] = append(group[knowledge.EmbeddingModelID], knowledge.ID) + } + for embeddingModelID, knowledgeList := range group { + embeddingModel, err := s.modelService.GetEmbeddingModel(ctx, embeddingModelID) + if err != nil { + logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge get embedding model failed") + return err + } + if err := retrieveEngine.DeleteByKnowledgeIDList(ctx, knowledgeList, embeddingModel.GetDimensions()); err != nil { + logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete knowledge embedding failed") + return err } } - storageAdjust -= knowledge.StorageSize - } - tenantInfo.StorageUsed += storageAdjust - if err := s.tenantRepo.AdjustStorageUsed(ctx, tenantInfo.ID, storageAdjust); err != nil { - logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge update tenant storage used failed") - } + return nil + }) + // 3. Delete all chunks associated with this knowledge + wg.Go(func() error { + if err := s.chunkService.DeleteByKnowledgeList(ctx, ids); err != nil { + logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete chunks failed") + return err + } + return nil + }) + + // 4. Delete the physical file if it exists + wg.Go(func() error { + storageAdjust := int64(0) + for _, knowledge := range knowledgeList { + if knowledge.FilePath != "" { + if err := s.fileSvc.DeleteFile(ctx, knowledge.FilePath); err != nil { + logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge delete file failed") + } + } + storageAdjust -= knowledge.StorageSize + } + tenantInfo.StorageUsed += storageAdjust + if err := s.tenantRepo.AdjustStorageUsed(ctx, tenantInfo.ID, storageAdjust); err != nil { + logger.GetLogger(ctx).WithField("error", err).Errorf("DeleteKnowledge update tenant storage used failed") + } + return nil + }) + + if err = wg.Wait(); err != nil { + return err + } // 5. Delete the knowledge entry itself from the database return s.repo.DeleteKnowledgeList(ctx, tenantInfo.ID, ids) } @@ -531,8 +575,10 @@ func (s *knowledgeService) cloneKnowledge(ctx context.Context, src *types.Knowle // processDocument handles asynchronous processing of document files func (s *knowledgeService) processDocument(ctx context.Context, - kb *types.KnowledgeBase, knowledge *types.Knowledge, file *multipart.FileHeader, + kb *types.KnowledgeBase, knowledge *types.Knowledge, file *multipart.FileHeader, enableMultimodel bool, ) { + logger.GetLogger(ctx).Infof("processDocument enableMultimodel: %v", enableMultimodel) + ctx, span := tracing.ContextWithSpan(ctx, "knowledgeService.processDocument") defer span.End() span.SetAttributes( @@ -545,7 +591,17 @@ func (s *knowledgeService) processDocument(ctx context.Context, attribute.String("file_path", knowledge.FilePath), attribute.Int64("file_size", knowledge.FileSize), attribute.String("embedding_model", knowledge.EmbeddingModelID), + attribute.Bool("enable_multimodal", enableMultimodel), ) + if !enableMultimodel && IsImageType(knowledge.FileType) { + logger.GetLogger(ctx).WithField("knowledge_id", knowledge.ID). + WithField("error", ErrImageNotParse).Errorf("processDocument image without enable multimodel") + knowledge.ParseStatus = "failed" + knowledge.ErrorMessage = ErrImageNotParse.Error() + s.repo.UpdateKnowledge(ctx, knowledge) + span.RecordError(ErrImageNotParse) + return + } // Update status to processing knowledge.ParseStatus = "processing" @@ -590,7 +646,7 @@ func (s *knowledgeService) processDocument(ctx context.Context, ChunkSize: int32(kb.ChunkingConfig.ChunkSize), ChunkOverlap: int32(kb.ChunkingConfig.ChunkOverlap), Separators: kb.ChunkingConfig.Separators, - EnableMultimodal: kb.ChunkingConfig.EnableMultimodal, + EnableMultimodal: enableMultimodel, }, RequestId: ctx.Value(types.RequestIDContextKey).(string), }) @@ -612,7 +668,7 @@ func (s *knowledgeService) processDocument(ctx context.Context, // processDocumentFromURL handles asynchronous processing of URL content func (s *knowledgeService) processDocumentFromURL(ctx context.Context, - kb *types.KnowledgeBase, knowledge *types.Knowledge, url string, + kb *types.KnowledgeBase, knowledge *types.Knowledge, url string, enableMultimodel bool, ) { // Update status to processing knowledge.ParseStatus = "processing" @@ -620,6 +676,7 @@ func (s *knowledgeService) processDocumentFromURL(ctx context.Context, if err := s.repo.UpdateKnowledge(ctx, knowledge); err != nil { return } + logger.GetLogger(ctx).Infof("processDocumentFromURL enableMultimodel: %v", enableMultimodel) // Fetch and chunk content from URL resp, err := s.docReaderClient.ReadFromURL(ctx, &proto.ReadFromURLRequest{ @@ -629,7 +686,7 @@ func (s *knowledgeService) processDocumentFromURL(ctx context.Context, ChunkSize: int32(kb.ChunkingConfig.ChunkSize), ChunkOverlap: int32(kb.ChunkingConfig.ChunkOverlap), Separators: kb.ChunkingConfig.Separators, - EnableMultimodal: kb.ChunkingConfig.EnableMultimodal, + EnableMultimodal: enableMultimodel, }, RequestId: ctx.Value(types.RequestIDContextKey).(string), }) @@ -780,7 +837,7 @@ func (s *knowledgeService) processChunks(ctx context.Context, TenantID: knowledge.TenantID, KnowledgeID: knowledge.ID, KnowledgeBaseID: knowledge.KnowledgeBaseID, - Content: fmt.Sprintf("图片OCR文本: %s", img.OcrText), + Content: img.OcrText, ChunkIndex: maxSeq + i*100 + 1, // 使用不冲突的索引方式 IsEnabled: true, CreatedAt: time.Now(), @@ -802,7 +859,7 @@ func (s *knowledgeService) processChunks(ctx context.Context, TenantID: knowledge.TenantID, KnowledgeID: knowledge.ID, KnowledgeBaseID: knowledge.KnowledgeBaseID, - Content: fmt.Sprintf("图片描述: %s", img.Caption), + Content: img.Caption, ChunkIndex: maxSeq + i*100 + 2, // 使用不冲突的索引方式 IsEnabled: true, CreatedAt: time.Now(), @@ -860,8 +917,7 @@ func (s *knowledgeService) processChunks(ctx context.Context, } else { for _, chunk := range textChunks { chunk.RelationChunks, _ = json.Marshal(graphBuilder.GetRelationChunks(chunk.ID, relationChunkSize)) - chunk.IndirectRelationChunks, _ = - json.Marshal(graphBuilder.GetIndirectRelationChunks(chunk.ID, indirectRelationChunkSize)) + chunk.IndirectRelationChunks, _ = json.Marshal(graphBuilder.GetIndirectRelationChunks(chunk.ID, indirectRelationChunkSize)) } for i, entity := range graphBuilder.GetAllEntities() { relationChunks, _ := json.Marshal(entity.ChunkIDs) @@ -1386,6 +1442,12 @@ func (s *knowledgeService) UpdateImageInfo(ctx context.Context, knowledgeID stri // Iterate through each chunk and update its content based on the image information updateChunk := []*types.Chunk{chunk} + var addChunk []*types.Chunk + + // Track whether we've found OCR and caption child chunks for this image + hasOCRChunk := false + hasCaptionChunk := false + for i, child := range chunkChildren { // Skip chunks that are not image types var cImageInfo []*types.ImageInfo @@ -1403,19 +1465,69 @@ func (s *knowledgeService) UpdateImageInfo(ctx context.Context, knowledgeID stri continue } - // Update the chunk content based on the image type - if child.ChunkType == types.ChunkTypeImageCaption && image.Caption != cImageInfo[0].Caption { - child.Content = fmt.Sprintf("图片描述: %s", image.Caption) - child.ImageInfo = imageInfo - updateChunk = append(updateChunk, chunkChildren[i]) - } else if child.ChunkType == types.ChunkTypeImageOCR && image.OCRText != cImageInfo[0].OCRText { - child.Content = fmt.Sprintf("图片OCR文本: %s", image.OCRText) - child.ImageInfo = imageInfo - updateChunk = append(updateChunk, chunkChildren[i]) + // Mark that we've found chunks for this image + if child.ChunkType == types.ChunkTypeImageCaption { + hasCaptionChunk = true + // Update caption if it has changed + if image.Caption != cImageInfo[0].Caption { + child.Content = image.Caption + child.ImageInfo = imageInfo + updateChunk = append(updateChunk, chunkChildren[i]) + } + } else if child.ChunkType == types.ChunkTypeImageOCR { + hasOCRChunk = true + // Update OCR if it has changed + if image.OCRText != cImageInfo[0].OCRText { + child.Content = image.OCRText + child.ImageInfo = imageInfo + updateChunk = append(updateChunk, chunkChildren[i]) + } } } + + // Create a new caption chunk if it doesn't exist and we have caption data + if !hasCaptionChunk && image.Caption != "" { + captionChunk := &types.Chunk{ + ID: uuid.New().String(), + TenantID: tenantID, + KnowledgeID: chunk.KnowledgeID, + KnowledgeBaseID: chunk.KnowledgeBaseID, + Content: image.Caption, + ChunkType: types.ChunkTypeImageCaption, + ParentChunkID: chunk.ID, + ImageInfo: imageInfo, + } + addChunk = append(addChunk, captionChunk) + logger.Infof(ctx, "Created new caption chunk ID: %s for image URL: %s", captionChunk.ID, image.OriginalURL) + } + + // Create a new OCR chunk if it doesn't exist and we have OCR data + if !hasOCRChunk && image.OCRText != "" { + ocrChunk := &types.Chunk{ + ID: uuid.New().String(), + TenantID: tenantID, + KnowledgeID: chunk.KnowledgeID, + KnowledgeBaseID: chunk.KnowledgeBaseID, + Content: image.OCRText, + ChunkType: types.ChunkTypeImageOCR, + ParentChunkID: chunk.ID, + ImageInfo: imageInfo, + } + addChunk = append(addChunk, ocrChunk) + logger.Infof(ctx, "Created new OCR chunk ID: %s for image URL: %s", ocrChunk.ID, image.OriginalURL) + } logger.Infof(ctx, "Updated %d chunks out of %d total chunks", len(updateChunk), len(chunkChildren)+1) + if len(addChunk) > 0 { + err := s.chunkService.CreateChunks(ctx, addChunk) + if err != nil { + logger.ErrorWithFields(ctx, err, map[string]interface{}{ + "add_chunk_size": len(addChunk), + }) + return err + } + } + // Update the chunks for _, c := range updateChunk { err := s.chunkService.UpdateChunk(ctx, c) @@ -1429,7 +1541,7 @@ func (s *knowledgeService) UpdateImageInfo(ctx context.Context, knowledgeID stri } // Update the chunk vector - err = s.updateChunkVector(ctx, chunk.KnowledgeBaseID, updateChunk) + err = s.updateChunkVector(ctx, chunk.KnowledgeBaseID, append(updateChunk, addChunk...)) if err != nil { logger.ErrorWithFields(ctx, err, map[string]interface{}{ "chunk_id": chunk.ID, @@ -1559,3 +1671,12 @@ func (s *knowledgeService) CloneChunk(ctx context.Context, src, dst *types.Knowl } return nil } + +func IsImageType(fileType string) bool { + switch fileType { + case "jpg", "jpeg", "png", "gif", "webp", "bmp", "svg", "tiff": + return true + default: + return false + } +} diff --git a/internal/handler/knowledge.go b/internal/handler/knowledge.go index 533f30b..d9ddcdf 100644 --- a/internal/handler/knowledge.go +++ b/internal/handler/knowledge.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + "strconv" "github.com/Tencent/WeKnora/internal/errors" "github.com/Tencent/WeKnora/internal/logger" @@ -115,8 +116,20 @@ func (h *KnowledgeHandler) CreateKnowledgeFromFile(c *gin.Context) { logger.Infof(ctx, "Received file metadata: %v", metadata) } + enableMultimodelForm := c.PostForm("enable_multimodel") + var enableMultimodel *bool + if enableMultimodelForm != "" { + parseBool, err := strconv.ParseBool(enableMultimodelForm) + if err != nil { + logger.Error(ctx, "Failed to parse enable_multimodel", err) + c.Error(errors.NewBadRequestError("Invalid enable_multimodel format").WithDetails(err.Error())) + return + } + enableMultimodel = &parseBool + } + // Create knowledge entry from the file - knowledge, err := h.kgService.CreateKnowledgeFromFile(ctx, kbID, file, metadata) + knowledge, err := h.kgService.CreateKnowledgeFromFile(ctx, kbID, file, metadata, enableMultimodel) // Check for duplicate knowledge error if err != nil { if h.handleDuplicateKnowledgeError(c, err, knowledge, "file") { @@ -148,7 +161,8 @@ func (h *KnowledgeHandler) CreateKnowledgeFromURL(c *gin.Context) { // Parse URL from request body var req struct { - URL string `json:"url" binding:"required"` + URL string `json:"url" binding:"required"` + EnableMultimodel *bool `json:"enable_multimodel"` } if err := c.ShouldBindJSON(&req); err != nil { logger.Error(ctx, "Failed to parse URL request", err) @@ -160,7 +174,7 @@ func (h *KnowledgeHandler) CreateKnowledgeFromURL(c *gin.Context) { logger.Infof(ctx, "Creating knowledge from URL, knowledge base ID: %s, URL: %s", kbID, req.URL) // Create knowledge entry from the URL - knowledge, err := h.kgService.CreateKnowledgeFromURL(ctx, kbID, req.URL) + knowledge, err := h.kgService.CreateKnowledgeFromURL(ctx, kbID, req.URL, req.EnableMultimodel) // Check for duplicate knowledge error if err != nil { if h.handleDuplicateKnowledgeError(c, err, knowledge, "url") { diff --git a/internal/types/interfaces/knowledge.go b/internal/types/interfaces/knowledge.go index 48c49c4..621385b 100644 --- a/internal/types/interfaces/knowledge.go +++ b/internal/types/interfaces/knowledge.go @@ -16,9 +16,10 @@ type KnowledgeService interface { kbID string, file *multipart.FileHeader, metadata map[string]string, + enableMultimodel *bool, ) (*types.Knowledge, error) // CreateKnowledgeFromURL creates knowledge from a URL. - CreateKnowledgeFromURL(ctx context.Context, kbID string, url string) (*types.Knowledge, error) + CreateKnowledgeFromURL(ctx context.Context, kbID string, url string, enableMultimodel *bool) (*types.Knowledge, error) // CreateKnowledgeFromPassage creates knowledge from text passages. CreateKnowledgeFromPassage(ctx context.Context, kbID string, passage []string) (*types.Knowledge, error) // GetKnowledgeByID retrieves knowledge by ID.