Files
ai_english/serve/internal/service/ai_service.go
2025-11-17 14:09:17 +08:00

309 lines
7.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/model"
)
// AIService AI服务接口
type AIService interface {
// 写作批改
CorrectWriting(ctx context.Context, content string, taskType string) (*model.WritingCorrection, error)
// 口语评估
EvaluateSpeaking(ctx context.Context, audioText string, prompt string) (*model.SpeakingEvaluation, error)
// 智能推荐
GetRecommendations(ctx context.Context, userLevel string, learningHistory []string) (*model.AIRecommendation, error)
// 生成练习题
GenerateExercise(ctx context.Context, content string, exerciseType string) (*model.Exercise, error)
}
type aiService struct {
apiKey string
baseURL string
client *http.Client
}
// NewAIService 创建AI服务实例
func NewAIService() AIService {
apiKey := os.Getenv("OPENAI_API_KEY")
baseURL := os.Getenv("OPENAI_BASE_URL")
if baseURL == "" {
baseURL = "https://api.openai.com/v1"
}
return &aiService{
apiKey: apiKey,
baseURL: baseURL,
client: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// OpenAI API请求结构
type openAIRequest struct {
Model string `json:"model"`
Messages []message `json:"messages"`
MaxTokens int `json:"max_tokens"`
Temperature float64 `json:"temperature"`
}
type message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type openAIResponse struct {
Choices []struct {
Message message `json:"message"`
} `json:"choices"`
Error *struct {
Message string `json:"message"`
Type string `json:"type"`
} `json:"error,omitempty"`
}
// callOpenAI 调用OpenAI API
func (s *aiService) callOpenAI(ctx context.Context, prompt string) (string, error) {
if s.apiKey == "" {
return "", fmt.Errorf("OpenAI API key not configured")
}
reqBody := openAIRequest{
Model: "gpt-3.5-turbo",
Messages: []message{
{
Role: "user",
Content: prompt,
},
},
MaxTokens: 1000,
Temperature: 0.7,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return "", fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", s.baseURL+"/chat/completions", bytes.NewBuffer(jsonData))
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+s.apiKey)
resp, err := s.client.Do(req)
if err != nil {
return "", fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %w", err)
}
var openAIResp openAIResponse
if err := json.Unmarshal(body, &openAIResp); err != nil {
return "", fmt.Errorf("failed to unmarshal response: %w", err)
}
if openAIResp.Error != nil {
return "", fmt.Errorf("OpenAI API error: %s", openAIResp.Error.Message)
}
if len(openAIResp.Choices) == 0 {
return "", fmt.Errorf("no response from OpenAI")
}
return openAIResp.Choices[0].Message.Content, nil
}
// CorrectWriting 写作批改
func (s *aiService) CorrectWriting(ctx context.Context, content string, taskType string) (*model.WritingCorrection, error) {
prompt := fmt.Sprintf(`请对以下英语写作进行批改,任务类型:%s
写作内容:
%s
请按照以下JSON格式返回批改结果
{
"overall_score": 85,
"grammar_score": 80,
"vocabulary_score": 90,
"structure_score": 85,
"content_score": 88,
"corrections": [
{
"original": "错误的句子",
"corrected": "修正后的句子",
"explanation": "修改说明",
"error_type": "grammar"
}
],
"suggestions": [
"建议1",
"建议2"
],
"strengths": [
"优点1",
"优点2"
],
"weaknesses": [
"需要改进的地方1",
"需要改进的地方2"
]
}`, taskType, content)
response, err := s.callOpenAI(ctx, prompt)
if err != nil {
return nil, err
}
// 解析JSON响应
var correction model.WritingCorrection
if err := json.Unmarshal([]byte(response), &correction); err != nil {
// 如果JSON解析失败返回基本的批改结果
return &model.WritingCorrection{
OverallScore: 75,
Suggestions: []string{"AI批改服务暂时不可用请稍后重试"},
}, nil
}
return &correction, nil
}
// EvaluateSpeaking 口语评估
func (s *aiService) EvaluateSpeaking(ctx context.Context, audioText string, prompt string) (*model.SpeakingEvaluation, error) {
evalPrompt := fmt.Sprintf(`请对以下英语口语进行评估,题目:%s
口语内容:
%s
请按照以下JSON格式返回评估结果
{
"overall_score": 85,
"pronunciation_score": 80,
"fluency_score": 90,
"grammar_score": 85,
"vocabulary_score": 88,
"feedback": "整体评价",
"strengths": [
"优点1",
"优点2"
],
"improvements": [
"需要改进的地方1",
"需要改进的地方2"
]
}`, prompt, audioText)
response, err := s.callOpenAI(ctx, evalPrompt)
if err != nil {
return nil, err
}
// 解析JSON响应
var evaluation model.SpeakingEvaluation
if err := json.Unmarshal([]byte(response), &evaluation); err != nil {
// 如果JSON解析失败返回基本的评估结果
return &model.SpeakingEvaluation{
OverallScore: 75,
Feedback: "AI评估服务暂时不可用请稍后重试",
}, nil
}
return &evaluation, nil
}
// GetRecommendations 智能推荐
func (s *aiService) GetRecommendations(ctx context.Context, userLevel string, learningHistory []string) (*model.AIRecommendation, error) {
historyStr := strings.Join(learningHistory, ", ")
prompt := fmt.Sprintf(`基于用户的英语水平(%s和学习历史%s请提供个性化的学习推荐。
请按照以下JSON格式返回推荐结果
{
"recommended_topics": [
"推荐主题1",
"推荐主题2"
],
"difficulty_level": "intermediate",
"study_plan": [
"学习计划步骤1",
"学习计划步骤2"
],
"focus_areas": [
"重点关注领域1",
"重点关注领域2"
]
}`, userLevel, historyStr)
response, err := s.callOpenAI(ctx, prompt)
if err != nil {
return nil, err
}
// 解析JSON响应
var recommendation model.AIRecommendation
if err := json.Unmarshal([]byte(response), &recommendation); err != nil {
// 如果JSON解析失败返回基本的推荐结果
return &model.AIRecommendation{
RecommendedTopics: []string{"基础语法练习", "日常对话练习"},
DifficultyLevel: "beginner",
StudyPlan: []string{"每天练习30分钟", "重点关注基础词汇"},
}, nil
}
return &recommendation, nil
}
// GenerateExercise 生成练习题
func (s *aiService) GenerateExercise(ctx context.Context, content string, exerciseType string) (*model.Exercise, error) {
prompt := fmt.Sprintf(`基于以下内容生成%s类型的练习题
内容:
%s
请按照以下JSON格式返回练习题
{
"title": "练习题标题",
"instructions": "练习说明",
"questions": [
{
"question": "问题1",
"options": ["选项A", "选项B", "选项C", "选项D"],
"correct_answer": "正确答案",
"explanation": "解释"
}
]
}`, exerciseType, content)
response, err := s.callOpenAI(ctx, prompt)
if err != nil {
return nil, err
}
// 解析JSON响应
var exercise model.Exercise
if err := json.Unmarshal([]byte(response), &exercise); err != nil {
// 如果JSON解析失败返回基本的练习题
return &model.Exercise{
Title: "基础练习",
Instructions: "AI练习生成服务暂时不可用请稍后重试",
Questions: []model.Question{},
}, nil
}
return &exercise, nil
}