From f51039e361c75c53073450ef5c1069eb95eaa0d9 Mon Sep 17 00:00:00 2001 From: Deng Ming Date: Sun, 1 Dec 2024 11:08:26 +0800 Subject: [PATCH] =?UTF-8?q?AI=20=E5=8A=9F=E8=83=BD=E4=B8=8A=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .run/webook.run.xml | 3 ++ internal/ai/internal/domain/jd.go | 4 +- .../internal/integration/llm_service_test.go | 21 ++++---- internal/ai/internal/service/jd_service.go | 20 +++++-- internal/ai/internal/web/vo.go | 4 +- internal/resume/internal/domain/analysis.go | 13 ++--- .../integration/analysis_handler_test.go | 22 +++----- .../integration/project_handler_test.go | 4 +- internal/resume/internal/service/analysis.go | 52 ++++++++++++------- ioc/admin.go | 7 ++- ioc/wire.go | 2 +- ioc/wire_gen.go | 5 +- 12 files changed, 95 insertions(+), 62 deletions(-) diff --git a/.run/webook.run.xml b/.run/webook.run.xml index 62190a4..003a6c5 100644 --- a/.run/webook.run.xml +++ b/.run/webook.run.xml @@ -4,6 +4,9 @@ + + + diff --git a/internal/ai/internal/domain/jd.go b/internal/ai/internal/domain/jd.go index 3365f9c..b82bcf7 100644 --- a/internal/ai/internal/domain/jd.go +++ b/internal/ai/internal/domain/jd.go @@ -7,8 +7,8 @@ const ( ) type JDEvaluation struct { - Score int `json:"score"` - Analysis string `json:"analysis"` + Score float64 `json:"score"` + Analysis string `json:"analysis"` } type JD struct { diff --git a/internal/ai/internal/integration/llm_service_test.go b/internal/ai/internal/integration/llm_service_test.go index 8a66dff..d285f57 100644 --- a/internal/ai/internal/integration/llm_service_test.go +++ b/internal/ai/internal/integration/llm_service_test.go @@ -643,21 +643,24 @@ func (s *LLMServiceSuite) TestHandler_AnalysisJD() { return domain.LLMResponse{ Tokens: 1000, Amount: 100, - Answer: `{"score": 7, "analysis": "tech"}`, + Answer: `score: 6 +这是技术前景`, }, nil } if request.Biz == "analysis_jd_biz" { return domain.LLMResponse{ Tokens: 100, Amount: 200, - Answer: `{"score": 8, "analysis": "biz"}`, + Answer: `score: 7 +这是业务前景`, }, nil } if request.Biz == "analysis_jd_position" { return domain.LLMResponse{ Tokens: 100, Amount: 100, - Answer: `{"score": 5, "analysis": "position"}`, + Answer: `score: 8 +这是公司地位`, }, nil } return domain.LLMResponse{}, errors.New("unknown biz") @@ -675,16 +678,16 @@ func (s *LLMServiceSuite) TestHandler_AnalysisJD() { assert.Equal(t, web.JDResponse{ Amount: 400, TechScore: &web.JDEvaluation{ - Score: 7, - Analysis: "tech", + Score: 6, + Analysis: "这是技术前景", }, BizScore: &web.JDEvaluation{ - Score: 8, - Analysis: "biz", + Score: 7, + Analysis: "这是业务前景", }, PosScore: &web.JDEvaluation{ - Score: 5, - Analysis: "position", + Score: 8, + Analysis: "这是公司地位", }, }, resp) diff --git a/internal/ai/internal/service/jd_service.go b/internal/ai/internal/service/jd_service.go index b52006b..093e947 100644 --- a/internal/ai/internal/service/jd_service.go +++ b/internal/ai/internal/service/jd_service.go @@ -2,7 +2,9 @@ package service import ( "context" - "encoding/json" + "errors" + "strconv" + "strings" "sync/atomic" "github.com/ecodeclub/webook/internal/ai/internal/domain" @@ -83,10 +85,18 @@ func (j *jdSvc) analysisJd(ctx context.Context, uid int64, biz string, jd string if err != nil { return 0, nil, err } - var jdEva domain.JDEvaluation - err = json.Unmarshal([]byte(resp.Answer), &jdEva) + answer := strings.SplitN(resp.Answer, "\n", 2) + if len(answer) != 2 { + return 0, nil, errors.New("不符合预期的大模型响应") + } + score := answer[0] + scoreNum, err := strconv.ParseFloat(strings.TrimSpace(strings.TrimPrefix(score, "score:")), 64) if err != nil { - return 0, nil, err + return 0, nil, errors.New("分数返回的数据不对") } - return resp.Amount, &jdEva, nil + + return resp.Amount, &domain.JDEvaluation{ + Score: scoreNum, + Analysis: strings.TrimSpace(strings.TrimPrefix(answer[1], "analysis:")), + }, nil } diff --git a/internal/ai/internal/web/vo.go b/internal/ai/internal/web/vo.go index da2002b..4735c6b 100644 --- a/internal/ai/internal/web/vo.go +++ b/internal/ai/internal/web/vo.go @@ -22,8 +22,8 @@ type JDResponse struct { } type JDEvaluation struct { - Score int `json:"score"` - Analysis string `json:"analysis"` + Score float64 `json:"score"` + Analysis string `json:"analysis"` } type Config struct { diff --git a/internal/resume/internal/domain/analysis.go b/internal/resume/internal/domain/analysis.go index 8a9b66f..7de71b4 100644 --- a/internal/resume/internal/domain/analysis.go +++ b/internal/resume/internal/domain/analysis.go @@ -1,12 +1,13 @@ package domain const ( - BizResumeSkillKeyPoints = "biz_resume_skill_keypoints" - BizSkillsRewrite = "biz_skills_rewrite" - BizResumeProjectKeyPoints = "biz_resume_project_keypoints" - BizResumeProjectRewrite = "biz_resume_project_rewrite" - BizResumeJobsKeyPoints = "biz_resume_jobs_keypoints" - BizResumeJobsRewrite = "biz_resume_jobs_rewrite" + BizResumeSkillKeyPoints = "biz_resume_skill_keypoints" + BizSkillsRewrite = "biz_resume_skill_rewrite" + // BizResumeProjectEvaluation 评价项目经历 + BizResumeProjectEvaluation = "biz_resume_project_evaluation" + BizResumeProjectRewrite = "biz_resume_project_rewrite" + //BizResumeJobsKeyPoints = "biz_resume_job_keypoints" + BizResumeJobsRewrite = "biz_resume_job_rewrite" ) type ResumeAnalysis struct { diff --git a/internal/resume/internal/integration/analysis_handler_test.go b/internal/resume/internal/integration/analysis_handler_test.go index f5c7e87..8009c40 100644 --- a/internal/resume/internal/integration/analysis_handler_test.go +++ b/internal/resume/internal/integration/analysis_handler_test.go @@ -47,29 +47,23 @@ func (a *AnalysisTestSuite) SetupSuite() { Amount: 200, Answer: fmt.Sprintf("%s:%s", req.Input[0], req.Input[1]), }, nil - case domain.BizResumeProjectKeyPoints: + case domain.BizResumeProjectEvaluation: return ai.LLMResponse{ Tokens: 150, Amount: 150, - Answer: fmt.Sprintf("project的keypoints %s", req.Input[0]), + Answer: fmt.Sprintf("project的评估 %s", req.Input[0]), }, nil case domain.BizResumeProjectRewrite: return ai.LLMResponse{ Tokens: 220, Amount: 220, - Answer: fmt.Sprintf("%s:%s", req.Input[0], req.Input[1]), - }, nil - case domain.BizResumeJobsKeyPoints: - return ai.LLMResponse{ - Tokens: 300, - Amount: 300, - Answer: fmt.Sprintf("jobs的keypoints %s", req.Input[0]), + Answer: fmt.Sprintf("project的重写 %s", req.Input[0]), }, nil case domain.BizResumeJobsRewrite: return ai.LLMResponse{ Tokens: 400, Amount: 400, - Answer: fmt.Sprintf("%s:%s", req.Input[0], req.Input[1]), + Answer: fmt.Sprintf("工作经历重写%s", req.Input[0]), }, nil default: return ai.LLMResponse{}, errors.New("mock Err") @@ -107,10 +101,10 @@ func (a *AnalysisTestSuite) TestAnalysis() { wantCode: 200, wantResp: test.Result[web.AnalysisResp]{ Data: web.AnalysisResp{ - Amount: 1370, - RewriteSkills: "resume:skill的keypoints resume", - RewriteJobs: "resume:jobs的keypoints resume", - RewriteProject: "resume:project的keypoints resume", + Amount: 1070, + RewriteSkills: ":skill的keypoints resume", + RewriteJobs: "工作经历重写resume", + RewriteProject: "project的重写 resume\n## 综合评价\nproject的评估 project的重写 resume", }, }, }, diff --git a/internal/resume/internal/integration/project_handler_test.go b/internal/resume/internal/integration/project_handler_test.go index e2ac15e..3dc466a 100644 --- a/internal/resume/internal/integration/project_handler_test.go +++ b/internal/resume/internal/integration/project_handler_test.go @@ -1055,7 +1055,7 @@ func (s *ProjectTestSuite) TestResumeInfo() { } } -func (s *ProjectTestSuite) TestReaumeList() { +func (s *ProjectTestSuite) TestResumeList() { for i := 1; i < 4; i++ { _, err := s.pdao.Upsert(context.Background(), dao.ResumeProject{ ID: int64(i), @@ -1116,8 +1116,8 @@ func (s *ProjectTestSuite) TestReaumeList() { }) req, err := http.NewRequest(http.MethodPost, "/resume/project/list", iox.NewJSONReader(nil)) - req.Header.Set("content-type", "application/json") require.NoError(s.T(), err) + req.Header.Set("content-type", "application/json") recorder := test.NewJSONResponseRecorder[[]web.Project]() s.server.ServeHTTP(recorder, req) diff --git a/internal/resume/internal/service/analysis.go b/internal/resume/internal/service/analysis.go index 2d53c50..781b309 100644 --- a/internal/resume/internal/service/analysis.go +++ b/internal/resume/internal/service/analysis.go @@ -35,12 +35,13 @@ func (r *analysisService) Analysis(ctx context.Context, uid int64, resume string var rewriteSKills, rewriteProject, rewriteJobs string // 重写技能 eg.Go(func() error { - keyPointsAmount, keyPoints, err := r.getKeyPoints(ctx, uid, domain.BizResumeSkillKeyPoints, fmt.Sprintf("%s_skills_get_keypoints", tid), resume) + keyPointsAmount, keyPoints, err := r.skillKeypoint(ctx, uid, fmt.Sprintf("%s_skills_get_keypoints", tid), resume) if err != nil { return err } atomic.AddInt64(&amount, keyPointsAmount) - rewriteSkillsAmount, ans, err := r.rewriteSkills(ctx, uid, fmt.Sprintf("%s_skills_rewrite", tid), keyPoints, resume) + // 暂时不需要传入原始简历,不然会严重超时,并且上下文太长,搞崩系统 + rewriteSkillsAmount, ans, err := r.rewriteSkills(ctx, uid, fmt.Sprintf("%s_skills_rewrite", tid), keyPoints, "") if err != nil { return err } @@ -50,27 +51,30 @@ func (r *analysisService) Analysis(ctx context.Context, uid int64, resume string }) // 重写项目 eg.Go(func() error { - keyPointsAmount, keyPoints, err := r.getKeyPoints(ctx, uid, domain.BizResumeProjectKeyPoints, fmt.Sprintf("%s_project_get_keypoints", tid), resume) + rewriteProjectAmount, ans, err := r.rewriteProject(ctx, uid, fmt.Sprintf("%s_project_rewrite", tid), resume) if err != nil { return err } - atomic.AddInt64(&amount, keyPointsAmount) - rewriteProjectAmount, ans, err := r.rewriteProject(ctx, uid, fmt.Sprintf("%s_project_rewrite", tid), keyPoints, resume) + atomic.AddInt64(&amount, rewriteProjectAmount) + evaluationAmt, evaluation, err := r.evaluatePrj(ctx, uid, + domain.BizResumeProjectEvaluation, + fmt.Sprintf("%s_project_get_evaludation", tid), ans) if err != nil { return err } - atomic.AddInt64(&amount, rewriteProjectAmount) - rewriteProject = ans + atomic.AddInt64(&amount, evaluationAmt) + rewriteProject = fmt.Sprintf("%s\n## 综合评价\n%s", ans, evaluation) return nil }) // 重写工作经历 eg.Go(func() error { - keyPointsAmount, keyPoints, err := r.getKeyPoints(ctx, uid, domain.BizResumeJobsKeyPoints, fmt.Sprintf("%s_jobs_get_keypoints", tid), resume) - if err != nil { - return err - } - atomic.AddInt64(&amount, keyPointsAmount) - rewriteJobsAmount, ans, err := r.rewriteJobs(ctx, uid, fmt.Sprintf("%s_jobs_rewrite", tid), keyPoints, resume) + // 暂时还不需要提取关键字 + //keyPointsAmount, keyPoints, err := r.evaluatePrj(ctx, uid, domain.BizResumeJobsKeyPoints, fmt.Sprintf("%s_jobs_get_keypoints", tid), resume) + //if err != nil { + // return err + //} + //atomic.AddInt64(&amount, keyPointsAmount) + rewriteJobsAmount, ans, err := r.rewriteJobs(ctx, uid, fmt.Sprintf("%s_jobs_rewrite", tid), "", resume) if err != nil { return err } @@ -92,14 +96,27 @@ func (r *analysisService) Analysis(ctx context.Context, uid int64, resume string } -// 提取关键字 -func (r *analysisService) getKeyPoints(ctx context.Context, uid int64, biz, tid, resume string) (int64, string, error) { - // 首先提取关键字 +func (r *analysisService) evaluatePrj(ctx context.Context, uid int64, biz, tid, rewritePrj string) (int64, string, error) { aiReq := ai.LLMRequest{ Uid: uid, Tid: tid, Biz: biz, // 标题,标准答案,输入 + Input: []string{rewritePrj}, + } + resp, err := r.aiSvc.Invoke(ctx, aiReq) + if err != nil { + return 0, "", err + } + return resp.Amount, resp.Answer, nil +} + +// 提取关键字 +func (r *analysisService) skillKeypoint(ctx context.Context, uid int64, tid, resume string) (int64, string, error) { + aiReq := ai.LLMRequest{ + Uid: uid, + Tid: tid, + Biz: domain.BizResumeSkillKeyPoints, Input: []string{resume}, } resp, err := r.aiSvc.Invoke(ctx, aiReq) @@ -131,7 +148,7 @@ func (r *analysisService) rewriteSkills(ctx context.Context, uid int64, tid, key } // 重写项目 -func (r *analysisService) rewriteProject(ctx context.Context, uid int64, tid, keyPoints, resume string) (int64, string, error) { +func (r *analysisService) rewriteProject(ctx context.Context, uid int64, tid, resume string) (int64, string, error) { aiReq := ai.LLMRequest{ Uid: uid, Tid: tid, @@ -139,7 +156,6 @@ func (r *analysisService) rewriteProject(ctx context.Context, uid int64, tid, ke // 标题,标准答案,输入 Input: []string{ resume, - keyPoints, }, } resp, err := r.aiSvc.Invoke(ctx, aiReq) diff --git a/ioc/admin.go b/ioc/admin.go index cb750ee..aba8d06 100644 --- a/ioc/admin.go +++ b/ioc/admin.go @@ -18,6 +18,8 @@ import ( "net/http" "strings" + "github.com/ecodeclub/webook/internal/ai" + "github.com/ecodeclub/webook/internal/cases" baguwen "github.com/ecodeclub/webook/internal/question" @@ -43,7 +45,9 @@ func InitAdminServer(prj *project.AdminHandler, queSet *baguwen.AdminQuestionSetHandler, caseHdl *cases.AdminCaseHandler, caseSetHdl *cases.AdminCaseSetHandler, - mark *marketing.AdminHandler) AdminServer { + mark *marketing.AdminHandler, + aiHdl *ai.AdminHandler, +) AdminServer { res := egin.Load("admin").Build() res.Use(cors.New(cors.Config{ ExposeHeaders: []string{"X-Refresh-Token", "X-Access-Token"}, @@ -73,6 +77,7 @@ func InitAdminServer(prj *project.AdminHandler, que.PrivateRoutes(res.Engine) caseHdl.PrivateRoutes(res.Engine) caseSetHdl.PrivateRoutes(res.Engine) + aiHdl.RegisterRoutes(res.Engine) return res } diff --git a/ioc/wire.go b/ioc/wire.go index 30d70fd..2bf3903 100644 --- a/ioc/wire.go +++ b/ioc/wire.go @@ -92,7 +92,7 @@ func InitApp() (*App, error) { wire.FieldsOf(new(*bff.Module), "Hdl"), resume.InitModule, wire.FieldsOf(new(*resume.Module), "PrjHdl", "AnalysisHandler"), - wire.FieldsOf(new(*ai.Module), "Hdl"), + wire.FieldsOf(new(*ai.Module), "Hdl", "AdminHandler"), initLocalActiveLimiterBuilder, initCronJobs, diff --git a/ioc/wire_gen.go b/ioc/wire_gen.go index 1a9234a..9234ffe 100644 --- a/ioc/wire_gen.go +++ b/ioc/wire_gen.go @@ -1,6 +1,6 @@ // Code generated by Wire. DO NOT EDIT. -//go:generate go run -mod=mod github.com/google/wire/cmd/wire +//go:generate go run github.com/google/wire/cmd/wire //go:build !wireinject // +build !wireinject @@ -143,7 +143,8 @@ func InitApp() (*App, error) { adminCaseHandler := casesModule.AdminHandler adminCaseSetHandler := casesModule.AdminSetHandler adminHandler3 := marketingModule.AdminHdl - adminServer := InitAdminServer(adminHandler, webAdminHandler, adminHandler2, adminQuestionSetHandler, adminCaseHandler, adminCaseSetHandler, adminHandler3) + adminHandler4 := aiModule.AdminHandler + adminServer := InitAdminServer(adminHandler, webAdminHandler, adminHandler2, adminQuestionSetHandler, adminCaseHandler, adminCaseSetHandler, adminHandler3, adminHandler4) closeTimeoutOrdersJob := orderModule.CloseTimeoutOrdersJob closeTimeoutLockedCreditsJob := creditModule.CloseTimeoutLockedCreditsJob syncWechatOrderJob := paymentModule.SyncWechatOrderJob