Files
customer-server/service/dataset_service.go
2026-03-14 10:02:49 +08:00

198 lines
7.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"context"
"customer-server/dao"
"customer-server/model/entity"
"fmt"
"gitea.com/red-future/common/ragflow"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
// EnsureTenantDataset 确保租户知识库存在
// 逻辑:
// 1. 用租户ID查找MongoDB中的知识库ID
// 2. 测试知识库ID是否在RAGFlow中可用
// 3. 如果不可用就直接根据名字在RAGFlow中查找
// 4. 找到了就把知识库ID存到MongoDB
// 5. 如果名字也找不到,就创建新的
// 6. 把新的ID和名字存到MongoDB
func EnsureTenantDataset(ctx context.Context, tenantId string) (datasetId string, err error) {
ragflowClient := ragflow.GetGlobalClient()
datasetName := fmt.Sprintf("dataset_tenant_%s", tenantId)
// 1. 用租户ID查找MongoDB中的知识库ID
datasetId, err = dao.RAGFlowConfig.FindDatasetIdByTenant(ctx, tenantId)
if err != nil && err.Error() != "mongo: no documents in result" {
return "", gerror.Wrap(err, "查询租户知识库ID失败")
}
// 2. 测试知识库ID是否在RAGFlow中可用
if datasetId != "" {
g.Log().Infof(ctx, "租户%s已有知识库ID: %s测试是否可用", tenantId, datasetId)
listReq := &ragflow.ListDatasetsReq{Page: 1, PageSize: 100}
listRes, listErr := ragflowClient.ListDatasets(ctx, listReq)
if listErr == nil && listRes != nil {
g.Log().Infof(ctx, "ListDatasets返回%d个知识库", len(listRes.Data))
for i, ds := range listRes.Data {
g.Log().Infof(ctx, "知识库[%d]: id=%s, name=%s", i, ds.Id, ds.Name)
}
// 测试ID是否可用
for _, ds := range listRes.Data {
if ds.Id == datasetId {
g.Log().Infof(ctx, "知识库ID可用: %s", datasetId)
return datasetId, nil
}
}
// 3. ID不可用直接根据名字查找
g.Log().Warningf(ctx, "知识库ID不可用根据名字查找: %s", datasetName)
for _, ds := range listRes.Data {
if ds.Name == datasetName {
datasetId = ds.Id
g.Log().Infof(ctx, "找到同名知识库: name=%s, id=%s", datasetName, datasetId)
// 4. 把知识库ID存到MongoDB
if updateErr := updateDatasetIdInMongo(ctx, tenantId, datasetId); updateErr != nil {
g.Log().Errorf(ctx, "更新MongoDB失败: %v", updateErr)
}
return datasetId, nil
}
}
g.Log().Warningf(ctx, "未找到同名知识库: %s", datasetName)
} else {
g.Log().Errorf(ctx, "ListDatasets失败: %v", listErr)
}
}
// 5. 名字也找不到,创建新的
g.Log().Infof(ctx, "创建新知识库: name=%s", datasetName)
// 从config读取embedding模型如果未配置则使用默认值
embeddingModel := g.Cfg().MustGet(ctx, "ragflow.embedding_model", "text-embedding-v4").String()
createReq := &ragflow.CreateDatasetReq{
Name: datasetName,
EmbeddingModel: embeddingModel,
ChunkMethod: "naive",
}
dataset, err := ragflowClient.CreateDataset(ctx, createReq)
// 不依赖CreateDataset的返回值因为RAGFlow可能返回code=0但data=null
// 立即用ListDatasets查找新创建的知识库来确认
g.Log().Infof(ctx, "创建请求已发送,立即查找确认: name=%s", datasetName)
listReq2 := &ragflow.ListDatasetsReq{Page: 1, PageSize: 100}
listRes2, listErr2 := ragflowClient.ListDatasets(ctx, listReq2)
if listErr2 == nil && listRes2 != nil {
for _, ds := range listRes2.Data {
if ds.Name == datasetName {
datasetId = ds.Id
g.Log().Infof(ctx, "确认知识库已创建: id=%s, name=%s", datasetId, datasetName)
goto saveRecord
}
}
}
// 如果ListDatasets也找不到说明创建真的失败了
if err != nil {
return "", gerror.Wrapf(err, "创建知识库失败且无法确认")
}
if dataset == nil || dataset.Id == "" {
return "", gerror.Newf("创建知识库失败无法通过ListDatasets确认: name=%s", datasetName)
}
// 使用CreateDataset返回的ID兜底
datasetId = dataset.Id
g.Log().Infof(ctx, "知识库创建成功: id=%s, name=%s", datasetId, datasetName)
saveRecord:
// 6. 把新的ID和名字存到MongoDB
if err := saveSystemDatasetRecord(ctx, tenantId, datasetId, datasetName); err != nil {
g.Log().Errorf(ctx, "保存到MongoDB失败: %v", err)
return "", gerror.Wrap(err, "保存到MongoDB失败")
}
return datasetId, nil
}
// uploadPlaceholderDocument 上传占位文档
func uploadPlaceholderDocument(ctx context.Context, datasetId, tenantId string) error {
ragflowClient := ragflow.GetGlobalClient()
placeholderContent := "欢迎使用智能客服系统。这是一个占位文档,实际产品和话术会在后续上传。"
placeholderFilename := fmt.Sprintf("placeholder_tenant_%s.txt", tenantId)
documentId, err := ragflowClient.UploadDocumentFromText(ctx, datasetId, placeholderContent, placeholderFilename)
if err != nil {
return gerror.Wrap(err, "上传占位文档失败")
}
g.Log().Infof(ctx, "占位文档上传成功: document_id=%s", documentId)
if parseErr := ragflowClient.ParseDocuments(ctx, datasetId, []string{documentId}); parseErr != nil {
g.Log().Warningf(ctx, "解析占位文档失败: %v", parseErr)
return parseErr
}
g.Log().Infof(ctx, "占位文档解析已启动(后台异步进行)")
return nil
}
// saveSystemDatasetRecord 保存系统占位记录到MongoDB
func saveSystemDatasetRecord(ctx context.Context, tenantId, datasetId, datasetName string) error {
// 先检查是否已有记录
existing, err := dao.RAGFlowConfig.FindDatasetIdByTenant(ctx, tenantId)
if err == nil && existing != "" {
g.Log().Infof(ctx, "租户%s已有知识库记录无需创建系统占位记录", tenantId)
return nil
}
// 创建系统占位记录
systemAccountName := fmt.Sprintf("_system_tenant_%s", tenantId)
config := &entity.RAGFlowConfig{
AccountName: systemAccountName,
Platform: "system",
DatasetId: datasetId,
DatasetIds: []string{datasetId},
DatasetName: datasetName,
ChatId: "", // 系统记录不需要Chat
Prompt: "",
DocumentIds: []string{},
SimilarityThreshold: 0.2,
KeywordsSimilarityWeight: 0.7,
TopN: 8,
EmptyResponse: "抱歉,我暂时无法回答这个问题。",
SyncStatus: "synced",
}
// 统一使用string类型存储tenantId到MongoDB
config.TenantId = tenantId
config.IsDeleted = false
if err := dao.RAGFlowConfig.Insert(ctx, config); err != nil {
return gerror.Wrap(err, "插入系统占位记录失败")
}
g.Log().Infof(ctx, "系统占位记录已保存: tenant_id=%s, dataset_id=%s", tenantId, datasetId)
return nil
}
// updateDatasetIdInMongo 更新MongoDB中租户的所有datasetId记录
func updateDatasetIdInMongo(ctx context.Context, tenantId, newDatasetId string) error {
// 更新该租户所有ragflow_config记录的datasetId
if err := dao.RAGFlowConfig.UpdateDatasetIdByTenant(ctx, tenantId, newDatasetId); err != nil {
return gerror.Wrap(err, "更新租户datasetId失败")
}
g.Log().Infof(ctx, "已更新租户%s的所有记录的datasetId: %s", tenantId, newDatasetId)
return nil
}