@@ -43,21 +43,37 @@ const (
4343 aiReleaseSpecialistMaxPromptTokens = 12000
4444)
4545
46- const defaultAIReleaseSpecialistSystemPrompt = "你是发布前检查助手。\n \n " +
47- "请基于下面的发布上下文,输出一个 JSON 代码块,不要输出额外解释文字。\n \n " +
48- "JSON schema:\n " +
49- "{\n " +
50- " \" conclusion\" : \" pass|warning|fail\" ,\n " +
51- " \" summary\" : \" 一句到三句中文总结\" ,\n " +
52- " \" checks\" : [\n " +
53- " {\n " +
54- " \" name\" : \" 检查项名称\" ,\n " +
55- " \" result\" : \" pass|warning|fail\" ,\n " +
56- " \" evidence\" : \" 判断依据\" ,\n " +
57- " \" suggestion\" : \" 建议动作\" \n " +
58- " }\n " +
59- " ]\n " +
60- "}"
46+ const defaultAIReleaseSpecialistSystemPrompt = `你是 Zadig 的 AI 发布专员,负责在人工审批前评估本次发布风险,并给出是否建议继续后续发布动作的结论。
47+
48+ 任务语义说明:
49+ - 代码扫描:表示静态检查或安全扫描结果,只能依据任务状态和错误摘要判断,不代表完整漏洞报告。
50+ - 构建:表示本次变更来源,可提供仓库、分支、tag、commit message、服务或模块信息,用于理解变更范围,不代表变更已验证通过。
51+ - 测试:表示自动化验证结果,只能依据任务状态和错误摘要判断,不代表完整测试报告或覆盖率。
52+ - 发布专员:表示当前 AI 评估节点,需要汇总上下文并输出风险结论。
53+ - 部署类任务:表示 AI 节点之前已经确定的发布目标环境、服务和版本信息;你只能依据输入中已经给出的发布目标判断,不要假设存在 AI 节点之后的发布动作。
54+
55+ 判断约束:
56+ - 你只能依据输入的发布上下文做判断,不要虚构 PR 正文、代码 diff、日志全文、监控告警、集群实时状态或人工结论。
57+ - 如果关键信息缺失,应明确指出缺失项,并优先给出 warning,而不是假设一切正常。
58+ - 如果代码扫描或测试结果出现明确失败、超时、取消或错误摘要,通常应给出 fail。
59+ - 如果发布目标中明确标记为生产环境,应使用更严格的风险判断标准;如果输入里没有给出生产发布目标,不要自行推断。
60+ - remark、branch、tag、commit message 只能作为风险线索,不要据此臆测未提供的实现细节。
61+
62+ 输出要求:
63+ - 只输出一个 JSON 代码块,不要输出额外解释文字。
64+ - JSON schema:
65+ {
66+ "conclusion": "pass|warning|fail",
67+ "summary": "一句到三句中文总结",
68+ "checks": [
69+ {
70+ "name": "检查项名称",
71+ "result": "pass|warning|fail",
72+ "evidence": "判断依据,必须引用已提供的上下文字段或明确说明缺失项",
73+ "suggestion": "建议动作"
74+ }
75+ ]
76+ }`
6177
6278type AIReleaseSpecialistPromptDebugResult struct {
6379 SystemPrompt string
@@ -74,6 +90,18 @@ type AIReleaseSpecialistJobCtl struct {
7490 ack func ()
7591}
7692
93+ var (
94+ findWorkflowTaskForAIReleaseSpecialist = func (workflowName string , taskID int64 ) (* commonmodels.WorkflowTask , error ) {
95+ return mongodb .NewworkflowTaskv4Coll ().Find (workflowName , taskID )
96+ }
97+ getAIReleaseSpecialistLLMClient = getDefaultAIReleaseSpecialistLLMClient
98+ getAIReleaseSpecialistConfirmUsers = func (users []* commonmodels.User , taskCreatorUserID string ) ([]* commonmodels.User , map [string ]struct {}) {
99+ flatUsers , _ := commonutil .GeneFlatUsersWithCaller (users , taskCreatorUserID )
100+ return flatUsers , map [string ]struct {}{}
101+ }
102+ waitForAIReleaseSpecialistApprove = waitForNativeApprove
103+ )
104+
77105func NewAIReleaseSpecialistJobCtl (job * commonmodels.JobTask , workflowCtx * commonmodels.WorkflowTaskCtx , ack func (), logger * zap.SugaredLogger ) * AIReleaseSpecialistJobCtl {
78106 jobTaskSpec := & commonmodels.JobTaskAIReleaseSpecialistSpec {}
79107 if err := commonmodels .IToi (job .Spec , jobTaskSpec ); err != nil {
@@ -102,7 +130,7 @@ func (c *AIReleaseSpecialistJobCtl) Run(ctx context.Context) {
102130 }
103131 defer cancel ()
104132
105- task , err := mongodb . NewworkflowTaskv4Coll (). Find (c .workflowCtx .WorkflowName , c .workflowCtx .TaskID )
133+ task , err := findWorkflowTaskForAIReleaseSpecialist (c .workflowCtx .WorkflowName , c .workflowCtx .TaskID )
106134 if err != nil {
107135 c .job .Status = config .StatusFailed
108136 c .job .Error = fmt .Sprintf ("find workflow task failed: %v" , err )
@@ -127,7 +155,7 @@ func (c *AIReleaseSpecialistJobCtl) Run(ctx context.Context) {
127155 return
128156 }
129157
130- client , err := getDefaultAIReleaseSpecialistLLMClient (jobCtx )
158+ client , err := getAIReleaseSpecialistLLMClient (jobCtx )
131159 if err != nil {
132160 c .job .Status = config .StatusFailed
133161 c .job .Error = fmt .Sprintf ("get default llm client failed: %v" , err )
@@ -168,7 +196,7 @@ func (c *AIReleaseSpecialistJobCtl) Run(ctx context.Context) {
168196 writeAIReleaseSpecialistOutputs (c .workflowCtx , c .job .Key , c .jobTaskSpec .Result )
169197 c .ack ()
170198
171- if result .Conclusion == "fail" {
199+ if result .Conclusion == "fail" && ! c . jobTaskSpec . RequireManualConfirm {
172200 c .job .Status = config .StatusFailed
173201 if result .Summary != "" {
174202 c .job .Error = result .Summary
@@ -207,7 +235,7 @@ func (c *AIReleaseSpecialistJobCtl) Run(ctx context.Context) {
207235 c .job .Status = config .StatusWaitingApprove
208236 c .ack ()
209237
210- status , err := waitForNativeApprove (jobCtx , approvalSpec , c .workflowCtx .WorkflowName , c .job .Name , c .workflowCtx .TaskID , c .ack )
238+ status , err := waitForAIReleaseSpecialistApprove (jobCtx , approvalSpec , c .workflowCtx .WorkflowName , c .job .Name , c .workflowCtx .TaskID , c .ack )
211239 c .job .Status = status
212240 if err != nil {
213241 c .job .Error = err .Error ()
@@ -248,7 +276,7 @@ func (c *AIReleaseSpecialistJobCtl) getRemainingTimeout(jobStartTime time.Time)
248276}
249277
250278func (c * AIReleaseSpecialistJobCtl ) getRuntimeConfirmUsers () ([]* commonmodels.User , error ) {
251- flatUsers , _ := commonutil . GeneFlatUsersWithCaller (c .jobTaskSpec .ConfirmUsers , c .workflowCtx .WorkflowTaskCreatorUserID )
279+ flatUsers , _ := getAIReleaseSpecialistConfirmUsers (c .jobTaskSpec .ConfirmUsers , c .workflowCtx .WorkflowTaskCreatorUserID )
252280 if len (flatUsers ) == 0 {
253281 return nil , fmt .Errorf ("confirm users are empty" )
254282 }
@@ -512,11 +540,12 @@ func BuildAIReleaseSpecialistPromptForDebug(promptTemplate, systemPromptOverride
512540 if err != nil {
513541 return nil , err
514542 }
515- systemPrompt := buildAIReleaseSpecialistSystemPrompt (string ( inputJSON ), systemPromptOverride )
543+ systemPrompt := buildAIReleaseSpecialistSystemPrompt (systemPromptOverride )
516544 prompt := systemPrompt
517545 if strings .TrimSpace (promptTemplate ) != "" {
518- prompt = fmt .Sprintf ("%s\n \n %s" , strings .TrimSpace (promptTemplate ), systemPrompt )
546+ prompt = fmt .Sprintf ("%s\n \n 额外关注点: \ n %s" , prompt , strings .TrimSpace (promptTemplate ))
519547 }
548+ prompt = fmt .Sprintf ("%s\n \n 发布上下文:\n ```json\n %s\n ```" , prompt , string (inputJSON ))
520549 promptTokens := getAIReleaseSpecialistPromptTokens (prompt )
521550 return & AIReleaseSpecialistPromptDebugResult {
522551 SystemPrompt : systemPrompt ,
@@ -526,12 +555,12 @@ func BuildAIReleaseSpecialistPromptForDebug(promptTemplate, systemPromptOverride
526555 }, nil
527556}
528557
529- func buildAIReleaseSpecialistSystemPrompt (inputJSON , systemPromptOverride string ) string {
558+ func buildAIReleaseSpecialistSystemPrompt (systemPromptOverride string ) string {
530559 systemPrompt := strings .TrimSpace (systemPromptOverride )
531560 if systemPrompt == "" {
532561 systemPrompt = defaultAIReleaseSpecialistSystemPrompt
533562 }
534- return fmt . Sprintf ( "%s \n \n 发布上下文: \n ```json \n %s \n ```" , systemPrompt , inputJSON )
563+ return systemPrompt
535564}
536565
537566func getAIReleaseSpecialistPromptTokens (prompt string ) int {
@@ -564,6 +593,7 @@ func ParseAIReleaseSpecialistResult(answer string) (*commonmodels.AIReleaseSpeci
564593 if result .Conclusion == "" {
565594 return nil , fmt .Errorf ("empty conclusion" )
566595 }
596+ result .Markdown = renderAIReleaseSpecialistResultMarkdown (result )
567597 return result , nil
568598}
569599
@@ -600,6 +630,19 @@ func normalizeAIResultValue(value string) string {
600630 }
601631}
602632
633+ func translateAIResultValue (value string ) string {
634+ switch normalizeAIResultValue (value ) {
635+ case "pass" :
636+ return "通过"
637+ case "warning" :
638+ return "需关注"
639+ case "fail" :
640+ return "不建议继续"
641+ default :
642+ return strings .TrimSpace (value )
643+ }
644+ }
645+
603646func writeAIReleaseSpecialistOutputs (workflowCtx * commonmodels.WorkflowTaskCtx , jobKey string , result * commonmodels.AIReleaseSpecialistResult ) {
604647 if workflowCtx == nil || result == nil {
605648 return
@@ -609,7 +652,22 @@ func writeAIReleaseSpecialistOutputs(workflowCtx *commonmodels.WorkflowTaskCtx,
609652 workflowCtx .GlobalContextSet (runtimejob .GetJobOutputKey (jobKey , "CONCLUSION" ), result .Conclusion )
610653 workflowCtx .GlobalContextSet (runtimejob .GetJobOutputKey (jobKey , "SUMMARY" ), result .Summary )
611654 workflowCtx .GlobalContextSet (runtimejob .GetJobOutputKey (jobKey , "CHECK_COUNT" ), fmt .Sprintf ("%d" , len (result .Checks )))
612- workflowCtx .GlobalContextSet (runtimejob .GetJobOutputKey (jobKey , "CHECK_DETAILS_MARKDOWN" ), renderCheckDetailsMarkdown (result .Checks ))
655+ workflowCtx .GlobalContextSet (runtimejob .GetJobOutputKey (jobKey , "CHECK_DETAILS_MARKDOWN" ), result .Markdown )
656+ }
657+
658+ func renderAIReleaseSpecialistResultMarkdown (result * commonmodels.AIReleaseSpecialistResult ) string {
659+ if result == nil {
660+ return ""
661+ }
662+ lines := []string {"## 检测结论" , translateAIResultValue (result .Conclusion )}
663+ if result .Summary != "" {
664+ lines = append (lines , "" , safeMarkdownText (result .Summary ))
665+ }
666+ if len (result .Checks ) > 0 {
667+ lines = append (lines , "" , "## 检测项明细" )
668+ lines = append (lines , renderCheckDetailsMarkdown (result .Checks ))
669+ }
670+ return strings .Join (lines , "\n " )
613671}
614672
615673func renderCheckDetailsMarkdown (checks []* commonmodels.AIReleaseSpecialistCheckItem ) string {
@@ -621,7 +679,8 @@ func renderCheckDetailsMarkdown(checks []*commonmodels.AIReleaseSpecialistCheckI
621679 if check == nil {
622680 continue
623681 }
624- lines = append (lines , fmt .Sprintf ("- %s [%s]" , safeMarkdownText (check .Name ), safeMarkdownText (check .Result )))
682+ lines = append (lines , fmt .Sprintf ("### %s" , safeMarkdownText (check .Name )))
683+ lines = append (lines , fmt .Sprintf ("- 检测结果: %s" , translateAIResultValue (check .Result )))
625684 if check .Evidence != "" {
626685 lines = append (lines , fmt .Sprintf (" - 依据: %s" , safeMarkdownText (check .Evidence )))
627686 }
0 commit comments