| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409 |
- // Package controllers - chat.go
- //
- // ⚠️ DEPRECATED NOTICE (弃用说明)
- // ================================================================================
- // 本文件中的AI对话核心功能已迁移至微服务实现。
- // 当前保留此文件是因为前端部分接口仍依赖这里的路由定义。
- //
- // 迁移状态:
- // - AI对话核心逻辑: ✅ 已迁移至微服务
- // - 辅助接口(历史记录、推荐问题等): ⚠️ 仍在使用中
- //
- // TODO: 待前端完全切换到微服务后,可以移除本文件中已弃用的方法
- // ================================================================================
- package controllers
- import (
- "bufio"
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "regexp"
- "shudao-chat-go/models"
- "shudao-chat-go/utils"
- "strings"
- "time"
- "github.com/beego/beego/v2/server/web"
- )
- type ChatController struct {
- web.Controller
- }
- // 阿里大模型聊天函数
- func (c *ChatController) sendQwen3Message(userMessage string, useStream bool) (string, error) {
- // 从Beego配置读取阿里大模型配置
- apiURL, err := web.AppConfig.String("qwen3_api_url")
- if err != nil || apiURL == "" {
- return "", fmt.Errorf("配置文件中未找到qwen3_api_url")
- }
- model, err := web.AppConfig.String("qwen3_model")
- if err != nil || model == "" {
- return "", fmt.Errorf("配置文件中未找到qwen3_model")
- }
- // 在用户消息后面添加字数限制要求
- finalMessage := userMessage
- // fmt.Println("最终发送的消息:", finalMessage)
- // 创建阿里大模型请求
- qwen3Request := map[string]interface{}{
- "model": model,
- "stream": useStream,
- "temperature": 0.7,
- "messages": []map[string]string{
- // {"role": "system", "content": "你是一个乐于助人的助手。"},
- {"role": "user", "content": finalMessage},
- },
- }
- // 序列化请求
- requestBody, err := json.Marshal(qwen3Request)
- if err != nil {
- return "", fmt.Errorf("请求序列化失败: %v", err)
- }
- // 发送HTTP请求到阿里大模型
- req, err := http.NewRequest("POST", apiURL+"/v1/chat/completions", bytes.NewBuffer(requestBody))
- if err != nil {
- return "", fmt.Errorf("创建HTTP请求失败: %v", err)
- }
- // 设置请求头
- req.Header.Set("Content-Type", "application/json")
- // 发送请求
- client := &http.Client{Timeout: 600 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- return "", fmt.Errorf("请求发送失败: %v", err)
- }
- defer resp.Body.Close()
- // 检查HTTP状态码
- if resp.StatusCode != http.StatusOK {
- responseBody, err := io.ReadAll(resp.Body)
- if err != nil {
- return "", fmt.Errorf("阿里大模型API错误: 状态码 %d,读取响应失败: %v", resp.StatusCode, err)
- }
- return "", fmt.Errorf("阿里大模型API错误: %s", string(responseBody))
- }
- if useStream {
- // 处理流式响应
- // fmt.Println("处理流式响应1111111111")
- return c.handleStreamResponse(resp)
- } else {
- // 处理非流式响应
- return c.handleNonStreamResponse(resp)
- }
- }
- // 处理流式响应
- func (c *ChatController) handleStreamResponse(resp *http.Response) (string, error) {
- // 定义流式响应结构
- type StreamResponse struct {
- ID string `json:"id"`
- Object string `json:"object"`
- Created int64 `json:"created"`
- Model string `json:"model"`
- Choices []struct {
- Index int `json:"index"`
- Delta struct {
- Role string `json:"role,omitempty"`
- Content string `json:"content,omitempty"`
- ToolCalls []struct {
- Index int `json:"index"`
- ID string `json:"id"`
- Type string `json:"type"`
- Function struct {
- Name string `json:"name"`
- Arguments string `json:"arguments"`
- } `json:"function"`
- } `json:"tool_calls,omitempty"`
- } `json:"delta"`
- Logprobs interface{} `json:"logprobs"`
- FinishReason *string `json:"finish_reason"`
- StopReason *string `json:"stop_reason,omitempty"`
- } `json:"choices"`
- }
- // 逐行读取流式响应
- scanner := bufio.NewScanner(resp.Body)
- var fullContent strings.Builder
- var firstChunk = true
- for scanner.Scan() {
- line := scanner.Text()
- // 跳过空行和data:前缀
- if line == "" || !strings.HasPrefix(line, "data: ") {
- continue
- }
- // 移除"data: "前缀
- data := strings.TrimPrefix(line, "data: ")
- // 检查是否是结束标记
- if data == "[DONE]" {
- break
- }
- // 解析JSON数据
- var streamResp StreamResponse
- if err := json.Unmarshal([]byte(data), &streamResp); err != nil {
- continue // 跳过解析失败的数据
- }
- // 标记第一个块已处理
- if firstChunk {
- firstChunk = false
- }
- // 处理choices中的内容
- if len(streamResp.Choices) > 0 {
- choice := streamResp.Choices[0]
- if choice.Delta.Content != "" {
- fullContent.WriteString(choice.Delta.Content)
- }
- // 检查是否完成
- if choice.FinishReason != nil {
- break
- }
- }
- }
- if err := scanner.Err(); err != nil {
- return "", fmt.Errorf("读取流式响应失败: %v", err)
- }
- return fullContent.String(), nil
- }
- // 处理非流式响应
- func (c *ChatController) handleNonStreamResponse(resp *http.Response) (string, error) {
- // 定义非流式响应结构(与测试文件中的Qwen3ChatResponse保持一致)
- type Qwen3ChatResponse struct {
- ID string `json:"id"`
- Object string `json:"object"`
- Created int64 `json:"created"`
- Model string `json:"model"`
- Choices []struct {
- Index int `json:"index"`
- Message struct {
- Role string `json:"role"`
- Content string `json:"content"`
- Refusal *string `json:"refusal"`
- Annotations *string `json:"annotations"`
- Audio *string `json:"audio"`
- FunctionCall *string `json:"function_call"`
- ToolCalls []interface{} `json:"tool_calls"`
- ReasoningContent *string `json:"reasoning_content"`
- } `json:"message"`
- Logprobs *string `json:"logprobs"`
- FinishReason string `json:"finish_reason"`
- StopReason *string `json:"stop_reason"`
- } `json:"choices"`
- ServiceTier *string `json:"service_tier"`
- SystemFingerprint *string `json:"system_fingerprint"`
- Usage struct {
- PromptTokens int `json:"prompt_tokens"`
- TotalTokens int `json:"total_tokens"`
- CompletionTokens int `json:"completion_tokens"`
- PromptTokensDetails *string `json:"prompt_tokens_details"`
- } `json:"usage"`
- PromptLogprobs *string `json:"prompt_logprobs"`
- KvTransferParams *string `json:"kv_transfer_params"`
- }
- // 读取完整的响应内容
- responseBody, err := io.ReadAll(resp.Body)
- if err != nil {
- return "", fmt.Errorf("读取响应失败: %v", err)
- }
- // 解析JSON响应
- var response Qwen3ChatResponse
- if err := json.Unmarshal(responseBody, &response); err != nil {
- return "", fmt.Errorf("响应解析失败: %v", err)
- }
- // 验证响应
- if response.ID == "" {
- return "", fmt.Errorf("响应ID为空")
- }
- if len(response.Choices) == 0 {
- return "", fmt.Errorf("响应中没有选择项")
- }
- return response.Choices[0].Message.Content, nil
- }
- // sendIntentMessage 发送意图识别消息到新的模型接口
- func (c *ChatController) sendIntentMessage(userMessage string) (string, error) {
- // 从Beego配置读取意图识别模型配置
- apiURL, err := web.AppConfig.String("intent_api_url")
- if err != nil || apiURL == "" {
- return "", fmt.Errorf("配置文件中未找到intent_api_url")
- }
- model, err := web.AppConfig.String("intent_model")
- if err != nil || model == "" {
- return "", fmt.Errorf("配置文件中未找到intent_model")
- }
- // 创建意图识别请求
- intentRequest := map[string]interface{}{
- "model": model,
- "stream": false,
- "messages": []map[string]string{
- {"role": "user", "content": userMessage},
- },
- }
- // 序列化请求
- requestBody, err := json.Marshal(intentRequest)
- if err != nil {
- return "", fmt.Errorf("请求序列化失败: %v", err)
- }
- // 发送HTTP请求到意图识别模型
- req, err := http.NewRequest("POST", apiURL+"/v1/chat/completions", bytes.NewBuffer(requestBody))
- if err != nil {
- return "", fmt.Errorf("创建HTTP请求失败: %v", err)
- }
- // 设置请求头
- req.Header.Set("Content-Type", "application/json")
- // 发送请求
- client := &http.Client{Timeout: 60 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- return "", fmt.Errorf("请求发送失败: %v", err)
- }
- defer resp.Body.Close()
- // 检查HTTP状态码
- if resp.StatusCode != http.StatusOK {
- responseBody, err := io.ReadAll(resp.Body)
- if err != nil {
- return "", fmt.Errorf("意图识别API错误: 状态码 %d,读取响应失败: %v", resp.StatusCode, err)
- }
- return "", fmt.Errorf("意图识别API错误: %s", string(responseBody))
- }
- // 处理非流式响应
- return c.handleIntentResponse(resp)
- }
- // handleIntentResponse 处理意图识别响应
- func (c *ChatController) handleIntentResponse(resp *http.Response) (string, error) {
- // 定义意图识别响应结构
- type IntentResponse struct {
- ID string `json:"id"`
- Object string `json:"object"`
- Created int64 `json:"created"`
- Model string `json:"model"`
- Choices []struct {
- Index int `json:"index"`
- Message struct {
- Role string `json:"role"`
- Content string `json:"content"`
- Refusal *string `json:"refusal"`
- Annotations *string `json:"annotations"`
- Audio *string `json:"audio"`
- FunctionCall *string `json:"function_call"`
- ToolCalls []interface{} `json:"tool_calls"`
- ReasoningContent *string `json:"reasoning_content"`
- } `json:"message"`
- Logprobs *string `json:"logprobs"`
- FinishReason string `json:"finish_reason"`
- StopReason *string `json:"stop_reason"`
- } `json:"choices"`
- ServiceTier *string `json:"service_tier"`
- SystemFingerprint *string `json:"system_fingerprint"`
- Usage struct {
- PromptTokens int `json:"prompt_tokens"`
- TotalTokens int `json:"total_tokens"`
- CompletionTokens int `json:"completion_tokens"`
- PromptTokensDetails *string `json:"prompt_tokens_details"`
- } `json:"usage"`
- PromptLogprobs *string `json:"prompt_logprobs"`
- PromptTokenIds *string `json:"prompt_token_ids"`
- KvTransferParams *string `json:"kv_transfer_params"`
- }
- // 读取完整的响应内容
- responseBody, err := io.ReadAll(resp.Body)
- if err != nil {
- return "", fmt.Errorf("读取响应失败: %v", err)
- }
- // 解析JSON响应
- var response IntentResponse
- if err := json.Unmarshal(responseBody, &response); err != nil {
- return "", fmt.Errorf("响应解析失败: %v", err)
- }
- // 验证响应
- if response.ID == "" {
- return "", fmt.Errorf("响应ID为空")
- }
- if len(response.Choices) == 0 {
- return "", fmt.Errorf("响应中没有选择项")
- }
- if response.Choices[0].Message.Content == "" {
- return "", fmt.Errorf("响应内容为空")
- }
- return response.Choices[0].Message.Content, nil
- }
- // estimateTokens 估算文本的token数量(基于Qwen官方BPE分词规则)
- func (c *ChatController) estimateTokens(text string) int {
- // 基于Qwen官方BPE分词规则的token估算
- // 根据官方文档:中文字符通常一个汉字对应一个或多个Token
- // 英文单词通常一个单词或其部分对应一个Token
- // 计算中文字符数量
- chineseChars := 0
- englishWords := 0
- punctuationChars := 0
- jsonChars := 0
- whitespaceChars := 0
- // 统计各种字符类型
- for _, r := range text {
- if r >= 0x4e00 && r <= 0x9fff {
- chineseChars++
- } else if r == '{' || r == '}' || r == '[' || r == ']' || r == '"' || r == ':' || r == ',' {
- jsonChars++
- } else if r == '.' || r == ',' || r == ';' || r == '!' || r == '?' || r == ':' || r == '(' || r == ')' {
- punctuationChars++
- } else if r == ' ' || r == '\n' || r == '\t' || r == '\r' {
- whitespaceChars++
- }
- }
- // 计算英文单词数量(简单按空格分割)
- words := strings.Fields(text)
- for _, word := range words {
- // 检查是否包含英文字符
- hasEnglish := false
- for _, r := range word {
- if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
- hasEnglish = true
- break
- }
- }
- if hasEnglish {
- englishWords++
- }
- }
- // 基于Qwen BPE分词规则的token估算:
- // - 中文字符:每个约1.2-1.5个token(根据官方文档,一个汉字可能对应一个或多个token)
- // - 英文单词:每个约1-2个token(取决于单词长度和复杂度)
- // - 标点符号:每个约0.5-1个token
- // - JSON结构字符:每个约0.5个token
- // - 空白字符:每个约0.1个token
- tokens := int(float64(chineseChars)*1.35 + float64(englishWords)*1.5 + float64(punctuationChars)*0.75 + float64(jsonChars)*0.5 + float64(whitespaceChars)*0.1)
- return tokens
- }
- // truncateContextToFitTokens 截断context内容以适应token限制
- func (c *ChatController) truncateContextToFitTokens(contextJSON []byte, maxTokens int, promptPrefix string) []byte {
- // 估算prompt前缀的token数量
- promptTokens := c.estimateTokens(promptPrefix)
- // 预留一些token给AI回复(大约5000个token,更保守)
- reservedTokens := 5000
- // 计算可用于context的最大token数量
- availableTokens := maxTokens - promptTokens - reservedTokens
- if availableTokens <= 0 {
- // 如果连prompt前缀都不够,返回空context
- return []byte("[]")
- }
- // 解析context JSON
- var results []interface{}
- if err := json.Unmarshal(contextJSON, &results); err != nil {
- fmt.Printf("解析context JSON失败: %v\n", err)
- return contextJSON
- }
- // 从后往前删除文档,直到满足token限制
- for len(results) > 0 {
- // 估算当前context的token数量
- currentContextJSON, _ := json.Marshal(results)
- currentTokens := c.estimateTokens(string(currentContextJSON))
- if currentTokens <= availableTokens {
- fmt.Printf("Context截断完成,最终token数量: %d,文档数量: %d\n", currentTokens, len(results))
- return currentContextJSON
- }
- // 尝试截断最后一个文档的内容而不是完全删除
- if len(results) > 0 {
- lastDoc := results[len(results)-1]
- if docMap, ok := lastDoc.(map[string]interface{}); ok {
- if content, exists := docMap["content"].(string); exists && len(content) > 500 {
- // 截断文档内容到500字符
- docMap["content"] = content[:500] + "..."
- fmt.Printf("截断最后一个文档内容,剩余文档数量: %d\n", len(results))
- continue
- }
- }
- }
- // 如果无法截断,则删除最后一个文档
- results = results[:len(results)-1]
- fmt.Printf("删除一个文档,剩余文档数量: %d\n", len(results))
- }
- // 如果所有文档都删除了,返回空数组
- return []byte("[]")
- }
- // 发送deepseek消息
- // 构造一下message、ai_conversation_id的结构体(user_id从token中获取)
- type SendDeepSeekMessageRequest struct {
- Message string `json:"message"`
- AIConversationId uint64 `json:"ai_conversation_id"`
- BusinessType int `json:"business_type"`
- ExamName string `json:"exam_name"`
- AIMessageId uint64 `json:"ai_message_id"`
- }
- // cleanNaturalLanguageAnswer 清洗natural_language_answer中的溯源信息
- func (c *ChatController) cleanNaturalLanguageAnswer(naturalAnswer string) string {
- // 匹配三个规范段落,如果包含"暂未检索"但后面还有来源,就清空来源
- patterns := []string{
- `(\*\*1\.\s*国家规范\*\*[^*]*?暂未检索[^*]*?)(\[[^\]]+\])`,
- `(\*\*2\.\s*地方规范\*\*[^*]*?暂未检索[^*]*?)(\[[^\]]+\])`,
- `(\*\*3\.\s*企业规范\*\*[^*]*?暂未检索[^*]*?)(\[[^\]]+\])`,
- }
- cleaned := naturalAnswer
- for _, pattern := range patterns {
- re := regexp.MustCompile(pattern)
- cleaned = re.ReplaceAllStringFunc(cleaned, func(match string) string {
- // 提取段落内容和来源部分
- parts := re.FindStringSubmatch(match)
- if len(parts) >= 3 {
- // 只保留段落内容,移除来源部分
- fmt.Printf("清洗了包含'暂未检索'的段落来源: %s\n", parts[2])
- return parts[1]
- }
- return match
- })
- }
- return cleaned
- }
- // cleanStructuredDataSources 清洗structured_data:如果content包含"暂未检索",清空对应的sources
- func (c *ChatController) cleanStructuredDataSources(aiResponse map[string]interface{}) {
- if structuredData, ok := aiResponse["structured_data"].(map[string]interface{}); ok {
- levels := []string{"national_level", "local_level", "enterprise_level"}
- for _, level := range levels {
- if levelData, exists := structuredData[level].(map[string]interface{}); exists {
- if content, ok := levelData["content"].(string); ok {
- if strings.Contains(content, "暂未检索") {
- levelData["sources"] = []string{}
- fmt.Printf("清洗%s的sources,因为content包含'暂未检索'\n", level)
- }
- }
- }
- }
- }
- }
- // replaceSourcesInNaturalAnswer 使用structured_data中的sources替换natural_language_answer中的溯源信息
- func (c *ChatController) replaceSourcesInNaturalAnswer(naturalAnswer string, aiResponse map[string]interface{}) string {
- // 获取structured_data
- structuredData, ok := aiResponse["structured_data"].(map[string]interface{})
- if !ok {
- fmt.Printf("structured_data字段不存在或类型错误,返回原始natural_language_answer\n")
- return naturalAnswer
- }
- // 创建level到sources的映射
- levelSources := make(map[string][]string)
- levels := []string{"national_level", "local_level", "enterprise_level"}
- for _, level := range levels {
- if levelData, exists := structuredData[level].(map[string]interface{}); exists {
- if sources, ok := levelData["sources"].([]interface{}); ok {
- var levelSourcesList []string
- for _, source := range sources {
- if sourceStr, ok := source.(string); ok && sourceStr != "" {
- levelSourcesList = append(levelSourcesList, sourceStr)
- }
- }
- levelSources[level] = levelSourcesList
- }
- }
- }
- // 检查是否有任何有效的sources
- hasValidSources := false
- for _, sources := range levelSources {
- if len(sources) > 0 {
- hasValidSources = true
- break
- }
- }
- if !hasValidSources {
- fmt.Printf("未找到有效的sources,返回原始natural_language_answer\n")
- return naturalAnswer
- }
- fmt.Printf("找到有效sources: %v\n", levelSources)
- // 第一步:完全删除natural_language_answer中所有的溯源标记
- result := naturalAnswer
- // 匹配并删除所有方括号中的内容(溯源信息)
- re := regexp.MustCompile(`\[([^\]]+)\]`)
- result = re.ReplaceAllString(result, "")
- fmt.Printf("删除所有原始溯源后的内容长度: %d\n", len(result))
- // 第二步:使用简单的字符串分割方法为每个level的段落添加对应的sources
- levelHeaders := map[string]string{
- "national_level": "**1. 国家规范**",
- "local_level": "**2. 地方规范**",
- "enterprise_level": "**3. 企业规范**",
- }
- // 按双换行符分割段落
- sections := strings.Split(result, "\n\n")
- fmt.Printf("总共分割出%d个段落\n", len(sections))
- for level, header := range levelHeaders {
- if sources, exists := levelSources[level]; exists && len(sources) > 0 {
- fmt.Printf("处理%s,sources: %v\n", level, sources)
- // 查找包含目标标题的段落
- for i, section := range sections {
- if strings.Contains(section, header) {
- fmt.Printf("找到%s段落%d,长度: %d\n", level, i+1, len(section))
- // 检查段落是否已经包含溯源信息
- if strings.Contains(section, "[") && strings.Contains(section, "]") {
- fmt.Printf("%s段落%d已包含溯源信息,跳过\n", level, i+1)
- continue
- }
- // 构建sources文本
- sourceText := ""
- for _, source := range sources {
- sourceText += "[" + source + "]"
- }
- // 在段落末尾添加溯源信息
- sections[i] = section + "\n" + sourceText
- fmt.Printf("为%s段落%d添加溯源: %s\n", level, i+1, sourceText)
- break // 只处理第一个匹配的段落
- }
- }
- } else {
- fmt.Printf("%s没有sources或sources为空\n", level)
- }
- }
- // 重新组合所有段落
- result = strings.Join(sections, "\n\n")
- fmt.Printf("溯源替换完成,新长度: %d\n", len(result))
- return result
- }
- func (c *ChatController) SendDeepSeekMessage() {
- // 从token中获取用户信息
- userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 401,
- "msg": "获取用户信息失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- user_id := uint64(userInfo.ID)
- if user_id == 0 {
- user_id = 1
- }
- // 从请求体获取消息
- var requestData SendDeepSeekMessageRequest
- if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 400,
- "msg": "请求数据解析失败",
- }
- c.ServeJSON()
- return
- }
- fmt.Println("请求数据:", requestData)
- userMessage := requestData.Message
- var userMessage1 string
- userMessage1 = userMessage
- ai_conversation_id := requestData.AIConversationId
- tx := models.DB.Begin()
- if ai_conversation_id == 0 {
- //新建对话
- ai_conversation := models.AIConversation{
- UserId: user_id,
- Content: userMessage,
- BusinessType: requestData.BusinessType,
- ExamName: requestData.ExamName,
- }
- if err := tx.Create(&ai_conversation).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "新建对话失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- ai_conversation_id = uint64(ai_conversation.ID)
- }
- business_type := requestData.BusinessType
- ai_message := models.AIMessage{
- UserId: user_id,
- Content: userMessage,
- Type: "user",
- AIConversationId: ai_conversation_id,
- }
- if err := tx.Create(&ai_message).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "新建消息失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- //安全培训
- if business_type == 1 {
- // Prompt := models.Prompt{}
- // models.DB.Model(&Prompt).Where("business_type = ? AND is_deleted = ?", business_type, 0).First(&Prompt)
- //userMessage 去向量数据库取30份文档传入下方的<context>中
- contextJSON := c.getChromaDBDocumentFunction(userMessage)
- prompt := ``
- userMessage1 = prompt
- }
- //AI写作
- if business_type == 2 {
- contextJSON := c.getChromaDBDocumentFunction(userMessage)
- prompt := ``
- userMessage1 = prompt
- }
- //如果是考试工坊则更新ai_conversation表中的content和exam_name
- if business_type == 3 {
- if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("content", userMessage).Update("exam_name", requestData.ExamName).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "更新内容失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- }
- var reply string
- // 使用阿里大模型替代DeepSeek
- if business_type != 0 {
- reply, err = c.sendQwen3Message(userMessage1, false) // 使用流式响应
- if err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "阿里大模型调用失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- } else {
- //这里写完成呃rag请求逻辑
- prompt := ``
- // reply, err = c.sendIntentMessage(prompt) // 使用新的意图识别模型
- //使用deepseek
- reply, err = c.sendQwen3Message(prompt, false) // 使用流式响应
- if err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "意图识别模型调用失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- fmt.Println("reply:", reply)
- // 解析AI返回的JSON响应
- var aiResponse map[string]interface{}
- // 清理回复中的换行符和多余空白字符
- cleanReply := strings.TrimSpace(reply)
- // 移除可能的markdown代码块标记
- cleanReply = strings.TrimPrefix(cleanReply, "```json")
- cleanReply = strings.TrimSuffix(cleanReply, "```")
- cleanReply = strings.TrimSpace(cleanReply)
- if err := json.Unmarshal([]byte(cleanReply), &aiResponse); err != nil {
- // 如果解析失败,可能是AI直接返回了文本格式(greeting、faq、out_of_scope)
- fmt.Printf("JSON解析失败,AI返回了文本格式回复: %s\n", reply)
- fmt.Printf("清理后回复: %s\n", cleanReply)
- fmt.Printf("解析错误: %v\n", err)
- // 直接使用AI的原始回复,不做格式检查
- fmt.Printf("直接使用AI的原始回复\n")
- } else {
- intent, ok := aiResponse["intent"].(string)
- if !ok {
- reply = "解析失败2"
- } else {
- // 根据intent类型决定返回内容
- if intent == "greeting" || intent == "faq" || intent == "out_of_scope" {
- // 对于greeting、faq、out_of_scope,AI应该直接返回自然回复
- // 检查是否有direct_answer字段,如果没有则使用原始回复
- if directAnswer, exists := aiResponse["direct_answer"].(string); exists && directAnswer != "" {
- reply = directAnswer
- } else {
- // 如果没有direct_answer字段,直接使用AI的原始回复
- fmt.Printf("intent为%s,直接使用AI的原始回复\n", intent)
- }
- } else {
- // reply = "复杂问题,进入下一步"
- //取出里面的数组search_queries
- search_queries, ok := aiResponse["search_queries"].([]interface{})
- if !ok || len(search_queries) == 0 {
- reply = "解析失败4"
- } else {
- // 将search_queries转换为字符串数组
- var queries []string
- for _, query := range search_queries {
- if queryStr, ok := query.(string); ok {
- queries = append(queries, queryStr)
- }
- }
- // 使用第一个查询进行搜索
- if len(queries) > 0 {
- // 构建搜索请求
- searchRequest := map[string]interface{}{
- "query": queries[0], // 使用第一个查询
- "n_results": 25, // 返回3个结果
- }
- requestBody, err := json.Marshal(searchRequest)
- if err != nil {
- reply = "解析失败5"
- } else {
- // 从配置文件中读取搜索API地址
- searchAPIURL, err := web.AppConfig.String("search_api_url")
- if err != nil || searchAPIURL == "" {
- reply = "配置文件中未找到search_api_url"
- } else {
- // 发送HTTP请求到本地Python服务
- req, err := http.NewRequest("POST", searchAPIURL, bytes.NewBuffer(requestBody))
- if err != nil {
- reply = "解析失败6"
- } else {
- req.Header.Set("Content-Type", "application/json")
- client := &http.Client{Timeout: 30 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- reply = "解析失败7" + err.Error()
- } else {
- defer resp.Body.Close()
- responseBody, err := io.ReadAll(resp.Body)
- if err != nil {
- reply = "解析失败8"
- } else if resp.StatusCode != http.StatusOK {
- reply = fmt.Sprintf("搜索API错误: 状态码 %d", resp.StatusCode)
- } else {
- // 解析搜索响应
- var searchResponse map[string]interface{}
- if err := json.Unmarshal(responseBody, &searchResponse); err != nil {
- reply = "解析失败10"
- } else {
- // 检查响应状态
- // fmt.Println("searchResponse11111111:", searchResponse)
- status, ok := searchResponse["status"].(string)
- if !ok || status != "success" {
- message, _ := searchResponse["message"].(string)
- reply = fmt.Sprintf("搜索失败: %s", message)
- } else {
- // 获取搜索结果
- results, ok := searchResponse["results"].([]interface{})
- // fmt.Println("results:", results)
- if !ok || len(results) == 0 {
- reply = "未找到相关文档"
- } else {
- // 直接将原始搜索结果转换为JSON字符串作为上下文
- // 获取历史对话(前两轮,如果只有1轮就到1轮,没有就不导入)
- var historyContext string
- if ai_conversation_id > 0 {
- var historyMessages []models.AIMessage
- // 获取当前对话的历史消息,按时间排序,排除当前消息
- models.DB.Model(&models.AIMessage{}).
- Where("user_id = ? AND ai_conversation_id = ? AND is_deleted = ? AND id < ?",
- user_id, ai_conversation_id, 0, ai_message.ID).
- Order("updated_at ASC").
- Find(&historyMessages)
- // 限制为前两轮对话(每轮包含用户消息和AI回复)
- if len(historyMessages) > 0 {
- // 计算轮数:每2条消息为1轮(用户消息+AI回复)
- maxRounds := 2
- maxMessages := maxRounds * 2
- if len(historyMessages) > maxMessages {
- historyMessages = historyMessages[len(historyMessages)-maxMessages:]
- }
- // 构建历史对话上下文
- historyContext = "\n\n# 历史对话上下文\n"
- for _, msg := range historyMessages {
- if msg.Type == "user" {
- historyContext += "用户: " + msg.Content + "\n"
- } else if msg.Type == "ai" {
- historyContext += "蜀安AI助手: " + msg.Content + "\n"
- }
- }
- historyContext += "\n"
- }
- //根据用户意图投入搜索来源数据
- }
- contextJSON, err := json.Marshal(results)
- // fmt.Println("contextJSON:", string(contextJSON))
- // fmt.Println("historyContext:", historyContext)
- if err != nil {
- reply = "处理搜索结果失败: " + err.Error()
- } else {
- // 获取联网搜索内容
- onlineSearchContent := c.getOnlineSearchContent(userMessage)
- // fmt.Println("联网数据:", onlineSearchContent)
- // 构建新的JSON格式提示词
- finalPrompt := ``
- finalReply, err := c.sendQwen3Message(finalPrompt, false) // 使用流式响应
- if err != nil {
- reply = "生成最终回答失败: " + err.Error()
- } else {
- // 解析AI返回的JSON响应
- fmt.Printf("AI原始回复: %s\n", finalReply)
- // 尝试清理JSON字符串
- cleanedReply := strings.TrimSpace(finalReply)
- // 移除可能的markdown代码块标记
- cleanedReply = strings.TrimPrefix(cleanedReply, "```json")
- cleanedReply = strings.TrimPrefix(cleanedReply, "```")
- cleanedReply = strings.TrimSuffix(cleanedReply, "```")
- cleanedReply = strings.TrimSpace(cleanedReply)
- var aiResponse map[string]interface{}
- if err := json.Unmarshal([]byte(cleanedReply), &aiResponse); err != nil {
- // 如果解析失败,尝试提取natural_language_answer字段的正则表达式
- fmt.Printf("JSON解析失败,尝试正则提取: %v\n", err)
- if strings.Contains(finalReply, "natural_language_answer") {
- // 使用正则表达式提取natural_language_answer的内容
- re := regexp.MustCompile(`"natural_language_answer"\s*:\s*"([^"]*(?:\\.[^"]*)*)"`)
- matches := re.FindStringSubmatch(finalReply)
- if len(matches) > 1 {
- naturalAnswer := matches[1]
- // 处理转义字符
- naturalAnswer = strings.ReplaceAll(naturalAnswer, "\\n", "\n")
- naturalAnswer = strings.ReplaceAll(naturalAnswer, "\\\"", "\"")
- fmt.Printf("正则提取成功,长度: %d\n", len(naturalAnswer))
- // 清洗natural_language_answer中的溯源信息
- naturalAnswer = c.cleanNaturalLanguageAnswer(naturalAnswer)
- // 尝试解析structured_data进行溯源替换
- var tempResponse map[string]interface{}
- if err := json.Unmarshal([]byte(cleanedReply), &tempResponse); err == nil {
- correctedAnswer := c.replaceSourcesInNaturalAnswer(naturalAnswer, tempResponse)
- reply = correctedAnswer
- } else {
- reply = naturalAnswer
- }
- } else {
- fmt.Printf("正则提取失败,使用原始回复\n")
- reply = finalReply
- }
- } else {
- fmt.Printf("未找到natural_language_answer字段,使用原始回复\n")
- reply = finalReply
- }
- } else {
- // 提取natural_language_answer字段
- if naturalAnswer, ok := aiResponse["natural_language_answer"].(string); ok {
- fmt.Printf("成功提取natural_language_answer,长度: %d\n", len(naturalAnswer))
- // 清洗natural_language_answer中的溯源信息
- naturalAnswer = c.cleanNaturalLanguageAnswer(naturalAnswer)
- // 使用structured_data中的sources替换natural_language_answer中的溯源信息
- correctedAnswer := c.replaceSourcesInNaturalAnswer(naturalAnswer, aiResponse)
- reply = correctedAnswer
- } else {
- // 如果字段不存在,使用原始回复
- fmt.Printf("natural_language_answer字段不存在或类型错误\n")
- reply = finalReply
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- } else {
- reply = "未找到有效的查询内容"
- }
- }
- }
- }
- }
- }
- //新建AI回复
- ai_reply := models.AIMessage{
- UserId: user_id,
- Content: reply,
- Type: "ai",
- AIConversationId: ai_conversation_id,
- PrevUserId: uint64(ai_message.ID),
- }
- if err := tx.Create(&ai_reply).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "新建消息失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- //更新AIConversation编辑时间
- if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("updated_at", time.Now().Unix()).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "更新编辑时间失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- tx.Commit()
- // 返回成功响应(保持与原来相同的格式)
- // fmt.Printf("最终返回的reply内容长度: %d\n", len(reply))
- // fmt.Printf("最终返回的reply内容: %s\n", reply)
- // if len(reply) > 100 {
- // fmt.Printf("最终返回的reply前100字符: %s\n", reply[:100])
- // } else {
- // fmt.Printf("最终返回的reply内容: %s\n", reply)
- // }
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- "data": map[string]interface{}{
- "reply": reply,
- // "user_message": userMessage,
- "ai_conversation_id": ai_conversation_id,
- "ai_message_id": ai_reply.ID,
- },
- }
- c.ServeJSON()
- }
- // 删除对话
- type DeleteConversationRequest struct {
- AIConversationID uint64 `json:"ai_conversation_id"`
- AIMessageID uint64 `json:"ai_message_id"`
- }
- func (c *ChatController) DeleteConversation() {
- var requestData DeleteConversationRequest
- if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 400,
- "msg": "请求数据解析失败",
- }
- c.ServeJSON()
- return
- }
- ai_message_id := requestData.AIMessageID
- fmt.Println("ai_message_id:", ai_message_id)
- tx := models.DB.Begin()
- //这里除了要删除这条ai消息,还要查询到prev_user_id这条消息,并删除
- if err := tx.Model(&models.AIMessage{}).Where("id = ?", ai_message_id).Update("is_deleted", 1).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "删除消息失败",
- }
- c.ServeJSON()
- return
- }
- var ai_message_user models.AIMessage
- models.DB.Where("id = ?", ai_message_id).First(&ai_message_user)
- prev_user_id := ai_message_user.PrevUserId
- if err := tx.Model(&models.AIMessage{}).Where("id = ?", prev_user_id).Update("is_deleted", 1).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "删除消息失败",
- }
- c.ServeJSON()
- return
- }
- //更新ai_conversation表中的编辑时间
- ai_conversation_id := ai_message_user.AIConversationId
- if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("updated_at", time.Now().Unix()).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "更新编辑时间失败",
- }
- c.ServeJSON()
- return
- }
- tx.Commit()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- }
- c.ServeJSON()
- }
- // ppt大纲存主表
- type SavePPTOutlineRequest struct {
- AIConversationID uint64 `json:"ai_conversation_id"`
- PPTOutline string `json:"ppt_outline"`
- PPTContent string `json:"ppt_content"`
- }
- func (c *ChatController) SavePPTOutline() {
- var requestData SavePPTOutlineRequest
- if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 400,
- "msg": "请求数据解析失败",
- }
- c.ServeJSON()
- return
- }
- ai_conversation_id := requestData.AIConversationID
- ppt_content := requestData.PPTContent
- fmt.Println("ppt_content", ppt_content)
- // ppt_outline := requestData.PPTOutline
- tx := models.DB.Begin()
- //更新到AIMessage表中的ppt_content
- if err := tx.Model(&models.AIMessage{}).Where("ai_conversation_id = ? AND type = 'ai'", ai_conversation_id).Update("content", ppt_content).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "保存ppt内容失败",
- }
- }
- //ai_conversation表中的p更新编辑时间
- if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("updated_at", time.Now().Unix()).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "更新编辑时间失败",
- }
- }
- tx.Commit()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- }
- c.ServeJSON()
- }
- // 返回历史记录
- func (c *ChatController) GetHistoryRecord() {
- // 从token中获取用户信息
- userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
- fmt.Println("userInfo", userInfo)
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 401,
- "msg": "获取用户信息失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- user_id := int64(userInfo.ID)
- if user_id == 0 {
- user_id = 1
- }
- ai_conversation_id, _ := c.GetInt64("ai_conversation_id")
- business_type, _ := c.GetInt64("business_type")
- //返回详情
- if ai_conversation_id > 0 {
- var ppt_outline string
- var ppt_json_content string
- //如果是ppt
- if business_type == 1 {
- var ai_conversation models.AIConversation
- models.DB.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).First(&ai_conversation)
- ppt_outline = ai_conversation.PPTOutline
- ppt_json_content = ai_conversation.PPTJsonContent
- }
- var ai_message []models.AIMessage
- models.DB.Model(&models.AIMessage{}).Where("user_id = ? AND ai_conversation_id = ? AND is_deleted = ?", user_id, ai_conversation_id, 0).Order("updated_at").Find(&ai_message)
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- "data": ai_message,
- "ppt_outline": ppt_outline,
- "ppt_json_content": ppt_json_content,
- }
- fmt.Println("ppt_outline", ppt_outline)
- c.ServeJSON()
- return
- }
- // 检查数据库连接
- sqlDB, err := models.DB.DB()
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "数据库连接失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- // 测试数据库连接
- if err := sqlDB.Ping(); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "数据库连接测试失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- var ai_conversation []models.AIConversation
- models.DB.Model(&models.AIConversation{}).Where("user_id = ? AND is_deleted = ? AND business_type = ?", user_id, 0, business_type).Order("-updated_at").Find(&ai_conversation)
- //计算返回的总共的数据数量
- var total int64
- models.DB.Model(&models.AIConversation{}).Where("user_id = ? AND is_deleted = ? AND business_type = ?", user_id, 0, business_type).Count(&total)
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- "data": ai_conversation,
- "total": total,
- }
- c.ServeJSON()
- }
- // 点赞和点踩post请求
- func (c *ChatController) LikeAndDislike() {
- var requestData models.AIMessage
- if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 400,
- "msg": "请求数据解析失败",
- }
- c.ServeJSON()
- return
- }
- id := requestData.ID
- user_feedback := requestData.UserFeedback
- tx := models.DB.Begin()
- if err := tx.Model(&models.AIMessage{}).Where("id = ?", id).Update("user_feedback", user_feedback).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "点赞和点踩失败",
- }
- c.ServeJSON()
- return
- }
- tx.Commit()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- }
- c.ServeJSON()
- }
- // 直接问问题
- func (c *ChatController) ReProduceSingleQuestion() {
- // 从请求体获取消息
- var requestData SendDeepSeekMessageRequest
- if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 400,
- "msg": "请求数据解析失败",
- }
- c.ServeJSON()
- return
- }
- fmt.Println("请求数据:", requestData)
- userMessage := requestData.Message
- // 使用阿里大模型替代DeepSeek
- reply, err := c.sendQwen3Message(userMessage, false) // 使用流式响应
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "阿里大模型调用失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- // 返回成功响应(保持与原来相同的格式)
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- "data": map[string]interface{}{
- "reply": reply,
- },
- }
- fmt.Println("回复:", reply)
- c.ServeJSON()
- }
- // 猜你想问
- func (c *ChatController) GuessYouWant() {
- // 从请求体获取消息
- var requestData SendDeepSeekMessageRequest
- if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 400,
- "msg": "请求数据解析失败",
- }
- c.ServeJSON()
- return
- }
- fmt.Println("请求数据:", requestData)
- userMessage := requestData.Message
- // 构建带有专业问题判断规则的提示词
- promptWithRules := fmt.Sprintf(``, userMessage)
- // 使用阿里大模型替代DeepSeek
- reply, err := c.sendQwen3Message(promptWithRules, false)
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "阿里大模型调用失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- ai_message_id := requestData.AIMessageId
- // fmt.Println("猜你想问的ai_message_id", ai_message_id)
- tx := models.DB.Begin()
- if err := tx.Model(&models.AIMessage{}).Where("id = ?", ai_message_id).Update("guess_you_want", reply).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "保存猜你想问失败",
- }
- }
- tx.Commit()
- // 返回成功响应(保持与原来相同的格式)
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- "data": map[string]interface{}{
- "reply": reply,
- },
- }
- fmt.Println("猜你想问:", reply)
- c.ServeJSON()
- }
- // 用户在输入框中每输入一个字,就调用一次阿里大模型返回推荐问题
- func (c *ChatController) GetUserRecommendQuestion() {
- // 从token中获取用户信息(GET请求也需要token)
- userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 401,
- "msg": "获取用户信息失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- user_id := int64(userInfo.ID)
- if user_id == 0 {
- user_id = 1
- }
- userMessage1 := c.GetString("user_message")
- // 直接从QA表中模糊查询问题
- var qaList []models.QA
- models.DB.Model(&models.QA{}).Where("question LIKE ? AND is_deleted = ?", "%"+userMessage1+"%", 0).Limit(10).Find(&qaList)
- if len(qaList) == 0 {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- }
- c.ServeJSON()
- return
- }
- // 提取问题列表
- var questions []string
- for _, qa := range qaList {
- questions = append(questions, qa.Question)
- }
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- "data": map[string]interface{}{
- "questions": questions,
- },
- }
- c.ServeJSON()
- }
- // 用户传文件名取数据库寻找链接(使用编辑距离算法匹配最相似的文件名)
- func (c *ChatController) GetFileLink() {
- fileName := c.GetString("fileName")
- fmt.Println("查询文件名:", fileName)
- // 获取所有未删除的文件记录
- var indexFiles []models.IndexFile
- models.DB.Model(&models.IndexFile{}).Where("is_deleted = ?", 0).Find(&indexFiles)
- if len(indexFiles) == 0 {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 404,
- "msg": "数据库中没有找到任何文件",
- "data": "",
- }
- c.ServeJSON()
- return
- }
- // 提取所有文件名作为候选列表
- var candidates []string
- for _, file := range indexFiles {
- candidates = append(candidates, file.FileName)
- }
- // 使用编辑距离算法找到最相似的文件名
- bestMatch, bestScore := utils.FindBestMatch(fileName, candidates)
- fmt.Printf("最佳匹配: %s (相似度: %.3f)\n", bestMatch, bestScore)
- // 找到对应的文件记录
- var matchedFile models.IndexFile
- for _, file := range indexFiles {
- if file.FileName == bestMatch {
- matchedFile = file
- break
- }
- }
- fmt.Println("匹配的文件记录:", matchedFile)
- fmt.Println("文件链接:", matchedFile.FilePath)
- // 如果相似度太低,可以设置阈值
- threshold := 0.3 // 相似度阈值,可以根据需要调整
- if bestScore < threshold {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": fmt.Sprintf("没有找到相似度 >= %.1f 的文件,最佳匹配相似度: %.3f", threshold, bestScore),
- "data": "",
- "bestMatch": bestMatch,
- "bestScore": bestScore,
- }
- c.ServeJSON()
- return
- }
- // 检查文件路径是否已经是代理URL格式,如果不是则转换为代理URL
- var fileURL string
- if matchedFile.FilePath != "" {
- if !strings.Contains(matchedFile.FilePath, "/apiv1/oss/parse/?url=") {
- fileURL = utils.GetProxyURL(matchedFile.FilePath)
- } else {
- fileURL = matchedFile.FilePath
- }
- }
- // 返回代理URL
- fmt.Println("代理URL:", fileURL)
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- "data": fileURL,
- "bestMatch": bestMatch,
- "bestScore": bestScore,
- "fileName": fileName,
- }
- c.ServeJSON()
- }
- // 删除历史记录
- type DeleteHistoryRecordRequest struct {
- AIConversationID uint64 `json:"ai_conversation_id"`
- }
- func (c *ChatController) DeleteHistoryRecord() {
- var requestData DeleteHistoryRecordRequest
- if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 400,
- "msg": "请求数据解析失败",
- }
- c.ServeJSON()
- return
- }
- ai_conversation_id := requestData.AIConversationID
- tx := models.DB.Begin()
- if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("is_deleted", 1).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "删除历史记录失败",
- }
- }
- tx.Commit()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- }
- c.ServeJSON()
- }
- // 删除隐患识别的历史记录
- type DeleteRecognitionRecordRequest struct {
- RecognitionRecordID uint64 `json:"recognition_record_id"`
- }
- func (c *ChatController) DeleteRecognitionRecord() {
- var requestData DeleteRecognitionRecordRequest
- if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 400,
- "msg": "请求数据解析失败",
- }
- c.ServeJSON()
- return
- }
- recognition_record_id := requestData.RecognitionRecordID
- tx := models.DB.Begin()
- if err := tx.Model(&models.RecognitionRecord{}).Where("id = ?", recognition_record_id).Update("is_deleted", 1).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "删除隐患识别的历史记录失败",
- }
- c.ServeJSON()
- return
- }
- tx.Commit()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- }
- c.ServeJSON()
- }
- // AI写作保存编辑文档内容
- func (c *ChatController) SaveEditDocument() {
- var requestData models.AIMessage
- if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 400,
- "msg": "请求数据解析失败",
- }
- }
- ai_conversation_id := requestData.AIConversationId
- fmt.Println("ai_conversation_id", ai_conversation_id)
- content := requestData.Content
- tx := models.DB.Begin()
- if err := tx.Model(&models.AIMessage{}).Where("ai_conversation_id = ? AND type = 'ai' AND is_deleted = ?", ai_conversation_id, 0).Update("content", content).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "保存编辑文档内容失败",
- }
- }
- if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("updated_at", time.Now().Unix()).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "更新编辑时间失败",
- }
- }
- tx.Commit()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- "data": content,
- }
- c.ServeJSON()
- }
- // 联网搜索
- func (c *ChatController) OnlineSearch() {
- // 获取请求参数
- keywords := c.GetString("keywords")
- // 参数验证
- if keywords == "" {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 400,
- "error": "参数错误:keywords不能为空",
- }
- c.ServeJSON()
- return
- }
- // 在关键词前加入检索策略提示词:
- // 1) 若用户意图属于土木工程/路桥隧轨/施工安全等相关领域,则直接按该意图搜索
- // 2) 若与上述领域无关,则根据用户表达猜测一个最可能的土木工程相关问题再进行搜索
- combinedKeywords := fmt.Sprintf("【搜索策略】先识别用户意图:若问题属于土木工程/路桥隧轨/施工安全等领域,则按此意图联网搜索;若非上述领域,请根据用户表达猜测一个最可能的土木工程相关问题并据此搜索。确保检索聚焦专业资料。确保回复字数在20字内。【用户问题】%s", keywords)
- //给AI发送消息
- reply, err := c.sendQwen3Message(combinedKeywords, false)
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "阿里大模型调用失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- fmt.Println("联网搜索回复:", reply)
- // 构建请求体
- requestBody := map[string]interface{}{
- "workflow_id": "4wfh1PPDderMtCeb",
- "inputs": map[string]interface{}{
- "keywords": reply,
- "num": 10, // 默认参数
- "max_text_len": 150,
- },
- "response_mode": "blocking", // 默认参数
- "user": "user_001",
- }
- // 序列化请求体
- jsonData, err := json.Marshal(requestBody)
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "error": "请求参数序列化失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- // 创建HTTP请求
- req, err := http.NewRequest("POST", utils.GetDifyWorkflowURL(), bytes.NewBuffer(jsonData))
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "error": "创建请求失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- // 设置请求头
- req.Header.Set("Authorization", "Bearer app-55CyO4lmDv1VeXK4QmFpt4ng")
- req.Header.Set("Content-Type", "application/json")
- // 发送请�?
- client := &http.Client{Timeout: 30 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "error": "请求失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- defer resp.Body.Close()
- // 读取响应
- responseBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "error": "读取响应失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- // 检查HTTP状态码
- if resp.StatusCode != http.StatusOK {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "error": fmt.Sprintf("API请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(responseBody)),
- }
- c.ServeJSON()
- return
- }
- // 解析响应
- var apiResponse map[string]interface{}
- if err := json.Unmarshal(responseBody, &apiResponse); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "error": "解析响应失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- fmt.Println("apiResponse", apiResponse)
- // 检查工作流状态
- data, ok := apiResponse["data"].(map[string]interface{})
- if !ok {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "error": "响应格式错误:缺少data字段",
- }
- c.ServeJSON()
- return
- }
- status, ok := data["status"].(string)
- if !ok || status != "succeeded" {
- errorMsg, _ := data["error"].(string)
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "error": fmt.Sprintf("工作流执行失败,状态: %s, 错误: %s", status, errorMsg),
- }
- c.ServeJSON()
- return
- }
- // 提取results字段
- outputs, ok := data["outputs"].(map[string]interface{})
- if !ok {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "error": "响应格式错误:缺少outputs字段",
- }
- c.ServeJSON()
- return
- }
- // 优先:解析 outputs.text(先直接解析;失败时再做清洗重试)
- var parsedFromText []interface{}
- if textResult, ok := outputs["text"].(string); ok && textResult != "" {
- // 1) 直接解析(适配已是标准JSON字符串的场景�?
- if err := json.Unmarshal([]byte(strings.TrimSpace(textResult)), &parsedFromText); err == nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "results": parsedFromText,
- }
- c.ServeJSON()
- return
- }
- // 2) 清洗再解析(适配Python风格字符串场景)
- cleaned := strings.ReplaceAll(textResult, "'", "\"")
- cleaned = strings.ReplaceAll(cleaned, "None", "null")
- cleaned = strings.ReplaceAll(cleaned, "\\xa0", " ")
- cleaned = strings.ReplaceAll(cleaned, "\\u0026", "&")
- if err := json.Unmarshal([]byte(strings.TrimSpace(cleaned)), &parsedFromText); err == nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "results": parsedFromText,
- }
- c.ServeJSON()
- return
- }
- }
- // 回退:如果存�?outputs.json[0].results,则按旧逻辑返回(字符串化数组)
- if jsonArray, ok := outputs["json"].([]interface{}); ok && len(jsonArray) > 0 {
- if firstResult, ok := jsonArray[0].(map[string]interface{}); ok {
- if results, ok := firstResult["results"].([]interface{}); ok {
- resultsStr, err := json.Marshal(results)
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "error": "结果序列化失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "results": string(resultsStr),
- }
- c.ServeJSON()
- return
- }
- }
- }
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "error": "响应格式错误:无法从outputs.text或outputs.json解析results",
- }
- c.ServeJSON()
- }
- // 联网搜索结果存入AIMessage表
- func (c *ChatController) SaveOnlineSearchResult() {
- var requestData models.AIMessage
- if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 400,
- "msg": "请求数据解析失败",
- }
- c.ServeJSON()
- return
- }
- search_source := requestData.SearchSource
- ai_conversation_id := requestData.AIConversationId
- id := requestData.ID
- tx := models.DB.Begin()
- fmt.Println("search_source", search_source)
- fmt.Println("ai_conversation_id", ai_conversation_id)
- fmt.Println("ai_message_id", id)
- // 更新AIMessage的search_source
- if err := tx.Model(&models.AIMessage{}).Where("id = ?", id).Update("search_source", search_source).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "保存联网搜索结果失败",
- }
- c.ServeJSON()
- return
- }
- // 更新AIConversation的updated_at
- if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("updated_at", time.Now().Unix()).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "更新编辑时间失败",
- }
- c.ServeJSON()
- return
- }
- // 提交事务
- if err := tx.Commit().Error; err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "事务提交失败",
- }
- c.ServeJSON()
- return
- }
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- }
- c.ServeJSON()
- }
- // 意图识别请求结构体(user_id从token中获取)
- type IntentRecognitionRequest struct {
- Message string `json:"message"`
- AIConversationId uint64 `json:"ai_conversation_id"`
- BusinessType int `json:"business_type"`
- }
- // 意图识别模型,用于识别用户意图
- func (c *ChatController) IntentRecognition() {
- // 从token中获取用户信息
- userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 401,
- "msg": "获取用户信息失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- user_id := uint64(userInfo.ID)
- if user_id == 0 {
- user_id = 1
- }
- // 从请求体获取消息
- var requestData IntentRecognitionRequest
- if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 400,
- "msg": "请求数据解析失败",
- }
- c.ServeJSON()
- return
- }
- fmt.Println("意图识别请求数据:", requestData)
- userMessage := requestData.Message
- ai_conversation_id := requestData.AIConversationId
- business_type := requestData.BusinessType
- // 复用意图识别提示词
- prompt := `
- `
- // 调用模型
- reply, err := c.sendQwen3Message(prompt, false)
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "意图识别模型调用失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- // 清洗与解析
- cleanReply := strings.TrimSpace(reply)
- cleanReply = strings.TrimPrefix(cleanReply, "```json")
- cleanReply = strings.TrimPrefix(cleanReply, "```")
- cleanReply = strings.TrimSuffix(cleanReply, "```")
- cleanReply = strings.TrimSpace(cleanReply)
- var aiResponse map[string]interface{}
- if err := json.Unmarshal([]byte(cleanReply), &aiResponse); err != nil {
- // 解析失败:将文本包装为标准结构返回
- aiResponse = map[string]interface{}{
- "intent": "faq",
- "confidence": 0.5,
- "search_queries": []string{userMessage},
- "direct_answer": reply,
- }
- }
- fmt.Println("aiResponse:", aiResponse)
- // 获取意图类型
- intent, ok := aiResponse["intent"].(string)
- if !ok {
- intent = "faq"
- }
- // 根据意图类型处理数据库操作
- if intent != "query_knowledge_base" {
- // 对于greeting和faq类型,需要保存到数据库
- tx := models.DB.Begin()
- // 如果ai_conversation_id为0,新建对话
- if ai_conversation_id == 0 {
- ai_conversation := models.AIConversation{
- UserId: user_id,
- Content: userMessage,
- BusinessType: business_type,
- }
- if err := tx.Create(&ai_conversation).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "新建对话失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- ai_conversation_id = uint64(ai_conversation.ID)
- }
- // 保存用户消息
- ai_message := models.AIMessage{
- UserId: user_id,
- Content: userMessage,
- Type: "user",
- AIConversationId: ai_conversation_id,
- }
- if err := tx.Create(&ai_message).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "新建消息失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- // 获取direct_answer
- directAnswer := ""
- if directAnswerValue, exists := aiResponse["direct_answer"].(string); exists {
- directAnswer = directAnswerValue
- } else {
- // 如果没有direct_answer字段,使用AI的原始回复
- directAnswer = reply
- }
- // 保存AI回复
- ai_reply := models.AIMessage{
- UserId: user_id,
- Content: directAnswer,
- Type: "ai",
- AIConversationId: ai_conversation_id,
- PrevUserId: uint64(ai_message.ID),
- }
- if err := tx.Create(&ai_reply).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "新建AI回复失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- // 更新AIConversation编辑时间
- if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("updated_at", time.Now().Unix()).Error; err != nil {
- tx.Rollback()
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "更新编辑时间失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- tx.Commit()
- // 返回成功响应
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- "data": map[string]interface{}{
- "intent_result": aiResponse,
- "direct_answer": directAnswer,
- "ai_conversation_id": ai_conversation_id,
- "ai_message_id": ai_reply.ID,
- "is_online_search": 0, // 不需要联网搜索
- },
- }
- } else {
- // 对于query_knowledge_base类型,只返回意图识别结果
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- "data": map[string]interface{}{
- "intent_result": aiResponse,
- "is_online_search": 1, // 需要联网搜索
- },
- }
- }
- c.ServeJSON()
- }
- // 获取chromadb的文档
- func (c *ChatController) GetChromaDBDocument() {
- // 从GET参数获取消息
- userMessage := c.GetString("message")
- // 构建搜索请求
- searchRequest := map[string]interface{}{
- "query": userMessage,
- "n_results": 25, // 返回25个结果
- }
- requestBody, err := json.Marshal(searchRequest)
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "构建搜索请求失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- // 从配置文件中读取搜索API地址
- searchAPIURL, err := web.AppConfig.String("search_api_url")
- if err != nil || searchAPIURL == "" {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "配置文件中未找到search_api_url",
- }
- c.ServeJSON()
- return
- }
- // 发送HTTP请求到Chroma搜索服务
- req, err := http.NewRequest("POST", searchAPIURL, bytes.NewBuffer(requestBody))
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "创建搜索请求失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- req.Header.Set("Content-Type", "application/json")
- client := &http.Client{Timeout: 30 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "搜索请求失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- defer resp.Body.Close()
- responseBody, err := io.ReadAll(resp.Body)
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "读取搜索响应失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- if resp.StatusCode != http.StatusOK {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": fmt.Sprintf("搜索API错误: 状态码 %d", resp.StatusCode),
- }
- c.ServeJSON()
- return
- }
- // 解析搜索响应
- var searchResponse map[string]interface{}
- if err := json.Unmarshal(responseBody, &searchResponse); err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "解析搜索响应失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- // 检查响应状态
- status, ok := searchResponse["status"].(string)
- if !ok || status != "success" {
- message, _ := searchResponse["message"].(string)
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": fmt.Sprintf("搜索失败: %s", message),
- }
- c.ServeJSON()
- return
- }
- // 获取搜索结果
- results, ok := searchResponse["results"].([]interface{})
- if !ok || len(results) == 0 {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- "data": map[string]interface{}{
- "reply": "未找到相关文档",
- },
- }
- c.ServeJSON()
- return
- }
- // 将搜索结果转换为JSON字符串作为上下文
- contextJSON, err := json.Marshal(results)
- if err != nil {
- c.Data["json"] = map[string]interface{}{
- "statusCode": 500,
- "msg": "处理搜索结果失败: " + err.Error(),
- }
- c.ServeJSON()
- return
- }
- fmt.Println("contextJSON:", string(contextJSON))
- // 返回成功响应
- c.Data["json"] = map[string]interface{}{
- "statusCode": 200,
- "msg": "success",
- "data": map[string]interface{}{
- "reply": string(contextJSON),
- },
- }
- c.ServeJSON()
- }
- // 获取chromadb的函数
- func (c *ChatController) getChromaDBDocumentFunction(userMessage string) string {
- // 构建搜索请求
- searchRequest := map[string]interface{}{
- "query": userMessage,
- "n_results": 25, // 返回25个结果
- }
- requestBody, err := json.Marshal(searchRequest)
- if err != nil {
- return "构建搜索请求失败: " + err.Error()
- }
- // 从配置文件中读取搜索API地址
- searchAPIURL, err := web.AppConfig.String("search_api_url")
- if err != nil || searchAPIURL == "" {
- return "配置文件中未找到search_api_url"
- }
- // 发送HTTP请求到Chroma搜索服务
- req, err := http.NewRequest("POST", searchAPIURL, bytes.NewBuffer(requestBody))
- if err != nil {
- return "构建搜索请求失败: " + err.Error()
- }
- req.Header.Set("Content-Type", "application/json")
- client := &http.Client{Timeout: 30 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- return "搜索请求失败: " + err.Error()
- }
- defer resp.Body.Close()
- responseBody, err := io.ReadAll(resp.Body)
- if err != nil {
- return "读取搜索响应失败: " + err.Error()
- }
- if resp.StatusCode != http.StatusOK {
- return "搜索API错误: " + resp.Status
- }
- // 解析搜索响应
- var searchResponse map[string]interface{}
- if err := json.Unmarshal(responseBody, &searchResponse); err != nil {
- return "解析搜索响应失败: " + err.Error()
- }
- // 检查响应状态
- status, ok := searchResponse["status"].(string)
- if !ok || status != "success" {
- message, _ := searchResponse["message"].(string)
- return fmt.Sprintf("搜索失败: %s", message)
- }
- // 获取搜索结果
- results, ok := searchResponse["results"].([]interface{})
- if !ok || len(results) == 0 {
- return "未找到相关文档"
- }
- // 将搜索结果转换为JSON字符串作为上下文
- contextJSON, err := json.Marshal(results)
- if err != nil {
- return "处理搜索结果失败: " + err.Error()
- }
- fmt.Println("contextJSON:", string(contextJSON))
- return string(contextJSON)
- }
- // getOnlineSearchContent 获取联网搜索内容
- func (c *ChatController) getOnlineSearchContent(userMessage string) string {
- // 在关键词前加入检索策略提示词:
- // 1) 若用户意图属于土木工程/路桥隧轨/施工安全等相关领域,则直接按该意图搜索
- // 2) 若与上述领域无关,则根据用户表达猜测一个最可能的土木工程相关问题再进行搜索
- combinedKeywords := fmt.Sprintf("【搜索策略】先识别用户意图:若问题属于土木工程/路桥隧轨/施工安全等领域,则按此意图联网搜索;若非上述领域,请根据用户表达猜测一个最可能的土木工程相关问题并据此搜索。确保检索聚焦专业资料。确保回复字数在20字内。【用户问题】%s", userMessage)
- //给AI发送消息
- reply, err := c.sendQwen3Message(combinedKeywords, false)
- if err != nil {
- fmt.Printf("联网搜索AI调用失败: %v\n", err)
- return ""
- }
- fmt.Println("联网搜索回复:", reply)
- // 构建请求体
- requestBody := map[string]interface{}{
- "workflow_id": "4wfh1PPDderMtCeb",
- "inputs": map[string]interface{}{
- "keywords": reply,
- "num": 10, // 默认参数
- "max_text_len": 150,
- },
- "response_mode": "blocking", // 默认参数
- "user": "user_001",
- }
- // 序列化请求体
- jsonData, err := json.Marshal(requestBody)
- if err != nil {
- fmt.Printf("联网搜索请求参数序列化失败: %v\n", err)
- return ""
- }
- // 创建HTTP请求
- req, err := http.NewRequest("POST", utils.GetDifyWorkflowURL(), bytes.NewBuffer(jsonData))
- if err != nil {
- fmt.Printf("联网搜索创建请求失败: %v\n", err)
- return ""
- }
- // 设置请求头
- req.Header.Set("Authorization", "Bearer app-55CyO4lmDv1VeXK4QmFpt4ng")
- req.Header.Set("Content-Type", "application/json")
- // 发送请求
- client := &http.Client{Timeout: 30 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- fmt.Printf("联网搜索请求失败: %v\n", err)
- return ""
- }
- defer resp.Body.Close()
- // 读取响应
- responseBody, err := io.ReadAll(resp.Body)
- if err != nil {
- fmt.Printf("联网搜索读取响应失败: %v\n", err)
- return ""
- }
- // 检查HTTP状态码
- if resp.StatusCode != http.StatusOK {
- fmt.Printf("联网搜索API请求失败,状态码: %d, 响应: %s\n", resp.StatusCode, string(responseBody))
- return ""
- }
- // 解析响应
- var apiResponse map[string]interface{}
- if err := json.Unmarshal(responseBody, &apiResponse); err != nil {
- fmt.Printf("联网搜索解析响应失败: %v\n", err)
- return ""
- }
- fmt.Println("联网搜索apiResponse", apiResponse)
- // 检查工作流状态
- data, ok := apiResponse["data"].(map[string]interface{})
- if !ok {
- fmt.Printf("联网搜索响应格式错误:缺少data字段\n")
- return ""
- }
- status, ok := data["status"].(string)
- if !ok || status != "succeeded" {
- errorMsg, _ := data["error"].(string)
- fmt.Printf("联网搜索工作流执行失败,状态: %s, 错误: %s\n", status, errorMsg)
- return ""
- }
- // 提取results字段
- outputs, ok := data["outputs"].(map[string]interface{})
- if !ok {
- fmt.Printf("联网搜索响应格式错误:缺少outputs字段\n")
- return ""
- }
- // 优先:解析 outputs.text(先直接解析;失败时再做清洗重试)
- var parsedFromText []interface{}
- if textResult, ok := outputs["text"].(string); ok && textResult != "" {
- // 1) 直接解析(适配已是标准JSON字符串的场景)
- if err := json.Unmarshal([]byte(strings.TrimSpace(textResult)), &parsedFromText); err == nil {
- // 将联网搜索结果转换为字符串格式
- onlineSearchStr := "\n\n# 联网搜索内容\n"
- for i, result := range parsedFromText {
- if resultMap, ok := result.(map[string]interface{}); ok {
- onlineSearchStr += fmt.Sprintf("联网搜索结果%d: %v\n", i+1, resultMap)
- }
- }
- return onlineSearchStr
- }
- // 2) 清洗再解析(适配Python风格字符串场景)
- cleaned := strings.ReplaceAll(textResult, "'", "\"")
- cleaned = strings.ReplaceAll(cleaned, "None", "null")
- cleaned = strings.ReplaceAll(cleaned, "\\xa0", " ")
- cleaned = strings.ReplaceAll(cleaned, "\\u0026", "&")
- if err := json.Unmarshal([]byte(strings.TrimSpace(cleaned)), &parsedFromText); err == nil {
- // 将联网搜索结果转换为字符串格式
- onlineSearchStr := "\n\n# 联网搜索内容\n"
- for i, result := range parsedFromText {
- if resultMap, ok := result.(map[string]interface{}); ok {
- onlineSearchStr += fmt.Sprintf("联网搜索结果%d: %v\n", i+1, resultMap)
- }
- }
- return onlineSearchStr
- }
- }
- // 回退:如果存在outputs.json[0].results,则按旧逻辑返回(字符串化数组)
- if jsonArray, ok := outputs["json"].([]interface{}); ok && len(jsonArray) > 0 {
- if firstResult, ok := jsonArray[0].(map[string]interface{}); ok {
- if results, ok := firstResult["results"].([]interface{}); ok {
- // 将联网搜索结果转换为字符串格式
- onlineSearchStr := "\n\n# 联网搜索内容\n"
- for i, result := range results {
- if resultMap, ok := result.(map[string]interface{}); ok {
- onlineSearchStr += fmt.Sprintf("联网搜索结果%d: %v\n", i+1, resultMap)
- }
- }
- return onlineSearchStr
- }
- }
- }
- fmt.Printf("联网搜索响应格式错误:无法从outputs.text或outputs.json解析results\n")
- return ""
- }
|