ai_scheduler/internal/biz/advice_file.go

179 lines
5.7 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"
dbmodel "ai_scheduler/internal/data/model"
"ai_scheduler/internal/data/mongo_model"
"ai_scheduler/internal/entitys"
"ai_scheduler/internal/pkg"
"context"
"encoding/json"
"fmt"
"os"
"strings"
"time"
)
type AdviceFileBiz struct {
openai *third_party.OpenAi
}
func NewAdviceFileBiz(openai *third_party.OpenAi) *AdviceFileBiz {
return &AdviceFileBiz{
openai: openai,
}
}
var DataMap = map[string]mongo_model.AdviceData{
"dialectFeatures": &mongo_model.DialectFeatures{},
"sentencePatterns": &mongo_model.SentencePatterns{},
"personalityTags": &mongo_model.PersonalityTags{},
"toneTags": &mongo_model.ToneTags{},
"signatureDialogues": &mongo_model.SignatureDialogues{},
"regionValue": &mongo_model.RegionValue{},
"competitionComparison": &mongo_model.CompetitionComparison{},
"coreSellingPoints": &mongo_model.CoreSellingPoints{},
"supportingFacilities": &mongo_model.SupportingFacilities{},
"developerBacking": &mongo_model.DeveloperBacking{},
"needsMining": &mongo_model.NeedsMining{},
"painPointResponse": &mongo_model.PainPointResponse{},
"valueBuilding": &mongo_model.ValueBuilding{},
"closingTechniques": &mongo_model.ClosingTechniques{},
"communicationRhythm": &mongo_model.CommunicationRhythm{},
"customer": &mongo_model.Customer{},
}
func (a *AdviceFileBiz) WordAna(ctx context.Context, wordContent string, projectInfo *entitys.AdvicerProjectInfoRes) (map[mongo_model.AdviceRole]map[string]mongo_model.AdviceData, error) {
if len(projectInfo.ModelInfo.FileModel) == 0 {
return nil, fmt.Errorf("项目文件模型信息缺失")
}
timeSte := time.Now().Format("200601021504")
dir := "./cache/" + timeSte
//缓存文件仅用于排查问题,写入失败不影响主流程
_ = os.MkdirAll(dir, 0755)
//获取示例
examples := a.getAllExamples()
//构建提示词
prompt := a.buildSimplePrompt(wordContent, examples)
_ = os.WriteFile(dir+"/requset.json", []byte(prompt), 0644)
//llm提取信息
anaContent, err := a.callLlm2(ctx, prompt, &projectInfo.ModelInfo)
if err != nil {
return nil, err
}
_ = os.WriteFile(dir+"/res.json", []byte(anaContent), 0644)
//格式整理
data, err := a.parseResponse(ctx, []byte(anaContent))
if err != nil {
return nil, err
}
//组装数据
resData := a.cateData(data)
_ = os.WriteFile(dir+"/extracted.json", pkg.JsonByteIgonErr(resData), 0644)
return resData, nil
}
func (a *AdviceFileBiz) cateData(data map[string]mongo_model.AdviceData) map[mongo_model.AdviceRole]map[string]mongo_model.AdviceData {
var res = make(map[mongo_model.AdviceRole]map[string]mongo_model.AdviceData)
for k, v := range data {
if _, ok := res[v.Role()]; !ok {
res[v.Role()] = make(map[string]mongo_model.AdviceData)
}
res[v.Role()][k] = v
}
return res
}
func (a *AdviceFileBiz) parseResponse(ctx context.Context, responseByte []byte) (resultOutPut map[string]mongo_model.AdviceData, err error) {
//只尝试修复一次
//if isValid := json.Valid(responseByte); !isValid {
//
// if err != nil {
// return nil, fmt.Errorf("json格式错误,修复失败:%s", err.Error())
// }
//}
if isValid := json.Valid(responseByte); !isValid {
return nil, fmt.Errorf("json格式错误")
}
var (
result map[string]interface{}
)
resultOutPut = make(map[string]mongo_model.AdviceData)
if err = json.Unmarshal(responseByte, &result); err != nil {
return
}
for k, v := range result {
//跳过预定义之外的字段,避免模型输出的额外说明导致整体解析中断
if _, ok := DataMap[k]; !ok {
continue
}
var vbyte []byte
if vbyte, err = json.Marshal(v); err != nil {
return
}
newData := DataMap[k].Copy()
if err = json.Unmarshal(vbyte, newData); err != nil {
err = fmt.Errorf("字段%s解析失败: %w", k, err)
return
}
resultOutPut[k] = newData
}
if len(resultOutPut) == 0 {
err = fmt.Errorf("响应中未包含有效数据")
return
}
return
}
// callLlm2 调用Responses接口提取文件信息
// 走json_object格式输出,保证返回内容可直接反序列化
func (a *AdviceFileBiz) callLlm2(ctx context.Context, prompt string, modelInfo *dbmodel.AiAdviceModelSup) (string, error) {
content, err := a.openai.RequestResponsesJson(ctx, modelInfo.Key, modelInfo.URL, modelInfo.FileModel, prompt)
if err != nil {
return "", fmt.Errorf("文件分析模型调用失败: %w", err)
}
if strings.TrimSpace(content) == "" {
return "", fmt.Errorf("文件分析模型返回内容为空")
}
return content, nil
}
func (a *AdviceFileBiz) getAllExamples() map[string]mongo_model.AdviceData {
return DataMap
}
func (a *AdviceFileBiz) buildSimplePrompt(wordContent string, examples map[string]mongo_model.AdviceData) string {
// 最简单的提示词模板
template := `分析以下房地产销售对话,按指定格式提取信息:
对话内容:
%s
请按照以下` + fmt.Sprintf("%d", len(examples)) + `个格式生成JSON数据,key为格式名称,value为对应值:
%s
输出要求:
1. 所有内容必须严格基于提供的对话原文,不得编造(重要!)
2. 每个结构体一个JSON对象
3. 严格按照示例格式
4. 将上述生成的` + fmt.Sprintf("%d", len(examples)) + `个JSON对象,json不需要有可读性,不要有特殊符号,比如"\n",用map[string]json来包裹所有json对象:{"SupportingFacilities":{...},"SignatureDialogues":[{...},{...}]}`
// 构建格式部分
var formats strings.Builder
for name, example := range examples {
formats.WriteString(fmt.Sprintf("=== %s (%s:%s)===\n示例:%s\n\n", name, mongo_model.RoleDesc[example.Role()], example.Desc(), example.Example()))
}
return fmt.Sprintf(template, wordContent, formats.String())
}