174 lines
4.9 KiB
Go
174 lines
4.9 KiB
Go
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
|
|
}
|