179 lines
5.7 KiB
Go
179 lines
5.7 KiB
Go
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())
|
||
}
|