diff --git a/cmd/server/main.go b/cmd/server/main.go index 4fd60c5..a2e0f4b 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -15,19 +15,25 @@ func main() { log.Fatalf("Failed to load configuration: %v", err) } - // Initialize handler + // Initialize handlers ocrHandler := handler.NewOCRHandler( cfg.TencentSecretID, cfg.TencentSecretKey, cfg.GeminiAPIKey, cfg.APIKey, ) + + rateHandler := handler.NewRateHandler( + cfg.GeminiAPIKey, + cfg.APIKey, + ) // Setup Gin router r := gin.Default() // Register routes r.POST("/ocr", ocrHandler.HandleOCR) + r.POST("/rate", rateHandler.HandleRate) // Start server if err := r.Run("localhost:8080"); err != nil { diff --git a/pkg/handler/rate.go b/pkg/handler/rate.go new file mode 100644 index 0000000..4a2f8f0 --- /dev/null +++ b/pkg/handler/rate.go @@ -0,0 +1,176 @@ +package handler + +import ( + "net/http" + "github.com/gin-gonic/gin" + "github.com/google/generative-ai-go/genai" + "google.golang.org/api/option" + "encoding/json" + "strings" +) + +type RateHandler struct { + geminiAPIKey string + apiKey string +} + +type RateRequest struct { + Content string `json:"content" binding:"required"` + Criteria string `json:"criteria"` + WritingRequirement string `json:"writing_requirement"` + APIKey string `json:"apikey" binding:"required"` +} + +type RateResponse struct { + Rate int `json:"rate"` + Summary string `json:"summary"` + DetailedReview string `json:"detailed_review"` + Success bool `json:"success"` +} + +func NewRateHandler(geminiAPIKey, apiKey string) *RateHandler { + return &RateHandler{ + geminiAPIKey: geminiAPIKey, + apiKey: apiKey, + } +} + +func (h *RateHandler) HandleRate(c *gin.Context) { + var req RateRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, RateResponse{ + Success: false, + }) + return + } + + // Validate API key + if req.APIKey != h.apiKey { + c.JSON(http.StatusUnauthorized, RateResponse{ + Success: false, + }) + return + } + + // Initialize Gemini client + ctx := c.Request.Context() + client, err := genai.NewClient(ctx, option.WithAPIKey(h.geminiAPIKey)) + if err != nil { + c.JSON(http.StatusInternalServerError, RateResponse{ + Success: false, + }) + return + } + defer client.Close() + + // Prepare criteria + criteria := req.Criteria + if criteria == "" { + criteria = `你是一名语文老师。你正在给学生的作文打分。根据以下中考作文评分标准,给作文打分。 +## 评分总分值:100分。 +### 88-100分 符合题意;写作目的和对象明确;思考充分,立意深刻,感情真挚;选材精当,内容充实;中心突出,条理清晰;表达准确,语言流畅。 +### 75-87分 符合题意;写作目的和对象较明确;思考较充分,立意清楚,感情真实;选材合理,内容具体;中心明确,有一定条理;表达较准确,语言通畅。 +### 60-74分 符合题意;写作目的和对象较模糊;有一定思考,感情真实;有一定内容;结构基本完整;语言尚通畅。 +### 60分以下 不符合题意;缺乏写作目的和对象;基本没有思考,感情虚假;内容空洞;结构混乱;不成篇。` + } + writing_requirement := req.WritingRequirement + if writing_requirement == "" { + writing_requirement = "写一篇不少于600字的作文,体裁不限。" + } + + // 规定输出格式是json,包含rate, summary, detailed_review,放入prompt的最后 + format := `请按照以下JSON格式输出: +{ + "rate": 分数, // 最多100分制的分数,int类型 + "summary": "总体评价", // 100字以内的总体评价,string类型 + "detailed_review": "详细点评" // 300字以内的详细点评,包含优点和建议,string类型 +}` + // Prepare prompt + prompt := "作文要求:\n" + writing_requirement + "\n\n" + "评分标准:\n" + criteria + format + "\n\n" + "\n\n作文内容:\n" + req.Content + + // Generate content + model := client.GenerativeModel("gemini-2.0-flash-exp") + resp, err := model.GenerateContent(ctx, genai.Text(prompt)) + if err != nil { + c.JSON(http.StatusInternalServerError, RateResponse{ + Success: false, + }) + return + } + + if len(resp.Candidates) > 0 && len(resp.Candidates[0].Content.Parts) > 0 { + if textPart, ok := resp.Candidates[0].Content.Parts[0].(genai.Text); ok { + // Parse the response to extract rate, summary, and detailed review + result := parseRateResponse(string(textPart)) + + c.JSON(http.StatusOK, RateResponse{ + Rate: result.Rate, + Summary: result.Summary, + DetailedReview: result.Detailed, + Success: true, + }) + return + } + } + + c.JSON(http.StatusInternalServerError, RateResponse{ + Success: false, + }) +} + +type rateResult struct { + Rate int `json:"rate"` + Summary string `json:"summary"` + Detailed string `json:"detailed_review"` +} + +func parseRateResponse(response string) rateResult { + var result rateResult + //去除所有\n + response = strings.ReplaceAll(response, "\n", "") + //去除所有\t + response = strings.ReplaceAll(response, "\t", "") + // 去除response中的```json前缀和```后缀 + response = strings.TrimSpace(response) + response = strings.TrimPrefix(response, "```json") + response = strings.TrimSuffix(response, "```") + + + // 检查response是否是json格式 + if !strings.HasPrefix(response, "{") { + return rateResult{ + Rate: 0, + Summary: "解析失败", + Detailed: "没有左括号", + } + } + if !strings.HasSuffix(response, "}") { + return rateResult{ + Rate: 0, + Summary: "解析失败", + Detailed: "没有右括号", + } + } + + // 解析json + err := json.Unmarshal([]byte(response), &result) + if err != nil { + return rateResult{ + Rate: 0, + Summary: "解析失败", + Detailed: "反序列化失败", + } + } + + // 合并所有验证条件 + if result.Rate <= 0 || result.Rate > 100 || + result.Summary == "" || result.Detailed == "" { + return rateResult{ + Rate: 0, + Summary: "解析失败", + Detailed: "字段验证条件不满足", + } + } + + return result +} \ No newline at end of file diff --git a/rate b/rate new file mode 100755 index 0000000..8a535e9 Binary files /dev/null and b/rate differ