Skip to content

Commit 02ce344

Browse files
feat: refine ai release specialist prompt and result output
Signed-off-by: huanghongbo-hhb <huanghongbo@koderover.com>
1 parent b85bf2a commit 02ce344

3 files changed

Lines changed: 279 additions & 27 deletions

File tree

pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,7 @@ type AIReleaseSpecialistResult struct {
738738
Conclusion string `json:"conclusion"`
739739
Summary string `json:"summary"`
740740
Checks []*AIReleaseSpecialistCheckItem `json:"checks"`
741+
Markdown string `json:"markdown,omitempty"`
741742
RawText string `json:"raw_text"`
742743
}
743744

pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_ai_release_specialist.go

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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

6278
type 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+
77105
func 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

250278
func (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

537566
func 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+
603646
func 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

615673
func 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

Comments
 (0)