| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446 |
- // 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(`你是蜀道安全管理AI智能助手,请根据用户的问题生成3个相关的后续问题建议(猜你想问)。
- ## 用户问题
- %s
- ## 生成问题规则(最高优先级)
- 1. 严禁生成任何政治敏感信息,包含重要国家领导人,重要国际事件等
- 2. 严禁在生成的问题中包含人名信息,任何人名都不行
- 3. 严禁生成色情敏感信息
- 4. 严禁生成超长文本,最多只能30个字
- ## 你的回答(仅输出3个问题,每行一个,或返回空)`, userMessage)
- >>>>>>> origin/dev
- promptWithRules := fmt.Sprintf(`你是蜀道安全管理AI智能助手,请根据用户的问题生成3个相关的后续问题建议(猜你想问)。
- ## 用户问题
- %s
- ## 生成问题规则(最高优先级)
- 1. 严禁生成任何政治敏感信息,包含重要国家领导人,重要国际事件等
- 2. 严禁在生成的问题中包含人名信息,任何人名都不行
- 3. 严禁生成色情敏感信息
- 4. 严禁生成超长文本,最多只能30个字
- ## 你的回答(仅输出3个问题,每行一个,或返回空)`, userMessage)
- =======
- promptWithRules := fmt.Sprintf(`你是蜀道安全管理AI智能助手,请根据用户的问题生成3个相关的后续问题建议(猜你想问)。
- ## 用户问题
- %s
- ## 生成问题规则(最高优先级)
- 1. 严禁生成任何政治敏感信息,包含重要国家领导人,重要国际事件等
- 2. 严禁在生成的问题中包含人名信息,任何人名都不行
- 3. 严禁生成色情敏感信息
- 4. 严禁生成超长文本,最多只能30个字
- ## 你的回答(仅输出3个问题,每行一个,或返回空)`, userMessage)
- >>>>>>> origin/dev
- // 使用阿里大模型替代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 ""
- }
|