ai_scheduler/internal/biz/advice_skill.go

264 lines
7.4 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 biz
import (
"ai_scheduler/internal/biz/llm_service/third_party"
"ai_scheduler/internal/data/constants"
"ai_scheduler/internal/data/mongo_model"
"ai_scheduler/internal/entitys"
"ai_scheduler/internal/pkg"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"context"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type AdviceSkillBiz struct {
AdvicerTalkSkillMongo *mongo_model.AdvicerTalkSkillMongo
mongo *pkg.Mongo
adviceProjectBiz *AdviceProjectBiz
}
func NewAdviceSkillBiz(
advicerTalkSkillMongo *mongo_model.AdvicerTalkSkillMongo,
mongo *pkg.Mongo,
adviceProjectBiz *AdviceProjectBiz,
) *AdviceSkillBiz {
return &AdviceSkillBiz{
AdvicerTalkSkillMongo: advicerTalkSkillMongo,
mongo: mongo,
adviceProjectBiz: adviceProjectBiz,
}
}
func (a *AdviceSkillBiz) VersionAdd(ctx context.Context, param *entitys.AdvicerTalkSkillAddReq) (id interface{}, err error) {
res, err := a.mongo.Co(a.AdvicerTalkSkillMongo).InsertOne(ctx, &mongo_model.AdvicerTalkSkillMongo{
ProjectId: param.ProjectId,
Desc: param.Desc,
Data: param.Data,
NeedsMining: param.NeedsMining,
PainPointResponse: param.PainPointResponse,
ValueBuilding: param.ValueBuilding,
ClosingTechniques: param.ClosingTechniques,
CommunicationRhythm: param.CommunicationRhythm,
LastUpdateTime: time.Now(),
})
if err != nil {
return nil, err
}
return res.InsertedID, err
}
func (a *AdviceSkillBiz) VersionUpdate(ctx context.Context, param *entitys.AdvicerTalkSkillUpdateReq) (err error) {
filter := bson.M{}
if len(param.Id) == 0 {
return errors.New("ID不能为空")
}
objectID, err := primitive.ObjectIDFromHex(param.Id)
if err != nil {
return fmt.Errorf("ID转换失败: %w", err)
}
filter["_id"] = objectID
update := bson.M{
"$set": &mongo_model.AdvicerTalkSkillMongo{
ProjectId: param.ProjectId,
Desc: param.Desc,
Data: param.Data,
NeedsMining: param.NeedsMining,
PainPointResponse: param.PainPointResponse,
ValueBuilding: param.ValueBuilding,
ClosingTechniques: param.ClosingTechniques,
CommunicationRhythm: param.CommunicationRhythm,
LastUpdateTime: time.Now(),
},
}
res := a.mongo.Co(a.AdvicerTalkSkillMongo).FindOneAndUpdate(ctx, filter, update)
return res.Err()
}
func (a *AdviceSkillBiz) VersionList(ctx context.Context, param *entitys.AdvicerTalkSkillListReq) (list []mongo_model.AdvicerTalkSkillItem, err error) {
filter := bson.M{}
if param.ProjectId != 0 {
filter["projectId"] = param.ProjectId
}
// 2. _id 条件
if len(param.Id) != 0 {
objectID, err := primitive.ObjectIDFromHex(param.Id)
if err != nil {
return nil, fmt.Errorf("ID转换失败: %w", err)
}
filter["_id"] = objectID
}
// 3. version_desc 模糊查询
if len(param.Desc) != 0 {
// 正确的方式:指定字段名
filter["desc"] = bson.M{
"$regex": primitive.Regex{
Pattern: param.Desc,
Options: "i",
},
}
}
cursor, err := a.mongo.Co(a.AdvicerTalkSkillMongo).Find(ctx, filter)
if err != nil {
return nil, err
}
// 遍历结果
for cursor.Next(ctx) {
var advicerVersion mongo_model.AdvicerTalkSkillItem
if err := cursor.Decode(&advicerVersion); err != nil {
return nil, err
}
fillSkillData(&advicerVersion.AdvicerTalkSkillMongo)
list = append(list, advicerVersion)
}
if err := cursor.Err(); err != nil {
return nil, err
}
return list, err
}
func (a *AdviceSkillBiz) VersionDel(ctx context.Context, param *entitys.AdvicerTalkSkillDelReq) (err error) {
filter := bson.M{}
if len(param.Id) != 0 {
objectID, err := primitive.ObjectIDFromHex(param.Id)
if err != nil {
return fmt.Errorf("ID转换失败: %w", err)
}
filter["_id"] = objectID
}
_, err = a.mongo.Co(a.AdvicerTalkSkillMongo).DeleteOne(ctx, filter)
return err
}
func (a *AdviceSkillBiz) Info(ctx context.Context, param *entitys.AdvicerTalkSkillInfoReq) (info mongo_model.AdvicerTalkSkillMongo, err error) {
filter := bson.M{}
if len(param.Id) != 0 {
objectID, err := primitive.ObjectIDFromHex(param.Id)
if err != nil {
return info, fmt.Errorf("ID转换失败: %w", err)
}
filter["_id"] = objectID
}
res := a.mongo.Co(a.AdvicerTalkSkillMongo).FindOne(ctx, filter)
if res.Err() != nil {
return info, err
}
// 遍历结果
if err = res.Decode(&info); err != nil {
return info, err
}
// 向后兼容:旧记录无 Data 字段时,从类型化字段转换
fillSkillData(&info)
return info, nil
}
// SkillGenerate AI 生成聊天技巧:基于项目信息 + 项目资料调用 LLM 生成 5 维度
func (a *AdviceSkillBiz) SkillGenerate(ctx context.Context, req *entitys.AdvicerSkillGenerateReq) (map[string]interface{}, error) {
// 1. 查询项目基础信息
baseInfo, err := a.adviceProjectBiz.BaseInfo(req.ProjectId)
if err != nil {
return nil, fmt.Errorf("查询项目信息失败: %w", err)
}
if baseInfo.ProjectID == 0 {
return nil, fmt.Errorf("项目不存在")
}
// 2. 确定模型配置
supId := req.SupId
if supId == 0 {
supId = baseInfo.ModelSupID
}
if supId == 0 {
return nil, fmt.Errorf("项目未配置模型,请手动选择模型配置")
}
sup, err := a.adviceProjectBiz.ModelInfo(supId)
if err != nil || sup.SupID == 0 {
return nil, fmt.Errorf("指定的模型配置不存在")
}
// 3. 查询项目资料(可为空)
projectData, _ := a.adviceProjectBiz.ProjectDataLoad(ctx, req.ProjectId)
projectDataStr := "暂无项目资料"
if len(projectData.Data) > 0 {
dataBytes, _ := json.Marshal(projectData.Data)
projectDataStr = string(dataBytes)
}
// 4. 构建提示词
prompt := constants.PromptSkillGenerate
prompt = strings.ReplaceAll(prompt, "{{项目名称}}", baseInfo.Name)
desc := baseInfo.TemplateDesc
if desc == "" {
desc = baseInfo.TemplateAdvicerDesc
}
prompt = strings.ReplaceAll(prompt, "{{项目描述}}", desc)
prompt = strings.ReplaceAll(prompt, "{{项目资料}}", projectDataStr)
// 5. 调用 LLM
result, err := third_party.NewOpenAi().RequestResponsesJson(ctx, sup.Key, sup.URL, sup.JSONModel, prompt)
if err != nil {
return nil, fmt.Errorf("AI 生成失败: %w", err)
}
// 6. 解析返回的 JSON
var parsed map[string]interface{}
if err := json.Unmarshal([]byte(result), &parsed); err != nil {
return nil, fmt.Errorf("解析 AI 返回结果失败: %w", err)
}
// 7. 提取 5 个维度(中文 key)
resp := make(map[string]interface{})
for _, key := range []string{"需求挖掘", "痛点应对", "价值塑造", "促单话术", "沟通节奏"} {
if v, ok := parsed[key]; ok {
resp[key] = v
}
}
if len(resp) == 0 {
return nil, fmt.Errorf("AI 未返回有效的聊天技巧数据")
}
return resp, nil
}
// fillSkillData 向后兼容:旧记录无 Data 字段时,从类型化字段转换到 Data
func fillSkillData(info *mongo_model.AdvicerTalkSkillMongo) {
if len(info.Data) > 0 {
return
}
info.Data = make(map[string]interface{})
if len(info.NeedsMining) > 0 {
info.Data["需求挖掘"] = info.NeedsMining
}
if len(info.PainPointResponse) > 0 {
info.Data["痛点应对"] = info.PainPointResponse
}
if len(info.ValueBuilding) > 0 {
info.Data["价值塑造"] = info.ValueBuilding
}
if len(info.ClosingTechniques) > 0 {
info.Data["促单话术"] = info.ClosingTechniques
}
if len(info.CommunicationRhythm) > 0 {
info.Data["沟通节奏"] = info.CommunicationRhythm
}
}