refactor: Clean up code by removing unnecessary blank lines and improving comments for better readability and maintainability
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
version: 2
|
||||
linters-settings:
|
||||
lll:
|
||||
line-length: 120
|
||||
tab-width: 4
|
||||
linters:
|
||||
enable:
|
||||
- lll # 控制行宽
|
||||
- govet
|
||||
- revive
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- gofumpt
|
||||
@@ -277,7 +277,6 @@ func (cli *CLI) askQuestion(query string) {
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("%s✗ Agent QA failed: %v%s\n", ColorRed, err, ColorReset)
|
||||
return
|
||||
|
||||
@@ -638,7 +638,6 @@ func (e *AgentEngine) streamReflectionToEventBus(
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Warnf(ctx, "Reflection failed: %v", err)
|
||||
return "", err
|
||||
@@ -710,7 +709,6 @@ func (e *AgentEngine) streamThinkingToEventBus(
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "[Agent][Thinking][Iteration-%d] Thinking stream failed: %v", iteration+1, err)
|
||||
return nil, err
|
||||
@@ -811,7 +809,6 @@ func (e *AgentEngine) streamFinalAnswerToEventBus(
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "[Agent][FinalAnswer] Final answer generation failed: %v", err)
|
||||
common.PipelineError(ctx, "Agent", "final_answer_stream_failed", map[string]interface{}{
|
||||
|
||||
@@ -2,7 +2,6 @@ package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
@@ -421,37 +420,3 @@ func (t *DatabaseQueryTool) formatQueryResults(columns []string, results []map[s
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// Helper function to convert database value to proper type
|
||||
func convertDatabaseValue(val interface{}) interface{} {
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := val.(type) {
|
||||
case []byte:
|
||||
return string(v)
|
||||
case sql.NullString:
|
||||
if v.Valid {
|
||||
return v.String
|
||||
}
|
||||
return nil
|
||||
case sql.NullInt64:
|
||||
if v.Valid {
|
||||
return v.Int64
|
||||
}
|
||||
return nil
|
||||
case sql.NullFloat64:
|
||||
if v.Valid {
|
||||
return v.Float64
|
||||
}
|
||||
return nil
|
||||
case sql.NullBool:
|
||||
if v.Valid {
|
||||
return v.Bool
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,18 +459,19 @@ func (t *KnowledgeSearchTool) filterByThreshold(
|
||||
var threshold float64
|
||||
|
||||
// Special handling for history matches: use lower threshold
|
||||
if r.MatchType == types.MatchTypeHistory {
|
||||
switch r.MatchType {
|
||||
case types.MatchTypeHistory:
|
||||
// Use the lower of the two thresholds, then reduce by 0.1 (minimum 0.5)
|
||||
th := vectorThreshold
|
||||
if keywordThreshold < th {
|
||||
th = keywordThreshold
|
||||
}
|
||||
threshold = math.Max(th-0.1, 0.5)
|
||||
} else if r.MatchType == types.MatchTypeEmbedding {
|
||||
case types.MatchTypeEmbedding:
|
||||
threshold = vectorThreshold
|
||||
} else if r.MatchType == types.MatchTypeKeywords {
|
||||
case types.MatchTypeKeywords:
|
||||
threshold = keywordThreshold
|
||||
} else {
|
||||
default:
|
||||
// For other match types (graph, nearby chunk, etc.), use the lower threshold
|
||||
threshold = vectorThreshold
|
||||
if keywordThreshold < threshold {
|
||||
@@ -1208,7 +1209,7 @@ func (t *KnowledgeSearchTool) findMissingChunkRanges(retrievedChunks map[int]boo
|
||||
}
|
||||
|
||||
var ranges []chunkRange
|
||||
var currentStart int = -1
|
||||
currentStart := -1
|
||||
|
||||
// Iterate through all possible chunk indices (0 to totalChunks-1)
|
||||
for i := 0; i < totalChunks; i++ {
|
||||
|
||||
@@ -190,11 +190,11 @@ func (t *ListKnowledgeChunksTool) buildOutput(
|
||||
builder.WriteString("=== 知识文档分块 ===\n\n")
|
||||
|
||||
if knowledgeTitle != "" {
|
||||
builder.WriteString(fmt.Sprintf("文档: %s (%s)\n", knowledgeTitle, knowledgeID))
|
||||
fmt.Fprintf(builder, "文档: %s (%s)\n", knowledgeTitle, knowledgeID)
|
||||
} else {
|
||||
builder.WriteString(fmt.Sprintf("文档 ID: %s\n", knowledgeID))
|
||||
fmt.Fprintf(builder, "文档 ID: %s\n", knowledgeID)
|
||||
}
|
||||
builder.WriteString(fmt.Sprintf("总分块数: %d\n", total))
|
||||
fmt.Fprintf(builder, "总分块数: %d\n", total)
|
||||
|
||||
if fetched == 0 {
|
||||
builder.WriteString("未找到任何分块,请确认文档是否已完成解析。\n")
|
||||
@@ -203,14 +203,14 @@ func (t *ListKnowledgeChunksTool) buildOutput(
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
builder.WriteString(fmt.Sprintf("本次拉取: %d 条, 检索范围: %d - %d\n\n", fetched, chunks[0].ChunkIndex, chunks[len(chunks)-1].ChunkIndex))
|
||||
fmt.Fprintf(builder, "本次拉取: %d 条, 检索范围: %d - %d\n\n", fetched, chunks[0].ChunkIndex, chunks[len(chunks)-1].ChunkIndex)
|
||||
|
||||
builder.WriteString("=== 分块内容预览 ===\n\n")
|
||||
for idx, c := range chunks {
|
||||
builder.WriteString(fmt.Sprintf("Chunk #%d (Index %d)\n", idx+1, c.ChunkIndex+1))
|
||||
builder.WriteString(fmt.Sprintf(" chunk_id: %s\n", c.ID))
|
||||
builder.WriteString(fmt.Sprintf(" 类型: %s\n", c.ChunkType))
|
||||
builder.WriteString(fmt.Sprintf(" 内容: %s\n\n", summarizeContent(c.Content)))
|
||||
fmt.Fprintf(builder, "Chunk #%d (Index %d)\n", idx+1, c.ChunkIndex+1)
|
||||
fmt.Fprintf(builder, " chunk_id: %s\n", c.ID)
|
||||
fmt.Fprintf(builder, " 类型: %s\n", c.ChunkType)
|
||||
fmt.Fprintf(builder, " 内容: %s\n\n", summarizeContent(c.Content))
|
||||
}
|
||||
|
||||
if int64(fetched) < total {
|
||||
|
||||
@@ -502,7 +502,7 @@ func (t *WebFetchTool) processNode(s *goquery.Selection, markdown *strings.Build
|
||||
isOrdered := nodeName == "ol"
|
||||
node.Find("li").Each(func(idx int, li *goquery.Selection) {
|
||||
if isOrdered {
|
||||
markdown.WriteString(fmt.Sprintf("%d. ", idx+1))
|
||||
fmt.Fprintf(markdown, "%d. ", idx+1)
|
||||
} else {
|
||||
markdown.WriteString("- ")
|
||||
}
|
||||
|
||||
@@ -138,7 +138,8 @@ func (r *knowledgeRepository) CheckKnowledgeExists(
|
||||
query := r.db.WithContext(ctx).Model(&types.Knowledge{}).
|
||||
Where("tenant_id = ? AND knowledge_base_id = ? AND parse_status <> ?", tenantID, kbID, "failed")
|
||||
|
||||
if params.Type == "file" {
|
||||
switch params.Type {
|
||||
case "file":
|
||||
// If file hash exists, prioritize exact match using hash
|
||||
if params.FileHash != "" {
|
||||
var knowledge types.Knowledge
|
||||
@@ -167,7 +168,7 @@ func (r *knowledgeRepository) CheckKnowledgeExists(
|
||||
}
|
||||
return true, &knowledge, nil
|
||||
}
|
||||
} else if params.Type == "url" {
|
||||
case "url":
|
||||
// If file hash exists, prioritize exact match using hash
|
||||
if params.FileHash != "" {
|
||||
var knowledge types.Knowledge
|
||||
|
||||
@@ -30,7 +30,6 @@ func (r *mcpServiceRepository) GetByID(ctx context.Context, tenantID uint64, id
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("id = ? AND tenant_id = ?", id, tenantID).
|
||||
First(&service).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
@@ -48,7 +47,6 @@ func (r *mcpServiceRepository) List(ctx context.Context, tenantID uint64) ([]*ty
|
||||
Where("tenant_id = ?", tenantID).
|
||||
Order("created_at DESC").
|
||||
Find(&services).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,7 +61,6 @@ func (r *mcpServiceRepository) ListEnabled(ctx context.Context, tenantID uint64)
|
||||
Where("tenant_id = ? AND enabled = ?", tenantID, true).
|
||||
Order("created_at DESC").
|
||||
Find(&services).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -81,7 +78,6 @@ func (r *mcpServiceRepository) ListByIDs(ctx context.Context, tenantID uint64, i
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("tenant_id = ? AND id IN ?", tenantID, ids).
|
||||
Find(&services).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -221,11 +221,3 @@ func listI2listS(list []any) []string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func mapI2mapS(prop map[string]any) map[string]string {
|
||||
attributes := make(map[string]string)
|
||||
for k, v := range prop {
|
||||
attributes[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
|
||||
@@ -304,12 +304,12 @@ func (f *Formater) formatExtraction(nodes []*types.GraphNode, relations []*types
|
||||
|
||||
func (f *Formater) parseOutput(ctx context.Context, text string) ([]map[string]interface{}, error) {
|
||||
if text == "" {
|
||||
return nil, errors.New("Empty or invalid input string.")
|
||||
return nil, errors.New("empty or invalid input string")
|
||||
}
|
||||
content := f.extractContent(ctx, text)
|
||||
// logger.Debugf(ctx, "Extracted content: %s", content)
|
||||
if content == "" {
|
||||
return nil, errors.New("Empty or invalid input string.")
|
||||
return nil, errors.New("empty or invalid input string")
|
||||
}
|
||||
|
||||
var parsed interface{}
|
||||
@@ -318,10 +318,10 @@ func (f *Formater) parseOutput(ctx context.Context, text string) ([]map[string]i
|
||||
err = json.Unmarshal([]byte(content), &parsed)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse %s content: %s", strings.ToUpper(string(f.formatType)), err.Error())
|
||||
return nil, fmt.Errorf("failed to parse %s content: %s", strings.ToUpper(string(f.formatType)), err.Error())
|
||||
}
|
||||
if parsed == nil {
|
||||
return nil, fmt.Errorf("Content must be a list of extractions or a dict.")
|
||||
return nil, fmt.Errorf("content must be a list of extractions or a dict")
|
||||
}
|
||||
|
||||
var items []interface{}
|
||||
@@ -330,7 +330,7 @@ func (f *Formater) parseOutput(ctx context.Context, text string) ([]map[string]i
|
||||
} else if parsedList, ok := parsed.([]interface{}); ok {
|
||||
items = parsedList
|
||||
} else {
|
||||
return nil, fmt.Errorf("Expected list or dict, got %T", parsed)
|
||||
return nil, fmt.Errorf("expected list or dict, got %T", parsed)
|
||||
}
|
||||
|
||||
itemsList := make([]map[string]interface{}, 0)
|
||||
@@ -338,7 +338,7 @@ func (f *Formater) parseOutput(ctx context.Context, text string) ([]map[string]i
|
||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||
itemsList = append(itemsList, itemMap)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Each item in the sequence must be a mapping.")
|
||||
return nil, fmt.Errorf("each item in the sequence must be a mapping.")
|
||||
}
|
||||
}
|
||||
return itemsList, nil
|
||||
@@ -350,7 +350,7 @@ func (f *Formater) ParseGraph(ctx context.Context, text string) (*types.GraphDat
|
||||
return nil, err
|
||||
}
|
||||
if len(matchData) == 0 {
|
||||
logger.Debugf(ctx, "Received empty extraction data.")
|
||||
logger.Debugf(ctx, "received empty extraction data.")
|
||||
return &types.GraphData{}, nil
|
||||
}
|
||||
// mm, _ := json.Marshal(matchData)
|
||||
@@ -403,9 +403,7 @@ func (f *Formater) rebuildGraph(ctx context.Context, graph *types.GraphData) {
|
||||
node.Attributes = make([]string, 0)
|
||||
}
|
||||
if prenode.Attributes != nil {
|
||||
for _, attr := range prenode.Attributes {
|
||||
node.Attributes = append(node.Attributes, attr)
|
||||
}
|
||||
node.Attributes = append(node.Attributes, prenode.Attributes...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -438,7 +438,6 @@ func (p *PluginMerge) expandShortContextWithNeighbors(ctx context.Context, chatM
|
||||
"neighbor_len": runeLen(chunk.Content),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package chatpipline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/models/rerank"
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
||||
"github.com/Tencent/WeKnora/internal/models/rerank"
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
||||
)
|
||||
|
||||
// PluginRerank implements reranking functionality for chat pipeline
|
||||
@@ -33,7 +33,7 @@ func (p *PluginRerank) ActivationEvents() []types.EventType {
|
||||
|
||||
// OnEvent handles reranking events in the chat pipeline
|
||||
func (p *PluginRerank) OnEvent(ctx context.Context,
|
||||
eventType types.EventType, chatManage *types.ChatManage, next func() *PluginError,
|
||||
eventType types.EventType, chatManage *types.ChatManage, next func() *PluginError,
|
||||
) *PluginError {
|
||||
pipelineInfo(ctx, "Rerank", "input", map[string]interface{}{
|
||||
"session_id": chatManage.SessionID,
|
||||
@@ -86,52 +86,52 @@ func (p *PluginRerank) OnEvent(ctx context.Context,
|
||||
}
|
||||
}
|
||||
|
||||
pipelineInfo(ctx, "Rerank", "model_response", map[string]interface{}{
|
||||
"result_cnt": len(rerankResp),
|
||||
})
|
||||
for i := range chatManage.SearchResult {
|
||||
chatManage.SearchResult[i].Metadata = ensureMetadata(chatManage.SearchResult[i].Metadata)
|
||||
}
|
||||
reranked := make([]*types.SearchResult, 0, len(rerankResp))
|
||||
for _, rr := range rerankResp {
|
||||
sr := chatManage.SearchResult[rr.Index]
|
||||
base := sr.Score
|
||||
sr.Metadata["base_score"] = fmt.Sprintf("%.4f", base)
|
||||
sr.Score = rr.RelevanceScore
|
||||
sr.Score = compositeScore(sr, rr.RelevanceScore, base, chatManage)
|
||||
reranked = append(reranked, sr)
|
||||
}
|
||||
final := applyMMR(ctx, reranked, chatManage, min(len(reranked), max(1, chatManage.RerankTopK)), 0.7)
|
||||
chatManage.RerankResult = final
|
||||
pipelineInfo(ctx, "Rerank", "model_response", map[string]interface{}{
|
||||
"result_cnt": len(rerankResp),
|
||||
})
|
||||
for i := range chatManage.SearchResult {
|
||||
chatManage.SearchResult[i].Metadata = ensureMetadata(chatManage.SearchResult[i].Metadata)
|
||||
}
|
||||
reranked := make([]*types.SearchResult, 0, len(rerankResp))
|
||||
for _, rr := range rerankResp {
|
||||
sr := chatManage.SearchResult[rr.Index]
|
||||
base := sr.Score
|
||||
sr.Metadata["base_score"] = fmt.Sprintf("%.4f", base)
|
||||
sr.Score = rr.RelevanceScore
|
||||
sr.Score = compositeScore(sr, rr.RelevanceScore, base, chatManage)
|
||||
reranked = append(reranked, sr)
|
||||
}
|
||||
final := applyMMR(ctx, reranked, chatManage, min(len(reranked), max(1, chatManage.RerankTopK)), 0.7)
|
||||
chatManage.RerankResult = final
|
||||
|
||||
// Log composite top scores and MMR selection summary
|
||||
topN := min(3, len(reranked))
|
||||
for i := 0; i < topN; i++ {
|
||||
pipelineInfo(ctx, "Rerank", "composite_top", map[string]interface{}{
|
||||
"rank": i + 1,
|
||||
"chunk_id": reranked[i].ID,
|
||||
"base_score": reranked[i].Metadata["base_score"],
|
||||
"final_score": fmt.Sprintf("%.4f", reranked[i].Score),
|
||||
"intent": chatManage.QueryIntent,
|
||||
})
|
||||
}
|
||||
// Log composite top scores and MMR selection summary
|
||||
topN := min(3, len(reranked))
|
||||
for i := 0; i < topN; i++ {
|
||||
pipelineInfo(ctx, "Rerank", "composite_top", map[string]interface{}{
|
||||
"rank": i + 1,
|
||||
"chunk_id": reranked[i].ID,
|
||||
"base_score": reranked[i].Metadata["base_score"],
|
||||
"final_score": fmt.Sprintf("%.4f", reranked[i].Score),
|
||||
"intent": chatManage.QueryIntent,
|
||||
})
|
||||
}
|
||||
|
||||
if len(chatManage.RerankResult) == 0 {
|
||||
pipelineWarn(ctx, "Rerank", "output", map[string]interface{}{
|
||||
"filtered_cnt": 0,
|
||||
})
|
||||
return ErrSearchNothing
|
||||
}
|
||||
if len(chatManage.RerankResult) == 0 {
|
||||
pipelineWarn(ctx, "Rerank", "output", map[string]interface{}{
|
||||
"filtered_cnt": 0,
|
||||
})
|
||||
return ErrSearchNothing
|
||||
}
|
||||
|
||||
pipelineInfo(ctx, "Rerank", "output", map[string]interface{}{
|
||||
"filtered_cnt": len(chatManage.RerankResult),
|
||||
})
|
||||
return next()
|
||||
return next()
|
||||
}
|
||||
|
||||
// rerank performs the actual reranking operation with given query and passages
|
||||
func (p *PluginRerank) rerank(ctx context.Context,
|
||||
chatManage *types.ChatManage, rerankModel rerank.Reranker, query string, passages []string,
|
||||
chatManage *types.ChatManage, rerankModel rerank.Reranker, query string, passages []string,
|
||||
) []rerank.RankResult {
|
||||
pipelineInfo(ctx, "Rerank", "model_call", map[string]interface{}{
|
||||
"query_variant": query,
|
||||
@@ -173,149 +173,149 @@ func (p *PluginRerank) rerank(ctx context.Context,
|
||||
rankFilter = append(rankFilter, result)
|
||||
}
|
||||
}
|
||||
return rankFilter
|
||||
return rankFilter
|
||||
}
|
||||
|
||||
func ensureMetadata(m map[string]string) map[string]string {
|
||||
if m == nil {
|
||||
return make(map[string]string)
|
||||
}
|
||||
return m
|
||||
if m == nil {
|
||||
return make(map[string]string)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func compositeScore(sr *types.SearchResult, modelScore, baseScore float64, chatManage *types.ChatManage) float64 {
|
||||
sourceWeight := 1.0
|
||||
switch strings.ToLower(sr.KnowledgeSource) {
|
||||
case "web_search":
|
||||
sourceWeight = 0.95
|
||||
default:
|
||||
sourceWeight = 1.0
|
||||
}
|
||||
intentBoost := 1.0
|
||||
if chatManage.QueryIntent != "" {
|
||||
switch chatManage.QueryIntent {
|
||||
case "definition":
|
||||
if sr.ChunkType == string(types.ChunkTypeSummary) {
|
||||
intentBoost = 1.05
|
||||
}
|
||||
case "howto":
|
||||
if sr.EndAt-sr.StartAt > 300 {
|
||||
intentBoost = 1.03
|
||||
}
|
||||
case "compare":
|
||||
intentBoost = 1.0
|
||||
}
|
||||
}
|
||||
positionPrior := 1.0
|
||||
if sr.StartAt >= 0 {
|
||||
positionPrior += clampFloat(1.0-float64(sr.StartAt)/float64(sr.EndAt+1), -0.05, 0.05)
|
||||
}
|
||||
composite := 0.6*modelScore + 0.3*baseScore + 0.1*sourceWeight
|
||||
composite *= intentBoost
|
||||
composite *= positionPrior
|
||||
if composite < 0 {
|
||||
composite = 0
|
||||
}
|
||||
if composite > 1 {
|
||||
composite = 1
|
||||
}
|
||||
return composite
|
||||
sourceWeight := 1.0
|
||||
switch strings.ToLower(sr.KnowledgeSource) {
|
||||
case "web_search":
|
||||
sourceWeight = 0.95
|
||||
default:
|
||||
sourceWeight = 1.0
|
||||
}
|
||||
intentBoost := 1.0
|
||||
if chatManage.QueryIntent != "" {
|
||||
switch chatManage.QueryIntent {
|
||||
case "definition":
|
||||
if sr.ChunkType == string(types.ChunkTypeSummary) {
|
||||
intentBoost = 1.05
|
||||
}
|
||||
case "howto":
|
||||
if sr.EndAt-sr.StartAt > 300 {
|
||||
intentBoost = 1.03
|
||||
}
|
||||
case "compare":
|
||||
intentBoost = 1.0
|
||||
}
|
||||
}
|
||||
positionPrior := 1.0
|
||||
if sr.StartAt >= 0 {
|
||||
positionPrior += clampFloat(1.0-float64(sr.StartAt)/float64(sr.EndAt+1), -0.05, 0.05)
|
||||
}
|
||||
composite := 0.6*modelScore + 0.3*baseScore + 0.1*sourceWeight
|
||||
composite *= intentBoost
|
||||
composite *= positionPrior
|
||||
if composite < 0 {
|
||||
composite = 0
|
||||
}
|
||||
if composite > 1 {
|
||||
composite = 1
|
||||
}
|
||||
return composite
|
||||
}
|
||||
|
||||
func applyMMR(ctx context.Context, results []*types.SearchResult, chatManage *types.ChatManage, k int, lambda float64) []*types.SearchResult {
|
||||
if k <= 0 || len(results) == 0 {
|
||||
return nil
|
||||
}
|
||||
pipelineInfo(ctx, "Rerank", "mmr_start", map[string]interface{}{
|
||||
"lambda": lambda,
|
||||
"k": k,
|
||||
"candidates": len(results),
|
||||
})
|
||||
selected := make([]*types.SearchResult, 0, k)
|
||||
candidates := make([]*types.SearchResult, len(results))
|
||||
copy(candidates, results)
|
||||
tokenSets := make([]map[string]struct{}, len(candidates))
|
||||
for i, r := range candidates {
|
||||
tokenSets[i] = tokenizeSimple(getEnrichedPassage(ctx, r))
|
||||
}
|
||||
for len(selected) < k && len(candidates) > 0 {
|
||||
bestIdx := 0
|
||||
bestScore := -1.0
|
||||
for i, r := range candidates {
|
||||
relevance := r.Score
|
||||
redundancy := 0.0
|
||||
for _, s := range selected {
|
||||
redundancy = math.Max(redundancy, jaccard(tokenSets[i], tokenizeSimple(getEnrichedPassage(ctx, s))))
|
||||
}
|
||||
mmr := lambda*relevance - (1.0-lambda)*redundancy
|
||||
if mmr > bestScore {
|
||||
bestScore = mmr
|
||||
bestIdx = i
|
||||
}
|
||||
}
|
||||
selected = append(selected, candidates[bestIdx])
|
||||
candidates = append(candidates[:bestIdx], candidates[bestIdx+1:]...)
|
||||
}
|
||||
// Compute average redundancy among selected
|
||||
avgRed := 0.0
|
||||
if len(selected) > 1 {
|
||||
pairs := 0
|
||||
for i := 0; i < len(selected); i++ {
|
||||
for j := i + 1; j < len(selected); j++ {
|
||||
si := tokenizeSimple(getEnrichedPassage(ctx, selected[i]))
|
||||
sj := tokenizeSimple(getEnrichedPassage(ctx, selected[j]))
|
||||
avgRed += jaccard(si, sj)
|
||||
pairs++
|
||||
}
|
||||
}
|
||||
if pairs > 0 {
|
||||
avgRed /= float64(pairs)
|
||||
}
|
||||
}
|
||||
pipelineInfo(ctx, "Rerank", "mmr_done", map[string]interface{}{
|
||||
"selected": len(selected),
|
||||
"avg_redundancy": fmt.Sprintf("%.4f", avgRed),
|
||||
})
|
||||
return selected
|
||||
if k <= 0 || len(results) == 0 {
|
||||
return nil
|
||||
}
|
||||
pipelineInfo(ctx, "Rerank", "mmr_start", map[string]interface{}{
|
||||
"lambda": lambda,
|
||||
"k": k,
|
||||
"candidates": len(results),
|
||||
})
|
||||
selected := make([]*types.SearchResult, 0, k)
|
||||
candidates := make([]*types.SearchResult, len(results))
|
||||
copy(candidates, results)
|
||||
tokenSets := make([]map[string]struct{}, len(candidates))
|
||||
for i, r := range candidates {
|
||||
tokenSets[i] = tokenizeSimple(getEnrichedPassage(ctx, r))
|
||||
}
|
||||
for len(selected) < k && len(candidates) > 0 {
|
||||
bestIdx := 0
|
||||
bestScore := -1.0
|
||||
for i, r := range candidates {
|
||||
relevance := r.Score
|
||||
redundancy := 0.0
|
||||
for _, s := range selected {
|
||||
redundancy = math.Max(redundancy, jaccard(tokenSets[i], tokenizeSimple(getEnrichedPassage(ctx, s))))
|
||||
}
|
||||
mmr := lambda*relevance - (1.0-lambda)*redundancy
|
||||
if mmr > bestScore {
|
||||
bestScore = mmr
|
||||
bestIdx = i
|
||||
}
|
||||
}
|
||||
selected = append(selected, candidates[bestIdx])
|
||||
candidates = append(candidates[:bestIdx], candidates[bestIdx+1:]...)
|
||||
}
|
||||
// Compute average redundancy among selected
|
||||
avgRed := 0.0
|
||||
if len(selected) > 1 {
|
||||
pairs := 0
|
||||
for i := 0; i < len(selected); i++ {
|
||||
for j := i + 1; j < len(selected); j++ {
|
||||
si := tokenizeSimple(getEnrichedPassage(ctx, selected[i]))
|
||||
sj := tokenizeSimple(getEnrichedPassage(ctx, selected[j]))
|
||||
avgRed += jaccard(si, sj)
|
||||
pairs++
|
||||
}
|
||||
}
|
||||
if pairs > 0 {
|
||||
avgRed /= float64(pairs)
|
||||
}
|
||||
}
|
||||
pipelineInfo(ctx, "Rerank", "mmr_done", map[string]interface{}{
|
||||
"selected": len(selected),
|
||||
"avg_redundancy": fmt.Sprintf("%.4f", avgRed),
|
||||
})
|
||||
return selected
|
||||
}
|
||||
|
||||
func tokenizeSimple(text string) map[string]struct{} {
|
||||
text = strings.ToLower(text)
|
||||
fields := strings.Fields(text)
|
||||
set := make(map[string]struct{}, len(fields))
|
||||
for _, f := range fields {
|
||||
if len(f) > 1 {
|
||||
set[f] = struct{}{}
|
||||
}
|
||||
}
|
||||
return set
|
||||
text = strings.ToLower(text)
|
||||
fields := strings.Fields(text)
|
||||
set := make(map[string]struct{}, len(fields))
|
||||
for _, f := range fields {
|
||||
if len(f) > 1 {
|
||||
set[f] = struct{}{}
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
func jaccard(a, b map[string]struct{}) float64 {
|
||||
if len(a) == 0 && len(b) == 0 {
|
||||
return 0
|
||||
}
|
||||
inter := 0
|
||||
for k := range a {
|
||||
if _, ok := b[k]; ok {
|
||||
inter++
|
||||
}
|
||||
}
|
||||
union := len(a) + len(b) - inter
|
||||
if union == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(inter) / float64(union)
|
||||
if len(a) == 0 && len(b) == 0 {
|
||||
return 0
|
||||
}
|
||||
inter := 0
|
||||
for k := range a {
|
||||
if _, ok := b[k]; ok {
|
||||
inter++
|
||||
}
|
||||
}
|
||||
union := len(a) + len(b) - inter
|
||||
if union == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(inter) / float64(union)
|
||||
}
|
||||
|
||||
func clampFloat(v, minV, maxV float64) float64 {
|
||||
if v < minV {
|
||||
return minV
|
||||
}
|
||||
if v > maxV {
|
||||
return maxV
|
||||
}
|
||||
return v
|
||||
if v < minV {
|
||||
return minV
|
||||
}
|
||||
if v > maxV {
|
||||
return maxV
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// getEnrichedPassage 合并Content和ImageInfo的文本内容
|
||||
|
||||
@@ -387,7 +387,7 @@ func (p *PluginSearch) searchKnowledgeBases(ctx context.Context, knowledgeBaseID
|
||||
|
||||
// searchWebIfEnabled executes web search when enabled and returns converted results
|
||||
func (p *PluginSearch) searchWebIfEnabled(ctx context.Context, chatManage *types.ChatManage) []*types.SearchResult {
|
||||
if !(chatManage.WebSearchEnabled && p.webSearchService != nil && p.tenantService != nil && chatManage.TenantID > 0) {
|
||||
if !chatManage.WebSearchEnabled || p.webSearchService == nil || p.tenantService == nil || chatManage.TenantID <= 0 {
|
||||
return nil
|
||||
}
|
||||
tenant := ctx.Value(types.TenantInfoContextKey).(*types.Tenant)
|
||||
|
||||
@@ -77,7 +77,7 @@ func (p *PluginTracing) OnEvent(ctx context.Context,
|
||||
|
||||
// Search traces search operations in the chat pipeline
|
||||
func (p *PluginTracing) Search(ctx context.Context,
|
||||
eventType types.EventType, chatManage *types.ChatManage, next func() *PluginError,
|
||||
eventType types.EventType, chatManage *types.ChatManage, next func() *PluginError,
|
||||
) *PluginError {
|
||||
_, span := tracing.ContextWithSpan(ctx, "PluginTracing.Search")
|
||||
defer span.End()
|
||||
@@ -87,23 +87,23 @@ func (p *PluginTracing) Search(ctx context.Context,
|
||||
attribute.Float64("keyword_threshold", chatManage.KeywordThreshold),
|
||||
attribute.Int("match_count", chatManage.EmbeddingTopK),
|
||||
)
|
||||
err := next()
|
||||
searchResultJson, _ := json.Marshal(chatManage.SearchResult)
|
||||
unique := make(map[string]struct{})
|
||||
for _, r := range chatManage.SearchResult {
|
||||
unique[r.ID] = struct{}{}
|
||||
}
|
||||
span.SetAttributes(
|
||||
attribute.String("hybrid_search", string(searchResultJson)),
|
||||
attribute.String("processed_query", chatManage.ProcessedQuery),
|
||||
attribute.Int("search_unique_count", len(unique)),
|
||||
)
|
||||
return err
|
||||
err := next()
|
||||
searchResultJson, _ := json.Marshal(chatManage.SearchResult)
|
||||
unique := make(map[string]struct{})
|
||||
for _, r := range chatManage.SearchResult {
|
||||
unique[r.ID] = struct{}{}
|
||||
}
|
||||
span.SetAttributes(
|
||||
attribute.String("hybrid_search", string(searchResultJson)),
|
||||
attribute.String("processed_query", chatManage.ProcessedQuery),
|
||||
attribute.Int("search_unique_count", len(unique)),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Rerank traces rerank operations in the chat pipeline
|
||||
func (p *PluginTracing) Rerank(ctx context.Context,
|
||||
eventType types.EventType, chatManage *types.ChatManage, next func() *PluginError,
|
||||
eventType types.EventType, chatManage *types.ChatManage, next func() *PluginError,
|
||||
) *PluginError {
|
||||
_, span := tracing.ContextWithSpan(ctx, "PluginTracing.Rerank")
|
||||
defer span.End()
|
||||
@@ -114,14 +114,14 @@ func (p *PluginTracing) Rerank(ctx context.Context,
|
||||
attribute.Float64("rerank_filter_threshold", chatManage.RerankThreshold),
|
||||
attribute.Int("rerank_filter_topk", chatManage.RerankTopK),
|
||||
)
|
||||
err := next()
|
||||
resultJson, _ := json.Marshal(chatManage.RerankResult)
|
||||
span.SetAttributes(
|
||||
attribute.Int("rerank_resp_count", len(chatManage.RerankResult)),
|
||||
attribute.String("rerank_resp_results", string(resultJson)),
|
||||
attribute.String("query_intent", chatManage.QueryIntent),
|
||||
)
|
||||
return err
|
||||
err := next()
|
||||
resultJson, _ := json.Marshal(chatManage.RerankResult)
|
||||
span.SetAttributes(
|
||||
attribute.Int("rerank_resp_count", len(chatManage.RerankResult)),
|
||||
attribute.String("rerank_resp_results", string(resultJson)),
|
||||
attribute.String("query_intent", chatManage.QueryIntent),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Merge traces merge operations in the chat pipeline
|
||||
|
||||
@@ -58,7 +58,6 @@ func (s *chunkService) GetRepository() interfaces.ChunkRepository {
|
||||
// Returns:
|
||||
// - error: Any error encountered during chunk creation
|
||||
func (s *chunkService) CreateChunks(ctx context.Context, chunks []*types.Chunk) error {
|
||||
|
||||
err := s.chunkRepository.CreateChunks(ctx, chunks)
|
||||
if err != nil {
|
||||
logger.ErrorWithFields(ctx, err, map[string]interface{}{
|
||||
|
||||
@@ -21,7 +21,8 @@ type minioFileService struct {
|
||||
|
||||
// NewMinioFileService creates a MinIO file service
|
||||
func NewMinioFileService(endpoint,
|
||||
accessKeyID, secretAccessKey, bucketName string, useSSL bool) (interfaces.FileService, error) {
|
||||
accessKeyID, secretAccessKey, bucketName string, useSSL bool,
|
||||
) (interfaces.FileService, error) {
|
||||
// Initialize MinIO client
|
||||
client, err := minio.New(endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
|
||||
|
||||
@@ -183,7 +183,8 @@ func (b *graphBuilder) extractEntities(ctx context.Context, chunk *types.Chunk)
|
||||
// extractRelationships extracts relationships between entities
|
||||
// It analyzes semantic connections between multiple entities and establishes relationships
|
||||
func (b *graphBuilder) extractRelationships(ctx context.Context,
|
||||
chunks []*types.Chunk, entities []*types.Entity) error {
|
||||
chunks []*types.Chunk, entities []*types.Entity,
|
||||
) error {
|
||||
log := logger.GetLogger(ctx)
|
||||
log.Infof("Extracting relationships from %d entities across %d chunks", len(entities), len(chunks))
|
||||
|
||||
@@ -340,7 +341,7 @@ func (b *graphBuilder) mergeChunkContents(chunks []*types.Chunk) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
var chunkContents = chunks[0].Content
|
||||
chunkContents := chunks[0].Content
|
||||
preChunk := chunks[0]
|
||||
|
||||
for i := 1; i < len(chunks); i++ {
|
||||
@@ -369,7 +370,7 @@ func (b *graphBuilder) BuildGraph(ctx context.Context, chunks []*types.Chunk) er
|
||||
startTime := time.Now()
|
||||
|
||||
// Concurrently extract entities from each document chunk
|
||||
var chunkEntities = make([][]*types.Entity, len(chunks))
|
||||
chunkEntities := make([][]*types.Entity, len(chunks))
|
||||
g, gctx := errgroup.WithContext(ctx)
|
||||
g.SetLimit(MaxConcurrentEntityExtractions) // Limit concurrency
|
||||
|
||||
@@ -868,7 +869,8 @@ func (b *graphBuilder) getEntityByTitle(title string) *types.Entity {
|
||||
// dfs depth-first search to find connected components
|
||||
func dfs(entityTitle string,
|
||||
adjacencyList map[string]map[string]*types.Relationship,
|
||||
visited map[string]bool, component *[]string) {
|
||||
visited map[string]bool, component *[]string,
|
||||
) {
|
||||
visited[entityTitle] = true
|
||||
*component = append(*component, entityTitle)
|
||||
|
||||
|
||||
@@ -864,207 +864,6 @@ func (s *knowledgeService) cloneKnowledge(ctx context.Context, src *types.Knowle
|
||||
return
|
||||
}
|
||||
|
||||
// processDocument handles asynchronous processing of document files
|
||||
func (s *knowledgeService) processDocument(ctx context.Context,
|
||||
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(
|
||||
attribute.String("request_id", ctx.Value(types.RequestIDContextKey).(string)),
|
||||
attribute.String("knowledge_base_id", kb.ID),
|
||||
attribute.Int("tenant_id", int(kb.TenantID)),
|
||||
attribute.String("knowledge_id", knowledge.ID),
|
||||
attribute.String("file_name", knowledge.FileName),
|
||||
attribute.String("file_type", knowledge.FileType),
|
||||
attribute.String("file_path", knowledge.FilePath),
|
||||
attribute.Int64("file_size", knowledge.FileSize),
|
||||
attribute.String("embedding_model", knowledge.EmbeddingModelID),
|
||||
attribute.Bool("enable_multimodal", enableMultimodel),
|
||||
)
|
||||
logger.GetLogger(ctx).Infof("processDocument trace id: %s", span.SpanContext().TraceID().String())
|
||||
|
||||
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"
|
||||
knowledge.UpdatedAt = time.Now()
|
||||
if err := s.repo.UpdateKnowledge(ctx, knowledge); err != nil {
|
||||
span.RecordError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Read and chunk the document
|
||||
f, err := file.Open()
|
||||
if err != nil {
|
||||
logger.GetLogger(ctx).WithField("knowledge_id", knowledge.ID).
|
||||
WithField("error", err).Errorf("processDocument open file failed")
|
||||
knowledge.ParseStatus = "failed"
|
||||
knowledge.ErrorMessage = err.Error()
|
||||
knowledge.UpdatedAt = time.Now()
|
||||
s.repo.UpdateKnowledge(ctx, knowledge)
|
||||
span.RecordError(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
span.AddEvent("start read file")
|
||||
contentBytes, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
knowledge.ParseStatus = "failed"
|
||||
knowledge.ErrorMessage = err.Error()
|
||||
knowledge.UpdatedAt = time.Now()
|
||||
s.repo.UpdateKnowledge(ctx, knowledge)
|
||||
span.RecordError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Split file into chunks using document reader service
|
||||
logger.GetLogger(ctx).Infof("processDocument split file content size: %d, file name: %s, file type: %s, separators: %v, enable multimodal: %v",
|
||||
len(contentBytes), knowledge.FileName, knowledge.FileType, kb.ChunkingConfig.Separators, enableMultimodel)
|
||||
var vlmConfig *proto.VLMConfig
|
||||
if enableMultimodel {
|
||||
vlmConfig, err = s.getVLMProtoConfig(ctx, kb)
|
||||
if err != nil {
|
||||
logger.GetLogger(ctx).WithField("knowledge_id", knowledge.ID).
|
||||
WithField("error", err).Errorf("processDocument build VLM config failed")
|
||||
knowledge.ParseStatus = "failed"
|
||||
knowledge.ErrorMessage = err.Error()
|
||||
knowledge.UpdatedAt = time.Now()
|
||||
s.repo.UpdateKnowledge(ctx, knowledge)
|
||||
span.RecordError(err)
|
||||
return
|
||||
}
|
||||
if vlmConfig == nil {
|
||||
logger.GetLogger(ctx).WithField("knowledge_id", knowledge.ID).
|
||||
Error("processDocument enable multimodal but VLM config missing")
|
||||
knowledge.ParseStatus = "failed"
|
||||
knowledge.ErrorMessage = "VLM 配置缺失"
|
||||
knowledge.UpdatedAt = time.Now()
|
||||
s.repo.UpdateKnowledge(ctx, knowledge)
|
||||
span.RecordError(errors.New("vlm config missing"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := s.docReaderClient.ReadFromFile(ctx, &proto.ReadFromFileRequest{
|
||||
FileContent: contentBytes,
|
||||
FileName: knowledge.FileName,
|
||||
FileType: knowledge.FileType,
|
||||
ReadConfig: &proto.ReadConfig{
|
||||
ChunkSize: int32(kb.ChunkingConfig.ChunkSize),
|
||||
ChunkOverlap: int32(kb.ChunkingConfig.ChunkOverlap),
|
||||
Separators: kb.ChunkingConfig.Separators,
|
||||
EnableMultimodal: enableMultimodel,
|
||||
StorageConfig: &proto.StorageConfig{
|
||||
Provider: proto.StorageProvider(proto.StorageProvider_value[strings.ToUpper(kb.StorageConfig.Provider)]),
|
||||
Region: kb.StorageConfig.Region,
|
||||
BucketName: kb.StorageConfig.BucketName,
|
||||
AccessKeyId: kb.StorageConfig.SecretID,
|
||||
SecretAccessKey: kb.StorageConfig.SecretKey,
|
||||
AppId: kb.StorageConfig.AppID,
|
||||
PathPrefix: kb.StorageConfig.PathPrefix,
|
||||
},
|
||||
VlmConfig: vlmConfig,
|
||||
},
|
||||
RequestId: ctx.Value(types.RequestIDContextKey).(string),
|
||||
})
|
||||
if err != nil {
|
||||
logger.GetLogger(ctx).WithField("knowledge_id", knowledge.ID).
|
||||
WithField("error", err).Errorf("processDocument read file failed")
|
||||
knowledge.ParseStatus = "failed"
|
||||
knowledge.ErrorMessage = err.Error()
|
||||
knowledge.UpdatedAt = time.Now()
|
||||
s.repo.UpdateKnowledge(ctx, knowledge)
|
||||
span.RecordError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Process and store chunks
|
||||
span.AddEvent("start process chunks")
|
||||
s.processChunks(ctx, kb, knowledge, resp.Chunks)
|
||||
}
|
||||
|
||||
// processDocumentFromURL handles asynchronous processing of URL content
|
||||
func (s *knowledgeService) processDocumentFromURL(ctx context.Context,
|
||||
kb *types.KnowledgeBase, knowledge *types.Knowledge, url string, enableMultimodel bool,
|
||||
) {
|
||||
// Update status to processing
|
||||
knowledge.ParseStatus = "processing"
|
||||
knowledge.UpdatedAt = time.Now()
|
||||
if err := s.repo.UpdateKnowledge(ctx, knowledge); err != nil {
|
||||
return
|
||||
}
|
||||
logger.GetLogger(ctx).Infof("processDocumentFromURL enableMultimodel: %v", enableMultimodel)
|
||||
|
||||
// Fetch and chunk content from URL
|
||||
var vlmConfig *proto.VLMConfig
|
||||
if enableMultimodel {
|
||||
cfg, cfgErr := s.getVLMProtoConfig(ctx, kb)
|
||||
if cfgErr != nil {
|
||||
logger.GetLogger(ctx).WithField("knowledge_id", knowledge.ID).
|
||||
WithField("error", cfgErr).Errorf("processDocumentFromURL build VLM config failed")
|
||||
knowledge.ParseStatus = "failed"
|
||||
knowledge.ErrorMessage = cfgErr.Error()
|
||||
knowledge.UpdatedAt = time.Now()
|
||||
s.repo.UpdateKnowledge(ctx, knowledge)
|
||||
return
|
||||
}
|
||||
if cfg == nil {
|
||||
logger.GetLogger(ctx).WithField("knowledge_id", knowledge.ID).
|
||||
Error("processDocumentFromURL enable multimodal but VLM config missing")
|
||||
knowledge.ParseStatus = "failed"
|
||||
knowledge.ErrorMessage = "VLM 配置缺失"
|
||||
knowledge.UpdatedAt = time.Now()
|
||||
s.repo.UpdateKnowledge(ctx, knowledge)
|
||||
return
|
||||
}
|
||||
vlmConfig = cfg
|
||||
}
|
||||
|
||||
resp, err := s.docReaderClient.ReadFromURL(ctx, &proto.ReadFromURLRequest{
|
||||
Url: url,
|
||||
Title: knowledge.Title,
|
||||
ReadConfig: &proto.ReadConfig{
|
||||
ChunkSize: int32(kb.ChunkingConfig.ChunkSize),
|
||||
ChunkOverlap: int32(kb.ChunkingConfig.ChunkOverlap),
|
||||
Separators: kb.ChunkingConfig.Separators,
|
||||
EnableMultimodal: enableMultimodel,
|
||||
StorageConfig: &proto.StorageConfig{
|
||||
Provider: proto.StorageProvider(proto.StorageProvider_value[strings.ToUpper(kb.StorageConfig.Provider)]),
|
||||
Region: kb.StorageConfig.Region,
|
||||
BucketName: kb.StorageConfig.BucketName,
|
||||
AccessKeyId: kb.StorageConfig.SecretID,
|
||||
SecretAccessKey: kb.StorageConfig.SecretKey,
|
||||
AppId: kb.StorageConfig.AppID,
|
||||
PathPrefix: kb.StorageConfig.PathPrefix,
|
||||
},
|
||||
VlmConfig: vlmConfig,
|
||||
},
|
||||
RequestId: ctx.Value(types.RequestIDContextKey).(string),
|
||||
})
|
||||
if err != nil {
|
||||
knowledge.ParseStatus = "failed"
|
||||
knowledge.ErrorMessage = err.Error()
|
||||
knowledge.UpdatedAt = time.Now()
|
||||
s.repo.UpdateKnowledge(ctx, knowledge)
|
||||
return
|
||||
}
|
||||
|
||||
// Process and store chunks
|
||||
s.processChunks(ctx, kb, knowledge, resp.Chunks)
|
||||
}
|
||||
|
||||
// processDocumentFromPassage handles asynchronous processing of text passages
|
||||
func (s *knowledgeService) processDocumentFromPassage(ctx context.Context,
|
||||
kb *types.KnowledgeBase, knowledge *types.Knowledge, passage []string,
|
||||
@@ -1767,7 +1566,6 @@ func (s *knowledgeService) CloneKnowledgeBase(ctx context.Context, srcID, dstID
|
||||
batch := 10
|
||||
g, gctx := errgroup.WithContext(ctx)
|
||||
for ids := range slices.Chunk(delKnowledge, batch) {
|
||||
ids = ids
|
||||
g.Go(func() error {
|
||||
err := s.DeleteKnowledgeList(gctx, ids)
|
||||
if err != nil {
|
||||
@@ -1787,7 +1585,6 @@ func (s *knowledgeService) CloneKnowledgeBase(ctx context.Context, srcID, dstID
|
||||
g, gctx = errgroup.WithContext(ctx)
|
||||
g.SetLimit(batch)
|
||||
for _, knowledge := range addKnowledge {
|
||||
knowledge = knowledge
|
||||
g.Go(func() error {
|
||||
srcKn, err := s.repo.GetKnowledgeByID(gctx, srcKB.TenantID, knowledge)
|
||||
if err != nil {
|
||||
@@ -1916,7 +1713,8 @@ func (s *knowledgeService) UpdateImageInfo(ctx context.Context, knowledgeID stri
|
||||
}
|
||||
|
||||
// Mark that we've found chunks for this image
|
||||
if child.ChunkType == types.ChunkTypeImageCaption {
|
||||
switch child.ChunkType {
|
||||
case types.ChunkTypeImageCaption:
|
||||
hasCaptionChunk = true
|
||||
// Update caption if it has changed
|
||||
if image.Caption != cImageInfo[0].Caption {
|
||||
@@ -1924,7 +1722,7 @@ func (s *knowledgeService) UpdateImageInfo(ctx context.Context, knowledgeID stri
|
||||
child.ImageInfo = imageInfo
|
||||
updateChunk = append(updateChunk, chunkChildren[i])
|
||||
}
|
||||
} else if child.ChunkType == types.ChunkTypeImageOCR {
|
||||
case types.ChunkTypeImageOCR:
|
||||
hasOCRChunk = true
|
||||
// Update OCR if it has changed
|
||||
if image.OCRText != cImageInfo[0].OCRText {
|
||||
@@ -2240,7 +2038,8 @@ func (s *knowledgeService) UpsertFAQEntries(ctx context.Context,
|
||||
|
||||
// calculateAppendOperations 计算Append模式下需要处理的条目,跳过已存在且内容相同的条目
|
||||
func (s *knowledgeService) calculateAppendOperations(ctx context.Context,
|
||||
tenantID uint64, knowledgeID string, entries []types.FAQEntryPayload) ([]types.FAQEntryPayload, int, error) {
|
||||
tenantID uint64, knowledgeID string, entries []types.FAQEntryPayload,
|
||||
) ([]types.FAQEntryPayload, int, error) {
|
||||
if len(entries) == 0 {
|
||||
return []types.FAQEntryPayload{}, 0, nil
|
||||
}
|
||||
@@ -2249,7 +2048,8 @@ func (s *knowledgeService) calculateAppendOperations(ctx context.Context,
|
||||
|
||||
// calculateReplaceOperations 计算Replace模式下需要删除、创建、更新的条目
|
||||
func (s *knowledgeService) calculateReplaceOperations(ctx context.Context,
|
||||
tenantID uint64, knowledgeID string, newEntries []types.FAQEntryPayload) ([]types.FAQEntryPayload, []*types.Chunk, int, error) {
|
||||
tenantID uint64, knowledgeID string, newEntries []types.FAQEntryPayload,
|
||||
) ([]types.FAQEntryPayload, []*types.Chunk, int, error) {
|
||||
// 计算所有新条目的 content hash,并同时构建 hash 到 entry 的映射
|
||||
type entryWithHash struct {
|
||||
entry types.FAQEntryPayload
|
||||
@@ -2316,7 +2116,8 @@ func (s *knowledgeService) calculateReplaceOperations(ctx context.Context,
|
||||
|
||||
// executeFAQImport 执行实际的FAQ导入逻辑
|
||||
func (s *knowledgeService) executeFAQImport(ctx context.Context, taskID string, kbID string,
|
||||
payload *types.FAQBatchUpsertPayload, tenantID uint64, processedCount int) (err error) {
|
||||
payload *types.FAQBatchUpsertPayload, tenantID uint64, processedCount int,
|
||||
) (err error) {
|
||||
// 保存知识库和embedding模型信息,用于清理索引
|
||||
var kb *types.KnowledgeBase
|
||||
var embeddingModel embedding.Embedder
|
||||
@@ -2364,8 +2165,7 @@ func (s *knowledgeService) executeFAQImport(ctx context.Context, taskID string,
|
||||
|
||||
if payload.Mode == types.FAQBatchModeReplace {
|
||||
// Replace模式:计算需要删除、创建、更新的条目
|
||||
entriesToProcess, chunksToDelete, skippedCount, err =
|
||||
s.calculateReplaceOperations(ctx, tenantID, faqKnowledge.ID, payload.Entries)
|
||||
entriesToProcess, chunksToDelete, skippedCount, err = s.calculateReplaceOperations(ctx, tenantID, faqKnowledge.ID, payload.Entries)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to calculate replace operations: %w", err)
|
||||
}
|
||||
@@ -3080,13 +2880,15 @@ func (s *knowledgeService) ensureFAQKnowledge(ctx context.Context, tenantID uint
|
||||
}
|
||||
|
||||
func (s *knowledgeService) updateFAQImportStatus(ctx context.Context, knowledgeID string, status types.FAQImportTaskStatus,
|
||||
progress, total, processed int, errorMsg string) error {
|
||||
progress, total, processed int, errorMsg string,
|
||||
) error {
|
||||
return s.updateFAQImportStatusWithRanges(ctx, knowledgeID, status, progress, total, processed, errorMsg)
|
||||
}
|
||||
|
||||
// updateFAQImportStatusWithRanges 更新FAQ Knowledge的导入任务状态,包含NextChunkIndex
|
||||
func (s *knowledgeService) updateFAQImportStatusWithRanges(ctx context.Context, knowledgeID string, status types.FAQImportTaskStatus,
|
||||
progress, total, processed int, errorMsg string) error {
|
||||
progress, total, processed int, errorMsg string,
|
||||
) error {
|
||||
tenantID := ctx.Value(types.TenantIDContextKey).(uint64)
|
||||
knowledge, err := s.repo.GetKnowledgeByID(ctx, tenantID, knowledgeID)
|
||||
if err != nil {
|
||||
|
||||
@@ -131,14 +131,15 @@ func (s *knowledgeBaseService) ListKnowledgeBases(ctx context.Context) ([]*types
|
||||
kb.EnsureDefaults()
|
||||
|
||||
// Get knowledge count
|
||||
if kb.Type == types.KnowledgeBaseTypeDocument {
|
||||
switch kb.Type {
|
||||
case types.KnowledgeBaseTypeDocument:
|
||||
knowledgeCount, err := s.kgRepo.CountKnowledgeByKnowledgeBaseID(ctx, tenantID, kb.ID)
|
||||
if err != nil {
|
||||
logger.Warnf(ctx, "Failed to get knowledge count for knowledge base %s: %v", kb.ID, err)
|
||||
} else {
|
||||
kb.KnowledgeCount = knowledgeCount
|
||||
}
|
||||
} else if kb.Type == types.KnowledgeBaseTypeFAQ {
|
||||
case types.KnowledgeBaseTypeFAQ:
|
||||
// Get chunk count
|
||||
chunkCount, err := s.chunkRepo.CountChunksByKnowledgeBaseID(ctx, tenantID, kb.ID)
|
||||
if err != nil {
|
||||
|
||||
@@ -192,7 +192,6 @@ func (s *smartCompressionStrategy) summarizeMessages(ctx context.Context, messag
|
||||
Temperature: 0.3, // Lower temperature for more consistent summaries
|
||||
MaxTokens: 500, // Limit summary length
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate summary: %w", err)
|
||||
}
|
||||
|
||||
@@ -96,11 +96,6 @@ func getWordNgrams(n int, sentences []string, exclusive bool) *Ngrams {
|
||||
return getNgrams(n, words, exclusive)
|
||||
}
|
||||
|
||||
func lenLcs(x, y []string) int {
|
||||
table := lcs(x, y)
|
||||
return table[len(x)][len(y)]
|
||||
}
|
||||
|
||||
func lcs(x, y []string) [][]int {
|
||||
n, m := len(x), len(y)
|
||||
table := make([][]int, n+1)
|
||||
|
||||
@@ -83,7 +83,6 @@ func (m *MetricList) Avg() *types.MetricResult {
|
||||
|
||||
// HookMetric tracks evaluation metrics for QA pairs
|
||||
type HookMetric struct {
|
||||
task *types.EvaluationTask
|
||||
qaPairMetricList []*qaPairMetric // Per-QA pair metrics
|
||||
metricResults *MetricList // Aggregated results
|
||||
mu *sync.RWMutex // Thread safety
|
||||
|
||||
@@ -27,7 +27,7 @@ func (r *RetrieveEngineRegistry) Register(repo interfaces.RetrieveEngineService)
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if _, exists := r.repositories[repo.EngineType()]; exists {
|
||||
return fmt.Errorf("Repository type %s already registered", repo.EngineType())
|
||||
return fmt.Errorf("repository type %s already registered", repo.EngineType())
|
||||
}
|
||||
|
||||
r.repositories[repo.EngineType()] = repo
|
||||
@@ -43,7 +43,7 @@ func (r *RetrieveEngineRegistry) GetRetrieveEngineService(repoType types.Retriev
|
||||
|
||||
repo, exists := r.repositories[repoType]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("Repository of type %s not found", repoType)
|
||||
return nil, fmt.Errorf("repository of type %s not found", repoType)
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
|
||||
@@ -587,7 +587,6 @@ func (s *sessionService) selectChatModelIDWithOverride(ctx context.Context, sess
|
||||
// 3. Session's SummaryModelID (if not Remote)
|
||||
// 4. First knowledge base's SummaryModelID
|
||||
func (s *sessionService) selectChatModelID(ctx context.Context, session *types.Session, knowledgeBaseIDs []string) (string, error) {
|
||||
|
||||
// First, check if session has a SummaryModelID and if it's a Remote model
|
||||
if session.SummaryModelID != "" {
|
||||
model, err := s.modelService.GetModelByID(ctx, session.SummaryModelID)
|
||||
|
||||
@@ -124,7 +124,7 @@ func BuildContainer(container *dig.Container) *dig.Container {
|
||||
|
||||
// Session service (depends on agent service)
|
||||
// SessionService is created after AgentService and passes itself to AgentService.CreateAgentEngine when needed
|
||||
must(container.Provide(service.NewSessionService))
|
||||
must(container.Provide(service.NewSessionService))
|
||||
|
||||
must(container.Provide(router.NewAsyncqClient))
|
||||
must(container.Provide(router.NewAsynqServer))
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
||||
"github.com/Tencent/WeKnora/internal/utils"
|
||||
secutils "github.com/Tencent/WeKnora/internal/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/ollama/ollama/api"
|
||||
@@ -198,7 +197,7 @@ type InitializationRequest struct {
|
||||
// UpdateKBConfig 根据知识库ID和模型ID更新配置(简化版)
|
||||
func (h *InitializationHandler) UpdateKBConfig(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
kbIdStr := secutils.SanitizeForLog(c.Param("kbId"))
|
||||
kbIdStr := utils.SanitizeForLog(c.Param("kbId"))
|
||||
|
||||
var req KBModelConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@@ -210,7 +209,7 @@ func (h *InitializationHandler) UpdateKBConfig(c *gin.Context) {
|
||||
// 获取知识库信息
|
||||
kb, err := h.kbService.GetKnowledgeBaseByID(ctx, kbIdStr)
|
||||
if err != nil || kb == nil {
|
||||
logger.ErrorWithFields(ctx, err, map[string]interface{}{"kbId": secutils.SanitizeForLog(kbIdStr)})
|
||||
logger.ErrorWithFields(ctx, err, map[string]interface{}{"kbId": utils.SanitizeForLog(kbIdStr)})
|
||||
c.Error(errors.NewNotFoundError("知识库不存在"))
|
||||
return
|
||||
}
|
||||
@@ -350,7 +349,7 @@ func (h *InitializationHandler) UpdateKBConfig(c *gin.Context) {
|
||||
// InitializeByKB 根据知识库ID执行配置更新
|
||||
func (h *InitializationHandler) InitializeByKB(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
kbIdStr := secutils.SanitizeForLog(c.Param("kbId"))
|
||||
kbIdStr := utils.SanitizeForLog(c.Param("kbId"))
|
||||
|
||||
var req InitializationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@@ -358,12 +357,12 @@ func (h *InitializationHandler) InitializeByKB(c *gin.Context) {
|
||||
c.Error(errors.NewBadRequestError(err.Error()))
|
||||
return
|
||||
}
|
||||
logger.Infof(ctx, "Starting knowledge base configuration update, kbId: %s, request: %s", secutils.SanitizeForLog(kbIdStr), secutils.SanitizeForLog(utils.ToJSON(req)))
|
||||
logger.Infof(ctx, "Starting knowledge base configuration update, kbId: %s, request: %s", utils.SanitizeForLog(kbIdStr), utils.SanitizeForLog(utils.ToJSON(req)))
|
||||
|
||||
// 获取指定知识库信息
|
||||
kb, err := h.kbService.GetKnowledgeBaseByID(ctx, kbIdStr)
|
||||
if err != nil {
|
||||
logger.ErrorWithFields(ctx, err, map[string]interface{}{"kbId": secutils.SanitizeForLog(kbIdStr)})
|
||||
logger.ErrorWithFields(ctx, err, map[string]interface{}{"kbId": utils.SanitizeForLog(kbIdStr)})
|
||||
c.Error(errors.NewInternalServerError("获取知识库信息失败: " + err.Error()))
|
||||
return
|
||||
}
|
||||
@@ -450,18 +449,18 @@ func (h *InitializationHandler) InitializeByKB(c *gin.Context) {
|
||||
}{
|
||||
{
|
||||
modelType: types.ModelTypeKnowledgeQA,
|
||||
name: secutils.SanitizeForLog(req.LLM.ModelName),
|
||||
name: utils.SanitizeForLog(req.LLM.ModelName),
|
||||
source: types.ModelSource(req.LLM.Source),
|
||||
description: "LLM Model for Knowledge QA",
|
||||
baseURL: secutils.SanitizeForLog(req.LLM.BaseURL),
|
||||
baseURL: utils.SanitizeForLog(req.LLM.BaseURL),
|
||||
apiKey: req.LLM.APIKey,
|
||||
},
|
||||
{
|
||||
modelType: types.ModelTypeEmbedding,
|
||||
name: secutils.SanitizeForLog(req.Embedding.ModelName),
|
||||
name: utils.SanitizeForLog(req.Embedding.ModelName),
|
||||
source: types.ModelSource(req.Embedding.Source),
|
||||
description: "Embedding Model",
|
||||
baseURL: secutils.SanitizeForLog(req.Embedding.BaseURL),
|
||||
baseURL: utils.SanitizeForLog(req.Embedding.BaseURL),
|
||||
apiKey: req.Embedding.APIKey,
|
||||
dimension: req.Embedding.Dimension,
|
||||
},
|
||||
@@ -480,10 +479,10 @@ func (h *InitializationHandler) InitializeByKB(c *gin.Context) {
|
||||
interfaceType string
|
||||
}{
|
||||
modelType: types.ModelTypeRerank,
|
||||
name: secutils.SanitizeForLog(req.Rerank.ModelName),
|
||||
name: utils.SanitizeForLog(req.Rerank.ModelName),
|
||||
source: types.ModelSourceRemote,
|
||||
description: "Rerank Model",
|
||||
baseURL: secutils.SanitizeForLog(req.Rerank.BaseURL),
|
||||
baseURL: utils.SanitizeForLog(req.Rerank.BaseURL),
|
||||
apiKey: req.Rerank.APIKey,
|
||||
})
|
||||
}
|
||||
@@ -501,10 +500,10 @@ func (h *InitializationHandler) InitializeByKB(c *gin.Context) {
|
||||
interfaceType string
|
||||
}{
|
||||
modelType: types.ModelTypeVLLM,
|
||||
name: secutils.SanitizeForLog(req.Multimodal.VLM.ModelName),
|
||||
name: utils.SanitizeForLog(req.Multimodal.VLM.ModelName),
|
||||
source: types.ModelSourceRemote,
|
||||
description: "VLM Model",
|
||||
baseURL: secutils.SanitizeForLog(req.Multimodal.VLM.BaseURL),
|
||||
baseURL: utils.SanitizeForLog(req.Multimodal.VLM.BaseURL),
|
||||
apiKey: req.Multimodal.VLM.APIKey,
|
||||
interfaceType: req.Multimodal.VLM.InterfaceType,
|
||||
})
|
||||
@@ -676,7 +675,7 @@ func (h *InitializationHandler) InitializeByKB(c *gin.Context) {
|
||||
|
||||
err = h.kbRepository.UpdateKnowledgeBase(ctx, kb)
|
||||
if err != nil {
|
||||
logger.ErrorWithFields(ctx, err, map[string]interface{}{"kbId": secutils.SanitizeForLog(kbIdStr)})
|
||||
logger.ErrorWithFields(ctx, err, map[string]interface{}{"kbId": utils.SanitizeForLog(kbIdStr)})
|
||||
c.Error(errors.NewInternalServerError("更新知识库配置失败: " + err.Error()))
|
||||
return
|
||||
}
|
||||
@@ -775,7 +774,7 @@ func (h *InitializationHandler) CheckOllamaModels(c *gin.Context) {
|
||||
modelStatus[modelName] = available
|
||||
}
|
||||
|
||||
logger.Infof(ctx, "Model %s availability: %v", secutils.SanitizeForLog(modelName), modelStatus[modelName])
|
||||
logger.Infof(ctx, "Model %s availability: %v", utils.SanitizeForLog(modelName), modelStatus[modelName])
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@@ -1010,8 +1009,8 @@ func (h *InitializationHandler) pullModelWithProgress(ctx context.Context,
|
||||
|
||||
// 使用Ollama客户端的Pull方法,带进度回调
|
||||
err = h.ollamaService.GetClient().Pull(ctx, pullReq, func(progress api.ProgressResponse) error {
|
||||
var progressPercent float64 = 0.0
|
||||
var message string = "下载中"
|
||||
progressPercent := 0.0
|
||||
message := "下载中"
|
||||
|
||||
if progress.Total > 0 && progress.Completed > 0 {
|
||||
progressPercent = float64(progress.Completed) / float64(progress.Total) * 100
|
||||
@@ -1057,7 +1056,7 @@ func (h *InitializationHandler) updateTaskStatus(
|
||||
// GetCurrentConfigByKB 根据知识库ID获取配置信息
|
||||
func (h *InitializationHandler) GetCurrentConfigByKB(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
kbIdStr := secutils.SanitizeForLog(c.Param("kbId"))
|
||||
kbIdStr := utils.SanitizeForLog(c.Param("kbId"))
|
||||
|
||||
logger.Info(ctx, "Getting configuration for knowledge base")
|
||||
|
||||
@@ -1103,10 +1102,7 @@ func (h *InitializationHandler) GetCurrentConfigByKB(c *gin.Context) {
|
||||
Page: 1,
|
||||
PageSize: 1,
|
||||
}, "")
|
||||
hasFiles := false
|
||||
if err == nil && knowledgeList != nil && knowledgeList.Total > 0 {
|
||||
hasFiles = true
|
||||
}
|
||||
hasFiles := err == nil && knowledgeList != nil && knowledgeList.Total > 0
|
||||
|
||||
// 构建配置响应
|
||||
config := h.buildConfigResponse(ctx, models, kb, hasFiles)
|
||||
@@ -1339,7 +1335,7 @@ func (h *InitializationHandler) TestEmbeddingModel(c *gin.Context) {
|
||||
|
||||
emb, err := embedding.NewEmbedder(cfg)
|
||||
if err != nil {
|
||||
logger.ErrorWithFields(ctx, err, map[string]interface{}{"model": secutils.SanitizeForLog(req.ModelName)})
|
||||
logger.ErrorWithFields(ctx, err, map[string]interface{}{"model": utils.SanitizeForLog(req.ModelName)})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"data": gin.H{`available`: false, `message`: fmt.Sprintf("创建Embedder失败: %v", err), `dimension`: 0},
|
||||
@@ -1595,7 +1591,7 @@ func (h *InitializationHandler) TestMultimodalFunction(c *gin.Context) {
|
||||
c.Error(errors.NewBadRequestError("图片文件大小不能超过10MB"))
|
||||
return
|
||||
}
|
||||
logger.Infof(ctx, "Processing image: %s", secutils.SanitizeForLog(header.Filename))
|
||||
logger.Infof(ctx, "Processing image: %s", utils.SanitizeForLog(header.Filename))
|
||||
|
||||
// 解析文档分割配置
|
||||
chunkSizeInt32, err := strconv.ParseInt(req.ChunkSize, 10, 32)
|
||||
|
||||
@@ -30,4 +30,3 @@ var (
|
||||
// ErrConnectionClosed is returned when connection is closed unexpectedly
|
||||
ErrConnectionClosed = errors.New("connection closed")
|
||||
)
|
||||
|
||||
|
||||
@@ -122,7 +122,6 @@ func TestRemoteAPIChat(t *testing.T) {
|
||||
response.Usage.CompletionTokens,
|
||||
response.Usage.TotalTokens)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ type RankResult struct {
|
||||
|
||||
// Handles the RelevanceScore field by checking if RelevanceScore exists first, otherwise falls back to Score field
|
||||
func (r *RankResult) UnmarshalJSON(data []byte) error {
|
||||
|
||||
var temp struct {
|
||||
Index int `json:"index"`
|
||||
Document DocumentInfo `json:"document"`
|
||||
|
||||
@@ -73,7 +73,7 @@ func (m *MemoryStreamManager) AppendEvent(ctx context.Context, sessionID, messag
|
||||
|
||||
// Append event
|
||||
stream.events = append(stream.events, event)
|
||||
stream.lastUpdated = time.Now()
|
||||
stream.lastUpdated = time.Now()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -104,7 +104,7 @@ func (m *MemoryStreamManager) GetEvents(ctx context.Context, sessionID, messageI
|
||||
copy(eventsCopy, events)
|
||||
|
||||
return eventsCopy, nextOffset, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure MemoryStreamManager implements StreamManager interface
|
||||
var _ interfaces.StreamManager = (*MemoryStreamManager)(nil)
|
||||
|
||||
@@ -6,13 +6,14 @@ const (
|
||||
TypeFAQImport = "faq:import" // FAQ导入任务
|
||||
)
|
||||
|
||||
// ExtractChunkPayload represents the extract chunk task payload
|
||||
type ExtractChunkPayload struct {
|
||||
TenantID uint64 `json:"tenant_id"`
|
||||
ChunkID string `json:"chunk_id"`
|
||||
ModelID string `json:"model_id"`
|
||||
}
|
||||
|
||||
// DocumentProcessPayload 文档处理任务payload
|
||||
// DocumentProcessPayload represents the document process task payload
|
||||
type DocumentProcessPayload struct {
|
||||
RequestId string `json:"request_id"`
|
||||
TenantID uint64 `json:"tenant_id"`
|
||||
@@ -26,7 +27,7 @@ type DocumentProcessPayload struct {
|
||||
EnableMultimodel bool `json:"enable_multimodel"`
|
||||
}
|
||||
|
||||
// FAQImportPayload FAQ导入任务payload
|
||||
// FAQImportPayload represents the FAQ import task payload
|
||||
type FAQImportPayload struct {
|
||||
TenantID uint64 `json:"tenant_id"`
|
||||
TaskID string `json:"task_id"`
|
||||
@@ -36,6 +37,7 @@ type FAQImportPayload struct {
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
|
||||
// PromptTemplateStructured represents the prompt template structured
|
||||
type PromptTemplateStructured struct {
|
||||
Description string `json:"description"`
|
||||
Tags []string `json:"tags"`
|
||||
@@ -48,6 +50,7 @@ type GraphNode struct {
|
||||
Attributes []string `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
// GraphRelation represents the relation of the graph
|
||||
type GraphRelation struct {
|
||||
Node1 string `json:"node1,omitempty"`
|
||||
Node2 string `json:"node2,omitempty"`
|
||||
@@ -60,11 +63,13 @@ type GraphData struct {
|
||||
Relation []*GraphRelation `json:"relation,omitempty"`
|
||||
}
|
||||
|
||||
// NameSpace represents the name space of the knowledge base and knowledge
|
||||
type NameSpace struct {
|
||||
KnowledgeBase string `json:"knowledge_base"`
|
||||
Knowledge string `json:"knowledge"`
|
||||
}
|
||||
|
||||
// Labels returns the labels of the name space
|
||||
func (n NameSpace) Labels() []string {
|
||||
res := make([]string, 0)
|
||||
if n.KnowledgeBase != "" {
|
||||
|
||||
@@ -160,10 +160,14 @@ type FAQSearchRequest struct {
|
||||
type FAQImportTaskStatus string
|
||||
|
||||
const (
|
||||
FAQImportStatusPending FAQImportTaskStatus = "pending"
|
||||
// FAQImportStatusPending represents the pending status of the FAQ import task
|
||||
FAQImportStatusPending FAQImportTaskStatus = "pending"
|
||||
// FAQImportStatusProcessing represents the processing status of the FAQ import task
|
||||
FAQImportStatusProcessing FAQImportTaskStatus = "processing"
|
||||
FAQImportStatusCompleted FAQImportTaskStatus = "completed"
|
||||
FAQImportStatusFailed FAQImportTaskStatus = "failed"
|
||||
// FAQImportStatusCompleted represents the completed status of the FAQ import task
|
||||
FAQImportStatusCompleted FAQImportTaskStatus = "completed"
|
||||
// FAQImportStatusFailed represents the failed status of the FAQ import task
|
||||
FAQImportStatusFailed FAQImportTaskStatus = "failed"
|
||||
)
|
||||
|
||||
// FAQImportMetadata 存储在Knowledge.Metadata中的FAQ导入任务信息
|
||||
|
||||
@@ -10,10 +10,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// KnowledgeTypeManual represents the manual knowledge type
|
||||
KnowledgeTypeManual = "manual"
|
||||
KnowledgeTypeFAQ = "faq"
|
||||
// KnowledgeTypeFAQ represents the FAQ knowledge type
|
||||
KnowledgeTypeFAQ = "faq"
|
||||
)
|
||||
|
||||
// ManualKnowledgeFormat represents the format of the manual knowledge
|
||||
const (
|
||||
ManualKnowledgeFormatMarkdown = "markdown"
|
||||
ManualKnowledgeStatusDraft = "draft"
|
||||
|
||||
@@ -8,32 +8,34 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// KnowledgeBaseType represents the type of the knowledge base
|
||||
const (
|
||||
// KnowledgeBaseTypeDocument represents the document knowledge base type
|
||||
KnowledgeBaseTypeDocument = "document"
|
||||
KnowledgeBaseTypeFAQ = "faq"
|
||||
)
|
||||
|
||||
// FAQIndexMode 表示 FAQ 索引策略:仅索引标准问还是同时索引问答内容
|
||||
// FAQIndexMode represents the FAQ index mode: only index questions or index questions and answers
|
||||
type FAQIndexMode string
|
||||
|
||||
const (
|
||||
// FAQIndexModeQuestionOnly 仅使用标准问及相似问向量化
|
||||
// FAQIndexModeQuestionOnly only index questions and similar questions
|
||||
FAQIndexModeQuestionOnly FAQIndexMode = "question_only"
|
||||
// FAQIndexModeQuestionAnswer 将标准问与答案拼接后向量化
|
||||
// FAQIndexModeQuestionAnswer index questions and answers together
|
||||
FAQIndexModeQuestionAnswer FAQIndexMode = "question_answer"
|
||||
)
|
||||
|
||||
// FAQQuestionIndexMode 表示 FAQ 问题索引方式:一起索引还是分别索引
|
||||
// FAQQuestionIndexMode represents the FAQ question index mode: index together or index separately
|
||||
type FAQQuestionIndexMode string
|
||||
|
||||
const (
|
||||
// FAQQuestionIndexModeCombined 将标准问和相似问一起索引(当前方式)
|
||||
// FAQQuestionIndexModeCombined index questions and similar questions together
|
||||
FAQQuestionIndexModeCombined FAQQuestionIndexMode = "combined"
|
||||
// FAQQuestionIndexModeSeparate 将标准问和相似问分别索引
|
||||
// FAQQuestionIndexModeSeparate index questions and similar questions separately
|
||||
FAQQuestionIndexModeSeparate FAQQuestionIndexMode = "separate"
|
||||
)
|
||||
|
||||
// KnowledgeBase represents a knowledge base
|
||||
// KnowledgeBase represents a knowledge base entity
|
||||
type KnowledgeBase struct {
|
||||
// Unique identifier of the knowledge base
|
||||
ID string `yaml:"id" json:"id" gorm:"type:varchar(36);primaryKey"`
|
||||
@@ -195,6 +197,7 @@ func (c *VLMConfig) Scan(value interface{}) error {
|
||||
return json.Unmarshal(b, c)
|
||||
}
|
||||
|
||||
// ExtractConfig represents the extract configuration for a knowledge base
|
||||
type ExtractConfig struct {
|
||||
Enabled bool `yaml:"enabled" json:"enabled"`
|
||||
Text string `yaml:"text" json:"text,omitempty"`
|
||||
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
ModelTypeVLLM ModelType = "VLLM" // VLLM model
|
||||
)
|
||||
|
||||
// ModelStatus represents the status of the model
|
||||
type ModelStatus string
|
||||
|
||||
const (
|
||||
@@ -36,6 +37,7 @@ const (
|
||||
ModelSourceAliyun ModelSource = "aliyun" // Aliyun DashScope model
|
||||
)
|
||||
|
||||
// EmbeddingParameters represents the embedding parameters for a model
|
||||
type EmbeddingParameters struct {
|
||||
Dimension int `yaml:"dimension" json:"dimension"`
|
||||
TruncatePromptTokens int `yaml:"truncate_prompt_tokens" json:"truncate_prompt_tokens"`
|
||||
|
||||
@@ -17,6 +17,7 @@ const (
|
||||
FallbackStrategyModel FallbackStrategy = "model" // Model fallback response
|
||||
)
|
||||
|
||||
// SummaryConfig represents the summary configuration for a session
|
||||
type SummaryConfig struct {
|
||||
// Max tokens
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
@@ -109,6 +110,7 @@ func (s *Session) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StringArray represents a list of strings
|
||||
type StringArray []string
|
||||
|
||||
// Value implements the driver.Valuer interface, used to convert StringArray to database value
|
||||
|
||||
@@ -44,10 +44,12 @@ type Tenant struct {
|
||||
DeletedAt gorm.DeletedAt `yaml:"deleted_at" json:"deleted_at" gorm:"index"`
|
||||
}
|
||||
|
||||
// RetrieverEngines represents the retriever engines for a tenant
|
||||
type RetrieverEngines struct {
|
||||
Engines []RetrieverEngineParams `yaml:"engines" json:"engines" gorm:"type:json"`
|
||||
}
|
||||
|
||||
// BeforeCreate is a hook function that is called before creating a tenant
|
||||
func (t *Tenant) BeforeCreate(tx *gorm.DB) error {
|
||||
if t.RetrieverEngines.Engines == nil {
|
||||
t.RetrieverEngines.Engines = []RetrieverEngineParams{}
|
||||
|
||||
Reference in New Issue
Block a user