1 line
16 KiB
JSON
1 line
16 KiB
JSON
{"repo": "aprogramerwithoutglasses/cet-composition", "files": {"backend/service/deepseek.go": {"0a3477d44cce5f9f8ba05308b49a06b0f9c11444": {"t": 1785746105.1598756, "n": 3973, "h": "32d5a1047b195b22", "c": "package service\n\nimport (\n\t\"bytes\"\n\t\"cet-composition/config\"\n\t\"cet-composition/model\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst systemPromptTemplate = `你是一位专业的大学英语%s(CET-%s)作文评分老师。\n请严格按照以下真实CET评分标准对作文进行评分,不得随意给分。\n\n【CET作文真实评分标准(原始满分15分)】\n15分:内容切题,思路清晰,论述充分;结构严谨,衔接自然;词汇丰富多样,句式多样复杂;语言流畅,几乎无错误。\n13-14分:内容切题,有一定深度;结构较好;词汇较丰富,句式有变化;偶有小错,不影响理解。\n11-12分:内容基本切题;结构基本合理;词汇一般,句式较单调;有些语法错误但意思清楚。\n9-10分:内容部分切题;结构不够清晰;词汇有限,句式简单重复;语法错误较多,影响理解。\n7-8分:内容较少切题;结构混乱;词汇贫乏,句式极其简单;错误很多,严重影响理解。\n1-6分:内容基本不切题,或极短,或文不对题。\n\n【折算规则】将15分制折算为100分制:score_100 = round(score_15 / 15 * 100)\n\n请按照以下JSON格式返回,不要输出任何其他内容:\n{\n \"total_score\": <0-100整数,严格按折算规则>,\n \"vocab_score\": <0-100整数,词汇维度>,\n \"sentence_score\": <0-100整数,句式维度>,\n \"struct_score\": <0-100整数,篇章结构维度>,\n \"content_score\": <0-100整数,内容相关维度>,\n \"feedback\": \"<50字以内的正向鼓励反馈,肯定亮点,激励继续学习>\",\n \"annotations\": [\n {\n \"id\": \"ann-1\",\n \"type\": \"<vocab|sentence|structure|content>\",\n \"start\": <原文字符起始偏移,从0计>,\n \"end\": <原文字符结束偏移,不含>,\n \"original\": \"<原文片段>\",\n \"suggestion\": \"<建议写法>\",\n \"explanation\": \"<30字以内的中文解释>\"\n }\n ]\n}\n\nannotations 中每种类型至少标注1处,总数不超过10处,选择最有代表性的问题。`\n\nvar deepseekClient = &http.Client{Timeout: 120 * time.Second}\n\ntype DeepSeekResult struct {\n\tTotalScore int `json:\"total_score\"`\n\tVocabScore int `json:\"vocab_score\"`\n\tSentenceScore int `json:\"sentence_score\"`\n\tStructScore int `json:\"struct_score\"`\n\tContentScore int `json:\"content_score\"`\n\tFeedback string `json:\"feedback\"`\n\tAnnotations []model.Annotation `json:\"annotations\"`\n}\n\nfunc CallDeepSeek(cfg *config.Config, cetLevel int, text string) (*DeepSeekResult, error) {\n\tlevelStr := fmt.Sprintf(\"%d\", cetLevel)\n\tlevelName := \"四级\"\n\tif cetLevel == 6 {\n\t\tlevelName = \"六级\"\n\t}\n\tsystemPrompt := fmt.Sprintf(systemPromptTemplate, levelName, levelStr)\n\n\tpayload := map[string]interface{}{\n\t\t\"model\": \"deepseek-chat\",\n\t\t\"messages\": []map[string]string{\n\t\t\t{\"role\": \"system\", \"content\": systemPrompt},\n\t\t\t{\"role\": \"user\", \"content\": \"请批改以下作文:\\n\\n\" + text},\n\t\t},\n\t\t\"stream\": false,\n\t\t\"temperature\": 0.3,\n\t}\n\tbody, err := json.Marshal(payload)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"marshal payload: %w\", err)\n\t}\n\n\treq, err := http.NewRequest(\"POST\", \"https://api.deepseek.com/chat/completions\", bytes.NewReader(body))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+cfg.DeepSeekAPIKey())\n\n\tresp, err := deepseekClient.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"do request: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\traw, _ := io.ReadAll(resp.Body)\n\t\treturn nil, fmt.Errorf(\"DeepSeek API status %d: %s\", resp.StatusCode, string(raw))\n\t}\n\n\traw, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read response: %w\", err)\n\t}\n\n\tvar apiResp struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\tif err := json.Unmarshal(raw, &apiResp); err != nil || len(apiResp.Choices) == 0 {\n\t\treturn nil, fmt.Errorf(\"parse DeepSeek response: %s\", string(raw))\n\t}\n\n\tcontent := apiResp.Choices[0].Message.Content\n\t// Strip markdown code fences if present (handles ```json, ```\\n, trailing newlines, etc.)\n\tcontent = strings.TrimSpace(content)\n\tif strings.HasPrefix(content, \"```\") {\n\t\t// Remove first line (``` or ```json etc.)\n\t\tif idx := strings.Index(content, \"\\n\"); idx != -1 {\n\t\t\tcontent = content[idx+1:]\n\t\t}\n\t\t// Remove trailing ```\n\t\tcontent = strings.TrimSpace(content)\n\t\tif strings.HasSuffix(content, \"```\") {\n\t\t\tcontent = strings.TrimSpace(content[:len(content)-3])\n\t\t}\n\t}\n\n\tvar result DeepSeekResult\n\tif err := json.Unmarshal([]byte(content), &result); err != nil {\n\t\treturn nil, fmt.Errorf(\"parse result JSON: %v, content: %s\", err, content)\n\t}\n\treturn &result, nil\n}\n"}, "0dd29654f3d14c3988d4a08e9188a4abe1199612": {"t": 1785759883.3021407, "n": 3746, "h": "e5c43e87fe3d7741", "c": "package service\n\nimport (\n\t\"bytes\"\n\t\"cet-composition/config\"\n\t\"cet-composition/model\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst systemPromptTemplate = `你是一位专业的大学英语%s(CET-%s)作文评分老师。\n请严格按照以下真实CET评分标准对作文进行评分,不得随意给分。\n\n【CET作文真实评分标准(原始满分15分)】\n15分:内容切题,思路清晰,论述充分;结构严谨,衔接自然;词汇丰富多样,句式多样复杂;语言流畅,几乎无错误。\n13-14分:内容切题,有一定深度;结构较好;词汇较丰富,句式有变化;偶有小错,不影响理解。\n11-12分:内容基本切题;结构基本合理;词汇一般,句式较单调;有些语法错误但意思清楚。\n9-10分:内容部分切题;结构不够清晰;词汇有限,句式简单重复;语法错误较多,影响理解。\n7-8分:内容较少切题;结构混乱;词汇贫乏,句式极其简单;错误很多,严重影响理解。\n1-6分:内容基本不切题,或极短,或文不对题。\n\n【折算规则】将15分制折算为100分制:score_100 = round(score_15 / 15 * 100)\n\n请按照以下JSON格式返回,不要输出任何其他内容:\n{\n \"total_score\": <0-100整数,严格按折算规则>,\n \"vocab_score\": <0-100整数,词汇维度>,\n \"sentence_score\": <0-100整数,句式维度>,\n \"struct_score\": <0-100整数,篇章结构维度>,\n \"content_score\": <0-100整数,内容相关维度>,\n \"feedback\": \"<50字以内的正向鼓励反馈,肯定亮点,激励继续学习>\",\n \"annotations\": [\n {\n \"id\": \"ann-1\",\n \"type\": \"<vocab|sentence|structure|content>\",\n \"start\": <原文字符起始偏移,从0计>,\n \"end\": <原文字符结束偏移,不含>,\n \"original\": \"<原文片段>\",\n \"suggestion\": \"<建议写法>\",\n \"explanation\": \"<30字以内的中文解释>\"\n }\n ]\n}\n\nannotations 中每种类型至少标注1处,总数不超过10处,选择最有代表性的问题。`\n\nvar deepseekClient = &http.Client{Timeout: 120 * time.Second}\n\ntype DeepSeekResult struct {\n\tTotalScore int `json:\"total_score\"`\n\tVocabScore int `json:\"vocab_score\"`\n\tSentenceScore int `json:\"sentence_score\"`\n\tStructScore int `json:\"struct_score\"`\n\tContentScore int `json:\"content_score\"`\n\tFeedback string `json:\"feedback\"`\n\tAnnotations []model.Annotation `json:\"annotations\"`\n}\n\nfunc CallDeepSeek(cfg *config.Config, cetLevel int, text string) (*DeepSeekResult, error) {\n\tlevelStr := fmt.Sprintf(\"%d\", cetLevel)\n\tlevelName := \"四级\"\n\tif cetLevel == 6 {\n\t\tlevelName = \"六级\"\n\t}\n\tsystemPrompt := fmt.Sprintf(systemPromptTemplate, levelName, levelStr)\n\n\tpayload := map[string]interface{}{\n\t\t\"model\": \"deepseek-chat\",\n\t\t\"messages\": []map[string]string{\n\t\t\t{\"role\": \"system\", \"content\": systemPrompt},\n\t\t\t{\"role\": \"user\", \"content\": \"请批改以下作文:\\n\\n\" + text},\n\t\t},\n\t\t\"stream\": false,\n\t\t\"temperature\": 0.3,\n\t}\n\tbody, err := json.Marshal(payload)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"marshal payload: %w\", err)\n\t}\n\n\treq, err := http.NewRequest(\"POST\", \"https://api.deepseek.com/chat/completions\", bytes.NewReader(body))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+cfg.DeepSeekAPIKey())\n\n\tresp, err := deepseekClient.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"do request: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\traw, _ := io.ReadAll(resp.Body)\n\t\treturn nil, fmt.Errorf(\"DeepSeek API status %d: %s\", resp.StatusCode, string(raw))\n\t}\n\n\traw, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read response: %w\", err)\n\t}\n\n\tvar apiResp struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\tif err := json.Unmarshal(raw, &apiResp); err != nil || len(apiResp.Choices) == 0 {\n\t\treturn nil, fmt.Errorf(\"parse DeepSeek response: %s\", string(raw))\n\t}\n\n\tcontent := apiResp.Choices[0].Message.Content\n\t// Strip markdown code fences if present\n\tcontent = strings.TrimSpace(content)\n\tif strings.HasPrefix(content, \"```\") {\n\t\tlines := strings.Split(content, \"\\n\")\n\t\tif len(lines) > 2 {\n\t\t\tcontent = strings.Join(lines[1:len(lines)-1], \"\\n\")\n\t\t}\n\t}\n\n\tvar result DeepSeekResult\n\tif err := json.Unmarshal([]byte(content), &result); err != nil {\n\t\treturn nil, fmt.Errorf(\"parse result JSON: %v, content: %s\", err, content)\n\t}\n\treturn &result, nil\n}\n"}, "4220ed2017b35a2e732a152aa747fa5b821b2375": {"t": 1785759884.5092971, "n": 3744, "h": "64bda05ad2d3c530", "c": "package service\n\nimport (\n\t\"bytes\"\n\t\"cet-composition/config\"\n\t\"cet-composition/model\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst systemPromptTemplate = `你是一位专业的大学英语%s(CET-%s)作文评分老师。\n请严格按照以下真实CET评分标准对作文进行评分,不得随意给分。\n\n【CET作文真实评分标准(原始满分15分)】\n15分:内容切题,思路清晰,论述充分;结构严谨,衔接自然;词汇丰富多样,句式多样复杂;语言流畅,几乎无错误。\n13-14分:内容切题,有一定深度;结构较好;词汇较丰富,句式有变化;偶有小错,不影响理解。\n11-12分:内容基本切题;结构基本合理;词汇一般,句式较单调;有些语法错误但意思清楚。\n9-10分:内容部分切题;结构不够清晰;词汇有限,句式简单重复;语法错误较多,影响理解。\n7-8分:内容较少切题;结构混乱;词汇贫乏,句式极其简单;错误很多,严重影响理解。\n1-6分:内容基本不切题,或极短,或文不对题。\n\n【折算规则】将15分制折算为100分制:score_100 = round(score_15 / 15 * 100)\n\n请按照以下JSON格式返回,不要输出任何其他内容:\n{\n \"total_score\": <0-100整数,严格按折算规则>,\n \"vocab_score\": <0-100整数,词汇维度>,\n \"sentence_score\": <0-100整数,句式维度>,\n \"struct_score\": <0-100整数,篇章结构维度>,\n \"content_score\": <0-100整数,内容相关维度>,\n \"feedback\": \"<50字以内的正向鼓励反馈,肯定亮点,激励继续学习>\",\n \"annotations\": [\n {\n \"id\": \"ann-1\",\n \"type\": \"<vocab|sentence|structure|content>\",\n \"start\": <原文字符起始偏移,从0计>,\n \"end\": <原文字符结束偏移,不含>,\n \"original\": \"<原文片段>\",\n \"suggestion\": \"<建议写法>\",\n \"explanation\": \"<30字以内的中文解释>\"\n }\n ]\n}\n\nannotations 中每种类型至少标注1处,总数不超过10处,选择最有代表性的问题。`\n\nvar deepseekClient = &http.Client{Timeout: 120 * time.Second}\n\ntype DeepSeekResult struct {\n\tTotalScore int `json:\"total_score\"`\n\tVocabScore int `json:\"vocab_score\"`\n\tSentenceScore int `json:\"sentence_score\"`\n\tStructScore int `json:\"struct_score\"`\n\tContentScore int `json:\"content_score\"`\n\tFeedback string `json:\"feedback\"`\n\tAnnotations []model.Annotation `json:\"annotations\"`\n}\n\nfunc CallDeepSeek(cfg *config.Config, cetLevel int, text string) (*DeepSeekResult, error) {\n\tlevelStr := fmt.Sprintf(\"%d\", cetLevel)\n\tlevelName := \"四级\"\n\tif cetLevel == 6 {\n\t\tlevelName = \"六级\"\n\t}\n\tsystemPrompt := fmt.Sprintf(systemPromptTemplate, levelName, levelStr)\n\n\tpayload := map[string]interface{}{\n\t\t\"model\": \"deepseek-chat\",\n\t\t\"messages\": []map[string]string{\n\t\t\t{\"role\": \"system\", \"content\": systemPrompt},\n\t\t\t{\"role\": \"user\", \"content\": \"请批改以下作文:\\n\\n\" + text},\n\t\t},\n\t\t\"stream\": false,\n\t\t\"temperature\": 0.3,\n\t}\n\tbody, err := json.Marshal(payload)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"marshal payload: %w\", err)\n\t}\n\n\treq, err := http.NewRequest(\"POST\", \"https://api.deepseek.com/chat/completions\", bytes.NewReader(body))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+cfg.DeepSeekAPIKey)\n\n\tresp, err := deepseekClient.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"do request: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\traw, _ := io.ReadAll(resp.Body)\n\t\treturn nil, fmt.Errorf(\"DeepSeek API status %d: %s\", resp.StatusCode, string(raw))\n\t}\n\n\traw, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read response: %w\", err)\n\t}\n\n\tvar apiResp struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\tif err := json.Unmarshal(raw, &apiResp); err != nil || len(apiResp.Choices) == 0 {\n\t\treturn nil, fmt.Errorf(\"parse DeepSeek response: %s\", string(raw))\n\t}\n\n\tcontent := apiResp.Choices[0].Message.Content\n\t// Strip markdown code fences if present\n\tcontent = strings.TrimSpace(content)\n\tif strings.HasPrefix(content, \"```\") {\n\t\tlines := strings.Split(content, \"\\n\")\n\t\tif len(lines) > 2 {\n\t\t\tcontent = strings.Join(lines[1:len(lines)-1], \"\\n\")\n\t\t}\n\t}\n\n\tvar result DeepSeekResult\n\tif err := json.Unmarshal([]byte(content), &result); err != nil {\n\t\treturn nil, fmt.Errorf(\"parse result JSON: %v, content: %s\", err, content)\n\t}\n\treturn &result, nil\n}\n"}}}} |