fix: 1.修改HTTP机器人回调 2.修改HTTP卡片回调 3.追加知识库命中判断
This commit is contained in:
parent
99865c2bc4
commit
b104572e1b
|
|
@ -77,33 +77,23 @@ func (c *CallbackBiz) issueHandlingExtractContent(data chatbot.BotCallbackDataMo
|
||||||
}
|
}
|
||||||
// 解析 JSON 响应
|
// 解析 JSON 响应
|
||||||
var resp struct {
|
var resp struct {
|
||||||
Items []struct {
|
|
||||||
Question string `json:"question"`
|
Question string `json:"question"`
|
||||||
Answer string `json:"answer"`
|
Answer string `json:"answer"`
|
||||||
Confidence string `json:"confidence"`
|
Confidence string `json:"confidence"`
|
||||||
} `json:"items"`
|
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal([]byte(generateResp.Response), &resp); err != nil {
|
if err := json.Unmarshal([]byte(generateResp.Response), &resp); err != nil {
|
||||||
log.Errorf("解析 JSON 响应失败: %v", err)
|
log.Errorf("解析 JSON 响应失败: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.构建文本域内容
|
// 2.获取应用AppKey
|
||||||
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)
|
appKey, err := c.botConfigImpl.GetRobotAppKey(data.RobotCode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("获取应用配置失败: %v", err)
|
log.Errorf("获取应用配置失败: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4.创建并投放卡片
|
// 3.创建并投放卡片
|
||||||
outTrackId := constants.BuildCardOutTrackId(data.ConversationId, data.RobotCode) // 构建卡片 OutTrackId
|
outTrackId := constants.BuildCardOutTrackId(data.ConversationId, data.RobotCode) // 构建卡片 OutTrackId
|
||||||
_, err = c.dingtalkCardClient.CreateAndDeliver(
|
_, err = c.dingtalkCardClient.CreateAndDeliver(
|
||||||
appKey,
|
appKey,
|
||||||
|
|
@ -114,13 +104,14 @@ func (c *CallbackBiz) issueHandlingExtractContent(data chatbot.BotCallbackDataMo
|
||||||
CallbackRouteKey: tea.String(c.cfg.Dingtalk.Card.CallbackRouteKey),
|
CallbackRouteKey: tea.String(c.cfg.Dingtalk.Card.CallbackRouteKey),
|
||||||
CardData: &card_1_0.CreateAndDeliverRequestCardData{
|
CardData: &card_1_0.CreateAndDeliverRequestCardData{
|
||||||
CardParamMap: map[string]*string{
|
CardParamMap: map[string]*string{
|
||||||
|
"_CARD_DEBUG_TOOL_ENTRY": tea.String(c.cfg.Dingtalk.Card.DebugToolEntryShow), // 调试字段
|
||||||
"title": tea.String("QA知识收集"),
|
"title": tea.String("QA知识收集"),
|
||||||
"button_display": tea.String("true"),
|
"button_display": tea.String("true"),
|
||||||
"QA_details_now": tea.String(cardContent),
|
|
||||||
"textarea_display": tea.String("normal"),
|
"textarea_display": tea.String("normal"),
|
||||||
"action_id": tea.String("collect_qa"),
|
"action_id": tea.String("collect_qa"),
|
||||||
"tenant_id": tea.String(constants.KnowledgeTenantIdDefault),
|
"tenant_id": tea.String(constants.KnowledgeTenantIdDefault),
|
||||||
"_CARD_DEBUG_TOOL_ENTRY": tea.String(c.cfg.Dingtalk.Card.DebugToolEntryShow), // 调试字段
|
"question": tea.String(resp.Question),
|
||||||
|
"answer": tea.String(resp.Answer),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ImGroupOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{
|
ImGroupOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{
|
||||||
|
|
@ -223,15 +214,21 @@ func (c *CallbackBiz) issueHandlingQueryKnowledgeBase(data chatbot.BotCallbackDa
|
||||||
func (c *CallbackBiz) IssueHandlingCollectQA(data card.CardRequest) *card.CardResponse {
|
func (c *CallbackBiz) IssueHandlingCollectQA(data card.CardRequest) *card.CardResponse {
|
||||||
// 确认提交,文本写入知识库
|
// 确认提交,文本写入知识库
|
||||||
if data.CardActionData.CardPrivateData.Params["submit"] == "submit" {
|
if data.CardActionData.CardPrivateData.Params["submit"] == "submit" {
|
||||||
content := data.CardActionData.CardPrivateData.Params["QA_details"].(string)
|
question := data.CardActionData.CardPrivateData.Params["question_local"].(string)
|
||||||
|
answer := data.CardActionData.CardPrivateData.Params["answer_local"].(string)
|
||||||
tenantID := data.CardActionData.CardPrivateData.Params["tenant_id"].(string)
|
tenantID := data.CardActionData.CardPrivateData.Params["tenant_id"].(string)
|
||||||
|
|
||||||
// 协程执行耗时操作,防止阻塞
|
// 协程执行耗时操作,防止阻塞
|
||||||
util.SafeGo("inject_knowledge_base", func() {
|
util.SafeGo("inject_knowledge_base", func() {
|
||||||
knowledgeBase := knowledge_base.New(c.cfg.KnowledgeConfig)
|
knowledgeBase := knowledge_base.New(c.cfg.KnowledgeConfig)
|
||||||
err := knowledgeBase.IngestText(&knowledge_base.IngestTextRequest{
|
err := knowledgeBase.IngestBatchQA(&knowledge_base.IngestBacthQARequest{
|
||||||
TenantID: tenantID,
|
TenantID: tenantID,
|
||||||
Text: content,
|
QAList: []*knowledge_base.QA{
|
||||||
|
{
|
||||||
|
Question: question,
|
||||||
|
Answer: answer,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("注入知识库失败: %v", err)
|
log.Errorf("注入知识库失败: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -219,12 +219,16 @@ func (d *DingTalkBotBiz) handleKnowledgeQA(ctx context.Context, requireData *ent
|
||||||
log.Debugf("改写前后的Query: %s -> %s", requireData.Req.Text.Content, queryText)
|
log.Debugf("改写前后的Query: %s -> %s", requireData.Req.Text.Content, queryText)
|
||||||
|
|
||||||
// 获取知识库结果
|
// 获取知识库结果
|
||||||
isRetrieved, err := d.getKnowledgeAnswer(ctx, requireData, tenantId, queryText)
|
isRetrieved, responseContent, err := d.getKnowledgeAnswer(ctx, requireData.Ch, tenantId, queryText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if isRetrieved {
|
if isRetrieved {
|
||||||
return nil
|
// 过一遍 LLM 判断是否真的命中知识库
|
||||||
|
isRetrieved, err = d.handle.IsAnswerRelevant(ctx, queryText, responseContent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 未匹配&全局 -> 明确具体系统
|
// 未匹配&全局 -> 明确具体系统
|
||||||
|
|
@ -242,7 +246,7 @@ func (d *DingTalkBotBiz) handleKnowledgeQA(ctx context.Context, requireData *ent
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取知识库问答结果
|
// 获取知识库问答结果
|
||||||
func (d *DingTalkBotBiz) getKnowledgeAnswer(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, tenantId string, queryText string) (bool, error) {
|
func (d *DingTalkBotBiz) getKnowledgeAnswer(ctx context.Context, ch chan entitys.Response, tenantId string, queryText string) (bool, string, error) {
|
||||||
// 请求知识库工具
|
// 请求知识库工具
|
||||||
knowledgeBase := knowledge_base.New(d.conf.KnowledgeConfig)
|
knowledgeBase := knowledge_base.New(d.conf.KnowledgeConfig)
|
||||||
knowledgeResp, err := knowledgeBase.Query(&knowledge_base.QueryRequest{
|
knowledgeResp, err := knowledgeBase.Query(&knowledge_base.QueryRequest{
|
||||||
|
|
@ -254,11 +258,11 @@ func (d *DingTalkBotBiz) getKnowledgeAnswer(ctx context.Context, requireData *en
|
||||||
OnlyRAG: true,
|
OnlyRAG: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("请求知识库工具失败,err: %v", err)
|
return false, "", fmt.Errorf("请求知识库工具失败,err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取知识库SSE数据
|
// 读取知识库SSE数据
|
||||||
return d.groupConfigBiz.readKnowledgeSSE(knowledgeResp, requireData.Ch, true)
|
return d.groupConfigBiz.readKnowledgeSSE(knowledgeResp, ch, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
type resolveSystemAndIssueTypeResult struct {
|
type resolveSystemAndIssueTypeResult struct {
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ func (r *Handle) ClassifyIssueType(ctx context.Context, issueTypes []string, sys
|
||||||
- 当前输入未明确,但历史已有 → 继承历史类型
|
- 当前输入未明确,但历史已有 → 继承历史类型
|
||||||
- 当前输入未匹配,历史也没有 → 选择最接近的列表类型(尽量匹配意图)
|
- 当前输入未匹配,历史也没有 → 选择最接近的列表类型(尽量匹配意图)
|
||||||
- 除非是闲聊(如“你好”“在吗”),禁止返回空值
|
- 除非是闲聊(如“你好”“在吗”),禁止返回空值
|
||||||
- 除非明确是需求,否则禁止返回“开发需求”类型
|
- 除非明确是需求,否则禁止返回“开发需求”类型,疑问句式一定不能返回“开发需求”类型
|
||||||
|
|
||||||
3. 特殊规则
|
3. 特殊规则
|
||||||
- 当前输入只包含系统名/模块名/参数名 → 视为问题补充,继承历史 issue_type_name
|
- 当前输入只包含系统名/模块名/参数名 → 视为问题补充,继承历史 issue_type_name
|
||||||
|
|
@ -256,6 +256,56 @@ func (r *Handle) ClassifyIssueType(ctx context.Context, issueTypes []string, sys
|
||||||
return &result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IsAnswerRelevant struct {
|
||||||
|
Relevance string `json:"relevance"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断答案是否回答了问题
|
||||||
|
func (r *Handle) IsAnswerRelevant(ctx context.Context, question string, answer string) (bool, error) {
|
||||||
|
prompt := `## 角色
|
||||||
|
你是一个答案评估专家,你的任务是判断给定的答案是否真正回答了用户的问题。你必须严格分析语义、意图和信息覆盖情况,避免只看关键词。
|
||||||
|
|
||||||
|
## 输入
|
||||||
|
- question: %s
|
||||||
|
- answer: %s
|
||||||
|
|
||||||
|
## 判断逻辑
|
||||||
|
1. **直接回答**:答案明确提供了解决方案、步骤、结论或可执行信息 → 输出 True
|
||||||
|
2. **未回答**:答案仅泛泛提示、缺少关键步骤或信息,或者只是提供背景、登录信息等无关内容 → 输出 False
|
||||||
|
3. **部分回答**:答案提供了一部分可用信息,但未完全解决问题 → 输出 “Partial”
|
||||||
|
|
||||||
|
## 输出要求
|
||||||
|
输出严格 JSON 格式,只包含以下字段:
|
||||||
|
|
||||||
|
{
|
||||||
|
"relevance": "True / False / Partial",
|
||||||
|
"reason": "简要说明为什么答案被认为回答或未回答问题"
|
||||||
|
}`
|
||||||
|
resp, err := r.Ollama.Generation(ctx, fmt.Sprintf(prompt, question, answer))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试清理 JSON 内容(有时模型会返回 markdown 块)
|
||||||
|
resp = strings.TrimPrefix(resp, "```json")
|
||||||
|
resp = strings.TrimSuffix(resp, "```")
|
||||||
|
resp = strings.TrimSpace(resp)
|
||||||
|
|
||||||
|
var result IsAnswerRelevant
|
||||||
|
if err := json.Unmarshal([]byte(resp), &result); err != nil {
|
||||||
|
return false, fmt.Errorf("解析分类结果失败: %w, 原文: %s", err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("分析结果:%s,原因:%s", result.Relevance, result.Reason)
|
||||||
|
|
||||||
|
if result.Relevance == "True" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Handle) handleOtherTask(ctx context.Context, requireData *entitys.RequireData) (err error) {
|
func (r *Handle) handleOtherTask(ctx context.Context, requireData *entitys.RequireData) (err error) {
|
||||||
entitys.ResText(requireData.Ch, "", requireData.Match.Reasoning)
|
entitys.ResText(requireData.Ch, "", requireData.Match.Reasoning)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -507,7 +507,7 @@ func (g *GroupConfigBiz) handleKnowledge(ctx context.Context, rec *entitys.Recog
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取知识库SSE数据
|
// 读取知识库SSE数据
|
||||||
isRetrieved, err = g.readKnowledgeSSE(knowledgeResp, rec.Ch, true)
|
isRetrieved, _, err = g.readKnowledgeSSE(knowledgeResp, rec.Ch, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -522,16 +522,16 @@ func (g *GroupConfigBiz) handleKnowledge(ctx context.Context, rec *entitys.Recog
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取知识库 SSE 数据
|
// 读取知识库 SSE 数据
|
||||||
func (g *GroupConfigBiz) readKnowledgeSSE(resp io.ReadCloser, channel chan entitys.Response, useParagraphMode bool) (isRetrieved bool, err error) {
|
func (g *GroupConfigBiz) readKnowledgeSSE(resp io.ReadCloser, channel chan entitys.Response, useParagraphMode bool) (isRetrieved bool, allContent string, err error) {
|
||||||
scanner := bufio.NewScanner(resp)
|
scanner := bufio.NewScanner(resp)
|
||||||
var buffer strings.Builder
|
var buffer strings.Builder
|
||||||
|
var allContentBuilder strings.Builder
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
|
|
||||||
delta, done, err := knowledge_base.ParseOpenAIStreamData(line)
|
delta, done, err := knowledge_base.ParseOpenAIStreamData(line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("解析SSE数据失败: %w", err)
|
return false, "", fmt.Errorf("解析SSE数据失败: %w", err)
|
||||||
}
|
}
|
||||||
if done {
|
if done {
|
||||||
break
|
break
|
||||||
|
|
@ -544,7 +544,7 @@ func (g *GroupConfigBiz) readKnowledgeSSE(resp io.ReadCloser, channel chan entit
|
||||||
if delta.XRagStatus == constants.KnowledgeRagStatusMiss {
|
if delta.XRagStatus == constants.KnowledgeRagStatusMiss {
|
||||||
var missContent string = "知识库未检测到匹配信息。"
|
var missContent string = "知识库未检测到匹配信息。"
|
||||||
entitys.ResStream(channel, "", missContent)
|
entitys.ResStream(channel, "", missContent)
|
||||||
return false, nil
|
return false, missContent, nil
|
||||||
}
|
}
|
||||||
// 推理内容
|
// 推理内容
|
||||||
if delta.ReasoningContent != "" {
|
if delta.ReasoningContent != "" {
|
||||||
|
|
@ -555,6 +555,7 @@ func (g *GroupConfigBiz) readKnowledgeSSE(resp io.ReadCloser, channel chan entit
|
||||||
if delta.Content != "" && useParagraphMode {
|
if delta.Content != "" && useParagraphMode {
|
||||||
// 存入缓冲区
|
// 存入缓冲区
|
||||||
buffer.WriteString(delta.Content)
|
buffer.WriteString(delta.Content)
|
||||||
|
allContentBuilder.WriteString(delta.Content)
|
||||||
content := buffer.String()
|
content := buffer.String()
|
||||||
|
|
||||||
// 检查是否有换行符,按段落输出
|
// 检查是否有换行符,按段落输出
|
||||||
|
|
@ -572,10 +573,11 @@ func (g *GroupConfigBiz) readKnowledgeSSE(resp io.ReadCloser, channel chan entit
|
||||||
// 输出内容 - 逐字
|
// 输出内容 - 逐字
|
||||||
if delta.Content != "" && !useParagraphMode {
|
if delta.Content != "" && !useParagraphMode {
|
||||||
entitys.ResStream(channel, "", delta.Content)
|
entitys.ResStream(channel, "", delta.Content)
|
||||||
|
allContentBuilder.WriteString(delta.Content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return true, fmt.Errorf("读取SSE流中断: %w", err)
|
return true, "", fmt.Errorf("读取SSE流中断: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送缓冲区剩余内容(仅在段落模式下需要)
|
// 发送缓冲区剩余内容(仅在段落模式下需要)
|
||||||
|
|
@ -583,7 +585,7 @@ func (g *GroupConfigBiz) readKnowledgeSSE(resp io.ReadCloser, channel chan entit
|
||||||
entitys.ResStream(channel, "", buffer.String())
|
entitys.ResStream(channel, "", buffer.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, allContentBuilder.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 询问是否创建群聊处理问题
|
// 询问是否创建群聊处理问题
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,19 @@ func (r *OllamaService) Chat(ctx context.Context, messages []api.Message) (strin
|
||||||
return res.Message.Content, nil
|
return res.Message.Content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *OllamaService) Generation(ctx context.Context, prompt string) (string, error) {
|
||||||
|
res, err := r.client.Generation(ctx, &api.GenerateRequest{
|
||||||
|
Model: r.config.Ollama.GenerateModel,
|
||||||
|
Stream: new(bool),
|
||||||
|
Prompt: prompt,
|
||||||
|
Think: &api.ThinkValue{Value: false},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return res.Response, nil
|
||||||
|
}
|
||||||
|
|
||||||
//func (r *OllamaService) RecognizeWithImg(ctx context.Context, imgByte []api.ImageData, ch chan entitys.Response) (desc api.GenerateResponse, err error) {
|
//func (r *OllamaService) RecognizeWithImg(ctx context.Context, imgByte []api.ImageData, ch chan entitys.Response) (desc api.GenerateResponse, err error) {
|
||||||
// if imgByte == nil {
|
// if imgByte == nil {
|
||||||
// return
|
// return
|
||||||
|
|
|
||||||
|
|
@ -126,8 +126,7 @@ const IssueHandlingExtractContentPrompt string = `你是一个【问题与答案
|
||||||
|
|
||||||
当用户输入为【多条群聊聊天记录】时:
|
当用户输入为【多条群聊聊天记录】时:
|
||||||
- 结合问题主题,判断聊天记录中正在讨论或试图解决的问题
|
- 结合问题主题,判断聊天记录中正在讨论或试图解决的问题
|
||||||
- 一个群聊中可能包含多个相互独立的问题,但它们都围绕着一个主题,一般为用户提出的第一个问题,尽可能总结为一个问题
|
- 一个群聊中可能包含多个相互独立的问题,但它们都围绕着一个主题,一般为用户提出的第一个问题。尽可能总结为一个问题、一个答案
|
||||||
- 若确实问题很独立,需要分别识别,对每个问题,整理出清晰、可复用的“问题描述”和“对应答案”
|
|
||||||
|
|
||||||
生成答案时的原则:
|
生成答案时的原则:
|
||||||
- 答案必须来源于聊天内容中已经给出的信息或共识
|
- 答案必须来源于聊天内容中已经给出的信息或共识
|
||||||
|
|
@ -141,24 +140,19 @@ const IssueHandlingExtractContentPrompt string = `你是一个【问题与答案
|
||||||
- JSON 结构必须严格符合以下约定
|
- JSON 结构必须严格符合以下约定
|
||||||
|
|
||||||
JSON 结构约定:
|
JSON 结构约定:
|
||||||
{
|
|
||||||
"items": [
|
|
||||||
{
|
{
|
||||||
"question": "清晰、独立、可复用的问题描述",
|
"question": "清晰、独立、可复用的问题描述",
|
||||||
"answer": "基于聊天内容整理出的答案;如无结论则为“暂无明确结论”",
|
"answer": "基于聊天内容整理出的答案;如无结论则为“暂无明确结论”",
|
||||||
"confidence": "high | medium | low"
|
"confidence": "high | medium | low"
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
字段说明:
|
字段说明:
|
||||||
- items:问题与答案列表;若未识别到有效问题,则返回空数组 []
|
|
||||||
- question:抽象后的标准问题表述,不包含具体聊天语句
|
- question:抽象后的标准问题表述,不包含具体聊天语句
|
||||||
- answer:整理后的答案,不得引入聊天之外的信息
|
- answer:整理后的答案,不得引入聊天之外的信息
|
||||||
- confidence:根据聊天中信息的一致性和明确程度给出判断
|
- confidence:根据聊天中信息的一致性和明确程度给出判断
|
||||||
|
|
||||||
如果无法从输入中识别出任何有效问题,返回:
|
如果无法从输入中识别出任何有效问题,返回:
|
||||||
{ "items": [] }
|
{ "confidence": "low" }
|
||||||
|
|
||||||
用户输入:
|
用户输入:
|
||||||
%s
|
%s
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package knowledge_base
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/config"
|
"ai_scheduler/internal/config"
|
||||||
"ai_scheduler/internal/pkg/l_request"
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -110,3 +111,53 @@ func (c *Client) IngestText(req *IngestTextRequest) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IngestBatchQA 向知识库中注入问答对
|
||||||
|
func (c *Client) IngestBatchQA(req *IngestBacthQARequest) error {
|
||||||
|
if req == nil {
|
||||||
|
return fmt.Errorf("req is nil")
|
||||||
|
}
|
||||||
|
if req.TenantID == "" {
|
||||||
|
return fmt.Errorf("tenantID is empty")
|
||||||
|
}
|
||||||
|
for _, item := range req.QAList {
|
||||||
|
if item.Question == "" {
|
||||||
|
return fmt.Errorf("question is empty")
|
||||||
|
}
|
||||||
|
if item.Answer == "" {
|
||||||
|
return fmt.Errorf("answer is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data := []map[string]string{}
|
||||||
|
for _, item := range req.QAList {
|
||||||
|
data = append(data, map[string]string{
|
||||||
|
"question": item.Question,
|
||||||
|
"answer": item.Answer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
jsonByte, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
baseURL := strings.TrimRight(c.cfg.BaseURL, "/")
|
||||||
|
|
||||||
|
rsp, err := (&l_request.Request{
|
||||||
|
Method: "POST",
|
||||||
|
Url: baseURL + "/ingest/batch_qa",
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Tenant-ID": req.TenantID,
|
||||||
|
},
|
||||||
|
JsonByte: jsonByte,
|
||||||
|
}).Send()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("knowledge base returned status %d: %s", rsp.StatusCode, rsp.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,13 @@ type IngestTextRequest struct {
|
||||||
TenantID string // 租户 ID
|
TenantID string // 租户 ID
|
||||||
Text string // 要注入的文本内容
|
Text string // 要注入的文本内容
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IngestBacthQARequest struct {
|
||||||
|
TenantID string // 租户 ID
|
||||||
|
QAList []*QA // 问答对列表
|
||||||
|
}
|
||||||
|
|
||||||
|
type QA struct {
|
||||||
|
Question string // 问题
|
||||||
|
Answer string // 答案
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue