291 lines
9.4 KiB
Go
291 lines
9.4 KiB
Go
package biz
|
||
|
||
import (
|
||
"ai_scheduler/internal/config"
|
||
"ai_scheduler/internal/data/constants"
|
||
"ai_scheduler/internal/data/impl"
|
||
"ai_scheduler/internal/domain/tools/common/knowledge_base"
|
||
"ai_scheduler/internal/pkg/dingtalk"
|
||
"ai_scheduler/internal/pkg/util"
|
||
"ai_scheduler/internal/pkg/utils_ollama"
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"strings"
|
||
|
||
"gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/card"
|
||
"gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot"
|
||
"github.com/alibabacloud-go/dingtalk/card_1_0"
|
||
"github.com/alibabacloud-go/tea/tea"
|
||
"github.com/gofiber/fiber/v2/log"
|
||
"github.com/ollama/ollama/api"
|
||
)
|
||
|
||
type CallbackBiz struct {
|
||
cfg *config.Config
|
||
ollamaClient *utils_ollama.Client
|
||
dingtalkCardClient *dingtalk.CardClient
|
||
botConfigImpl *impl.BotConfigImpl
|
||
}
|
||
|
||
func NewCallbackBiz(
|
||
cfg *config.Config,
|
||
ollamaClient *utils_ollama.Client,
|
||
dingtalkCardClient *dingtalk.CardClient,
|
||
botConfigImpl *impl.BotConfigImpl,
|
||
) *CallbackBiz {
|
||
return &CallbackBiz{
|
||
cfg: cfg,
|
||
ollamaClient: ollamaClient,
|
||
dingtalkCardClient: dingtalkCardClient,
|
||
botConfigImpl: botConfigImpl,
|
||
}
|
||
}
|
||
|
||
// IssueHandlingGroup 问题处理群机器人回调
|
||
// 能力1: 通过[内容提取] 宏,分析用户QA问题,调出QA表单卡片
|
||
// 能力2: 通过[QA收集] 宏,收集用户反馈,写入知识库
|
||
// 能力3: 通过[知识库查询] 宏,查询知识库,返回答案
|
||
func (c *CallbackBiz) IssueHandlingGroup(data chatbot.BotCallbackDataModel) error {
|
||
// 能力1、2:分析用户QA问题,写入知识库
|
||
if strings.Contains(data.Text.Content, "[内容提取]") || strings.Contains(data.Text.Content, "[QA收集]") {
|
||
c.issueHandlingExtractContent(data)
|
||
}
|
||
// 能力3:查询知识库,返回答案
|
||
if strings.Contains(data.Text.Content, "[知识库查询]") {
|
||
c.issueHandlingQueryKnowledgeBase(data)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// 问题处理群机器人内容提取
|
||
func (c *CallbackBiz) issueHandlingExtractContent(data chatbot.BotCallbackDataModel) {
|
||
// 1.提取用户输入
|
||
prompt := fmt.Sprintf(constants.IssueHandlingExtractContentPrompt, data.Text.Content)
|
||
log.Infof("问题提取提示词: %s", prompt)
|
||
// LLM 提取
|
||
generateResp, err := c.ollamaClient.Generation(context.Background(), &api.GenerateRequest{
|
||
Model: c.cfg.Ollama.GenerateModel,
|
||
Prompt: prompt,
|
||
Stream: util.AnyToPoint(false),
|
||
})
|
||
if err != nil {
|
||
log.Errorf("问题提取失败: %v", err)
|
||
return
|
||
}
|
||
// 解析 JSON 响应
|
||
var resp struct {
|
||
Items []struct {
|
||
Question string `json:"question"`
|
||
Answer string `json:"answer"`
|
||
Confidence string `json:"confidence"`
|
||
} `json:"items"`
|
||
}
|
||
if err := json.Unmarshal([]byte(generateResp.Response), &resp); err != nil {
|
||
log.Errorf("解析 JSON 响应失败: %v", err)
|
||
return
|
||
}
|
||
|
||
// 2.构建文本域内容
|
||
cardContentTpl := "问题:%s \n答案:%s"
|
||
var cardContentList []string
|
||
for _, item := range resp.Items {
|
||
cardContentList = append(cardContentList, fmt.Sprintf(cardContentTpl, item.Question, item.Answer))
|
||
}
|
||
cardContent := strings.Join(cardContentList, "\n\n")
|
||
|
||
// 3.获取应用AppKey
|
||
appKey, err := c.botConfigImpl.GetRobotAppKey(data.RobotCode)
|
||
if err != nil {
|
||
log.Errorf("获取应用配置失败: %v", err)
|
||
return
|
||
}
|
||
|
||
// 4.创建并投放卡片
|
||
outTrackId := constants.BuildCardOutTrackId(data.ConversationId, data.RobotCode) // 构建卡片 OutTrackId
|
||
_, err = c.dingtalkCardClient.CreateAndDeliver(
|
||
appKey,
|
||
&card_1_0.CreateAndDeliverRequest{
|
||
CardTemplateId: tea.String(c.cfg.Dingtalk.Card.Template.ContentCollect),
|
||
OutTrackId: tea.String(outTrackId),
|
||
CallbackType: tea.String("HTTP"),
|
||
CallbackRouteKey: tea.String(c.cfg.Dingtalk.Card.CallbackRouteKey),
|
||
CardData: &card_1_0.CreateAndDeliverRequestCardData{
|
||
CardParamMap: map[string]*string{
|
||
"title": tea.String("QA知识收集"),
|
||
"button_display": tea.String("true"),
|
||
"QA_details_now": tea.String(cardContent),
|
||
"textarea_display": tea.String("normal"),
|
||
"action_id": tea.String("collect_qa"),
|
||
"tenant_id": tea.String(constants.KnowledgeTenantIdDefault),
|
||
"_CARD_DEBUG_TOOL_ENTRY": tea.String(c.cfg.Dingtalk.Card.DebugToolEntryShow), // 调试字段
|
||
},
|
||
},
|
||
ImGroupOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{
|
||
SupportForward: tea.Bool(false),
|
||
},
|
||
OpenSpaceId: tea.String("dtv1.card//im_group." + data.ConversationId),
|
||
ImGroupOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{
|
||
RobotCode: tea.String(c.cfg.Dingtalk.SceneGroup.GroupTemplateRobotIDIssueHandling),
|
||
},
|
||
},
|
||
)
|
||
|
||
}
|
||
|
||
// 问题处理群机器人查询知识库
|
||
func (c *CallbackBiz) issueHandlingQueryKnowledgeBase(data chatbot.BotCallbackDataModel) {
|
||
// 获取应用配置
|
||
appKey, err := c.botConfigImpl.GetRobotAppKey(data.RobotCode)
|
||
if err != nil {
|
||
log.Errorf("应用机器人配置不存在: %s, err: %v", data.RobotCode, err)
|
||
return
|
||
}
|
||
// 创建卡片
|
||
outTrackId := constants.BuildCardOutTrackId(data.ConversationId, data.RobotCode)
|
||
_, err = c.dingtalkCardClient.CreateAndDeliver(
|
||
appKey,
|
||
&card_1_0.CreateAndDeliverRequest{
|
||
CardTemplateId: tea.String(c.cfg.Dingtalk.Card.Template.BaseMsg),
|
||
CardData: &card_1_0.CreateAndDeliverRequestCardData{
|
||
CardParamMap: map[string]*string{
|
||
"title": tea.String(data.Text.Content),
|
||
"markdown": tea.String("知识库检索中..."),
|
||
},
|
||
},
|
||
OutTrackId: tea.String(outTrackId),
|
||
ImGroupOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{
|
||
SupportForward: tea.Bool(false),
|
||
},
|
||
OpenSpaceId: tea.String("dtv1.card//im_group." + data.ConversationId),
|
||
ImGroupOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{
|
||
RobotCode: tea.String(data.RobotCode),
|
||
},
|
||
},
|
||
)
|
||
|
||
// 查询知识库
|
||
knowledgeBase := knowledge_base.New(c.cfg.KnowledgeConfig)
|
||
knowledgeResp, err := knowledgeBase.Query(&knowledge_base.QueryRequest{
|
||
TenantID: constants.KnowledgeTenantIdDefault,
|
||
Query: data.Text.Content,
|
||
Mode: constants.KnowledgeModeMix,
|
||
Stream: false,
|
||
Think: false,
|
||
OnlyRAG: true,
|
||
})
|
||
if err != nil {
|
||
log.Errorf("查询知识库失败: %v", err)
|
||
return
|
||
}
|
||
knowledgeRespBytes, err := io.ReadAll(knowledgeResp)
|
||
if err != nil {
|
||
log.Errorf("读取知识库响应失败: %v", err)
|
||
return
|
||
}
|
||
|
||
// 卡片更新
|
||
message, isRetrieved, err := knowledge_base.ParseOpenAIHTTPData(string(knowledgeRespBytes))
|
||
if err != nil {
|
||
log.Errorf("读取知识库 SSE 数据失败: %v", err)
|
||
return
|
||
}
|
||
|
||
content := message.Content
|
||
if !isRetrieved {
|
||
content = "知识库未检测到匹配信息,请核查知识库数据是否正确。"
|
||
}
|
||
|
||
// 卡片更新
|
||
_, err = c.dingtalkCardClient.UpdateCard(
|
||
appKey,
|
||
&card_1_0.UpdateCardRequest{
|
||
OutTrackId: tea.String(outTrackId),
|
||
CardData: &card_1_0.UpdateCardRequestCardData{
|
||
CardParamMap: map[string]*string{
|
||
"markdown": tea.String(content),
|
||
},
|
||
},
|
||
CardUpdateOptions: &card_1_0.UpdateCardRequestCardUpdateOptions{
|
||
UpdateCardDataByKey: tea.Bool(true),
|
||
},
|
||
},
|
||
)
|
||
if err != nil {
|
||
log.Errorf("更新卡片失败: %v", err)
|
||
return
|
||
}
|
||
}
|
||
|
||
// IssueHandlingCollectQA 问题处理群机器人 QA 收集回调
|
||
func (c *CallbackBiz) IssueHandlingCollectQA(data card.CardRequest) *card.CardResponse {
|
||
// 确认提交,文本写入知识库
|
||
if data.CardActionData.CardPrivateData.Params["submit"] == "submit" {
|
||
content := data.CardActionData.CardPrivateData.Params["QA_details"].(string)
|
||
tenantID := data.CardActionData.CardPrivateData.Params["tenant_id"].(string)
|
||
|
||
// 协程执行耗时操作,防止阻塞
|
||
util.SafeGo("inject_knowledge_base", func() {
|
||
knowledgeBase := knowledge_base.New(c.cfg.KnowledgeConfig)
|
||
err := knowledgeBase.IngestText(&knowledge_base.IngestTextRequest{
|
||
TenantID: tenantID,
|
||
Text: content,
|
||
})
|
||
if err != nil {
|
||
log.Errorf("注入知识库失败: %v", err)
|
||
} else {
|
||
log.Infof("注入知识库成功: tenantID=%s", tenantID)
|
||
}
|
||
|
||
// 解析当前卡片的 ConversationId 和 robotCode
|
||
conversationId, robotCode := constants.ParseCardOutTrackId(data.OutTrackId)
|
||
|
||
// 获取应用配置
|
||
appKey, err := c.botConfigImpl.GetRobotAppKey(robotCode)
|
||
if err != nil {
|
||
log.Errorf("获取应用机器人配置失败: %v", err)
|
||
return
|
||
}
|
||
|
||
// 发送卡片通知用户注入成功
|
||
outTrackId := constants.BuildCardOutTrackId(conversationId, robotCode)
|
||
c.dingtalkCardClient.CreateAndDeliver(
|
||
appKey,
|
||
&card_1_0.CreateAndDeliverRequest{
|
||
CardTemplateId: tea.String(c.cfg.Dingtalk.Card.Template.BaseMsg),
|
||
OutTrackId: tea.String(outTrackId),
|
||
CardData: &card_1_0.CreateAndDeliverRequestCardData{
|
||
CardParamMap: map[string]*string{
|
||
"title": tea.String("QA知识收集结果"),
|
||
"markdown": tea.String("[Get] **成功**"),
|
||
},
|
||
},
|
||
ImGroupOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{
|
||
SupportForward: tea.Bool(false),
|
||
},
|
||
OpenSpaceId: tea.String("dtv1.card//im_group." + conversationId),
|
||
ImGroupOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{
|
||
RobotCode: tea.String(robotCode),
|
||
},
|
||
},
|
||
)
|
||
})
|
||
}
|
||
|
||
// 取消提交,禁用输入框
|
||
resp := &card.CardResponse{
|
||
CardUpdateOptions: &card.CardUpdateOptions{
|
||
UpdateCardDataByKey: true,
|
||
},
|
||
CardData: &card.CardDataDto{
|
||
CardParamMap: map[string]string{
|
||
"textarea_display": "disabled",
|
||
},
|
||
},
|
||
}
|
||
|
||
return resp
|
||
}
|