feat: 新增多模态参数及处理逻辑,优化图片与删除操作

This commit is contained in:
begoniezhao
2025-08-08 17:19:06 +08:00
parent 143ca602ff
commit 104a65ad23
5 changed files with 237 additions and 92 deletions

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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") {

View File

@@ -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.