package quesbank import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net/http" "sync" "time" ) // AIAnswerService AI答题服务 type AIAnswerService struct { config AIConfig mu sync.Mutex } // NewAIAnswerService 创建AI答题服务 func NewAIAnswerService(config AIConfig) *AIAnswerService { return &AIAnswerService{ config: config, } } // GetAnswer 获取AI答案 func (s *AIAnswerService) GetAnswer(ctx context.Context, messages AIChatMessages) (string, error) { s.mu.Lock() defer s.mu.Unlock() switch s.config.Type { case AITypeChatGLM: return s.chatGLMRequest(ctx, messages) case AITypeXingHuo: return s.xingHuoRequest(ctx, messages) case AITypeTongYi: return s.tongYiRequest(ctx, messages) case AITypeDouBao: return s.douBaoRequest(ctx, messages) case AITypeOpenAI: return s.openAIRequest(ctx, messages) case AITypeOther: return s.otherRequest(ctx, messages) default: return "", fmt.Errorf("unsupported AI type: %s", s.config.Type) } } // chatGLMRequest 智谱AI请求 func (s *AIAnswerService) chatGLMRequest(ctx context.Context, messages AIChatMessages) (string, error) { url := "https://open.bigmodel.cn/api/paas/v4/chat/completions" model := s.config.Model if model == "" { model = "glm-4" } return s.doOpenAIStyleRequest(ctx, url, model, messages) } // xingHuoRequest 讯飞星火请求 func (s *AIAnswerService) xingHuoRequest(ctx context.Context, messages AIChatMessages) (string, error) { url := "https://spark-api-open.xf-yun.com/v1/chat/completions" model := s.config.Model if model == "" { model = "generalv3.5" } return s.doOpenAIStyleRequest(ctx, url, model, messages) } // tongYiRequest 通义千问请求 func (s *AIAnswerService) tongYiRequest(ctx context.Context, messages AIChatMessages) (string, error) { url := "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions" model := s.config.Model if model == "" { model = "qwen-plus" } return s.doOpenAIStyleRequest(ctx, url, model, messages) } // douBaoRequest 豆包请求 func (s *AIAnswerService) douBaoRequest(ctx context.Context, messages AIChatMessages) (string, error) { url := "https://ark.cn-beijing.volces.com/api/v3/chat/completions" return s.doOpenAIStyleRequest(ctx, url, s.config.Model, messages) } // openAIRequest OpenAI请求 func (s *AIAnswerService) openAIRequest(ctx context.Context, messages AIChatMessages) (string, error) { url := "https://api.openai.com/v1/chat/completions" model := s.config.Model if model == "" { model = "gpt-3.5-turbo" } return s.doOpenAIStyleRequest(ctx, url, model, messages) } // otherRequest 其他兼容OpenAI格式的API请求 func (s *AIAnswerService) otherRequest(ctx context.Context, messages AIChatMessages) (string, error) { if s.config.URL == "" { return "", errors.New("API URL is required for 'other' AI type") } return s.doOpenAIStyleRequest(ctx, s.config.URL, s.config.Model, messages) } // doOpenAIStyleRequest 执行OpenAI风格的API请求 func (s *AIAnswerService) doOpenAIStyleRequest(ctx context.Context, url, model string, messages AIChatMessages) (string, error) { requestBody := map[string]interface{}{ "model": model, "messages": messages.Messages, } jsonData, err := json.Marshal(requestBody) if err != nil { return "", fmt.Errorf("failed to marshal request: %v", err) } var lastErr error for retry := 0; retry < 3; retry++ { result, err := s.doHTTPRequest(ctx, url, jsonData) if err == nil { return result, nil } lastErr = err time.Sleep(100 * time.Millisecond) } return "", lastErr } // doHTTPRequest 执行HTTP请求 func (s *AIAnswerService) doHTTPRequest(ctx context.Context, url string, jsonData []byte) (string, error) { client := &http.Client{Timeout: 60 * time.Second} req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) if err != nil { return "", fmt.Errorf("failed to create request: %v", err) } req.Header.Set("Authorization", "Bearer "+s.config.APIKey) req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("failed to execute request: %v", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("failed to read response: %v", err) } var responseMap map[string]interface{} if err := json.Unmarshal(body, &responseMap); err != nil { return "", fmt.Errorf("failed to parse response: %v, body: %s", err, string(body)) } choices, ok := responseMap["choices"].([]interface{}) if !ok || len(choices) == 0 { return "", fmt.Errorf("AI response not found, response: %s", string(body)) } message, ok := choices[0].(map[string]interface{})["message"].(map[string]interface{}) if !ok { return "", errors.New("failed to parse message from response") } content, ok := message["content"].(string) if !ok { return "", errors.New("content field missing in response") } return content, nil }