AnCao/internal/services/ai_grading.go
yanlongqi ea051e9380 添加AI评分系统和题目列表功能
新增功能:
1. AI智能评分系统
   - 集成OpenAI兼容API进行简答题评分
   - 提供分数、评语和改进建议
   - 支持自定义AI服务配置(BaseURL、APIKey、Model)

2. 题目列表页面
   - 展示所有题目和答案
   - Tab标签页形式的题型筛选(选择题、多选题、判断题、填空题、简答题)
   - 关键词搜索功能(支持题目内容和编号搜索)
   - 填空题特殊渲染:****显示为下划线
   - 判断题不显示选项,界面更简洁

3. UI优化
   - 答题结果组件重构,支持AI评分显示
   - 首页新增"题目列表"快速入口
   - 响应式设计,适配移动端和PC端

技术改进:
- 添加AI评分服务层(internal/services/ai_grading.go)
- 扩展题目模型支持AI评分结果
- 更新配置管理支持AI服务配置
- 添加AI评分测试脚本和文档

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 13:36:30 +08:00

157 lines
3.9 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 services
import (
"ankao/pkg/config"
"context"
"encoding/json"
"fmt"
"strings"
"github.com/sashabaranov/go-openai"
)
// AIGradingService AI评分服务
type AIGradingService struct {
client *openai.Client
config *config.AIConfig
}
// NewAIGradingService 创建AI评分服务实例
func NewAIGradingService() *AIGradingService {
cfg := config.GetAIConfig()
// 创建OpenAI客户端配置
clientConfig := openai.DefaultConfig(cfg.APIKey)
clientConfig.BaseURL = cfg.BaseURL + "/v1" // 标准OpenAI API格式需要/v1后缀
client := openai.NewClientWithConfig(clientConfig)
return &AIGradingService{
client: client,
config: cfg,
}
}
// AIGradingResult AI评分结果
type AIGradingResult struct {
Score float64 `json:"score"` // 得分 (0-100)
IsCorrect bool `json:"is_correct"` // 是否正确 (Score >= 60 视为正确)
Feedback string `json:"feedback"` // 评语
Suggestion string `json:"suggestion"` // 改进建议
}
// GradeShortAnswer 对简答题进行AI评分
// question: 题目内容
// standardAnswer: 标准答案
// userAnswer: 用户答案
func (s *AIGradingService) GradeShortAnswer(question, standardAnswer, userAnswer string) (*AIGradingResult, error) {
// 构建评分提示词
prompt := fmt.Sprintf(`你是一位专业的阅卷老师,请对以下简答题进行评分。
题目:%s
标准答案:%s
学生答案:%s
请按照以下要求进行评分:
1. 给出一个0-100的分数
2. 判断答案是否正确60分及以上为正确
3. 给出简短的评语不超过50字
4. 如果答案不完善给出改进建议不超过50字如果答案很好可以为空
请按照以下JSON格式返回结果
{
"score": 85,
"is_correct": true,
"feedback": "答案基本正确,要点全面",
"suggestion": "可以补充一些具体的例子"
}
注意只返回JSON格式的结果不要有其他内容。`, question, standardAnswer, userAnswer)
// 调用AI API
resp, err := s.client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: s.config.Model,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleSystem,
Content: "你是一位专业的阅卷老师,擅长对简答题进行公正、客观的评分。",
},
{
Role: openai.ChatMessageRoleUser,
Content: prompt,
},
},
Temperature: 0.3, // 较低的温度以获得更稳定的评分结果
},
)
if err != nil {
return nil, fmt.Errorf("AI评分失败: %w", err)
}
if len(resp.Choices) == 0 {
return nil, fmt.Errorf("AI未返回评分结果")
}
// 解析AI返回的JSON结果
content := resp.Choices[0].Message.Content
var result AIGradingResult
if err := parseAIResponse(content, &result); err != nil {
return nil, fmt.Errorf("解析AI响应失败: %w", err)
}
return &result, nil
}
// parseAIResponse 解析AI返回的JSON响应
func parseAIResponse(content string, result *AIGradingResult) error {
// 移除可能的markdown代码块标记
jsonStr := removeMarkdownCodeBlock(content)
// 使用json包解析
if err := json.Unmarshal([]byte(jsonStr), result); err != nil {
return fmt.Errorf("JSON解析失败: %w, 原始内容: %s", err, content)
}
return nil
}
// removeMarkdownCodeBlock 移除markdown代码块标记
func removeMarkdownCodeBlock(s string) string {
// 去除可能的```json和```标记
s = strings.TrimSpace(s)
// 移除开头的```json或```
if strings.HasPrefix(s, "```json") {
s = s[7:]
} else if strings.HasPrefix(s, "```") {
s = s[3:]
}
// 移除结尾的```
if strings.HasSuffix(s, "```") {
s = s[:len(s)-3]
}
s = strings.TrimSpace(s)
// 查找第一个{的位置
startIdx := strings.Index(s, "{")
if startIdx == -1 {
return s
}
// 查找最后一个}的位置
endIdx := strings.LastIndex(s, "}")
if endIdx == -1 || endIdx <= startIdx {
return s
}
return s[startIdx : endIdx+1]
}