|
|
@@ -1,2446 +0,0 @@
|
|
|
-// 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 ""
|
|
|
-}
|