// 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份文档传入下方的中 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 "" }