package runtime import ( "fmt" "regexp" "strings" agentllm "agent-native/runtime/llm" core "agent-system-core" ) // 工件编号和字节数只属于系统记录。收尾模型见过工具摘要后会把它们抄进回复。 var internalRef = regexp.MustCompile(`art_[0-9a-f]{8,}|\d[\d,]*\s*字节`) const composeFallbackPrompt = `你负责写给用户的最终回复。用户只看这一段,看不到上面的工作过程。 规则: - 只输出给用户看的一段话。不要写思考过程,不要使用 think 标签,不要写「根据规则」「所以我应该」。 - 直接回应用户的原始请求。先写结果和结论,再写交付了什么、要点是什么、还可以怎么调整。 - 不要复述过程,不要提内部检查、lint、重试、验证器、评分、文件路径、字节数或工件编号。 - 检查结论只用来判断有没有没做完的事。文件已经按用户的要求生成时,不要把评分或版式意见告诉用户。 - 只有用户的请求确实没完成时才说明,用用户能懂的话,不要引用检查原文。 - 不要编造材料里没有的内容。使用与用户相同的语言。` func (l *LLMRunner) composePrompt(owner string) string { if l.Prompts != nil { if body, ok, err := l.Prompts.ModuleBody(owner, "aux.compose.final"); err == nil && ok && strings.TrimSpace(body) != "" { return body } } return composeFallbackPrompt } // composeFinal 在工作循环结束后,用主模型单独写一段直接回答用户的回复。 // 失败或空结果时恢复草稿,不影响这一轮已经成功结束。 func (st *directRun) composeFinal(stream agentllm.StreamFunc, model, notice string, opts agentllm.StreamOptions) bool { _ = st.rc.Emit(core.EvRunPhase, core.RunPhasePayload{Phase: core.PhaseFinalizing}) draftIdx := st.demoteLastText() draft := "" if draftIdx >= 0 { draft = st.finalParts[draftIdx].Text } msgs := []agentllm.Message{ {Role: agentllm.RoleSystem, Content: st.l.composePrompt(st.rc.Profile().Owner)}, {Role: agentllm.RoleUser, Content: st.composeUserPrompt(draft, notice)}, } var b strings.Builder failed := false for ev := range stream(st.rc.Context(), model, msgs, nil, opts) { switch ev.Kind { case agentllm.KindTextDelta: b.WriteString(ev.Text) case agentllm.KindError: failed = true case agentllm.KindMessageEnd: st.usageTotal.PromptTokens += ev.Usage.PromptTokens st.usageTotal.CompletionTokens += ev.Usage.CompletionTokens st.usageTotal.TotalTokens += ev.Usage.TotalTokens if b.Len() == 0 { b.WriteString(ev.Message.Content) } } } // 有的模型把推理放进 think 再写正文。推理不发给用户;只发出剥离后的正文。 body, _ := splitThinkBlock(b.String()) body = scrubInternal(body) if failed || body == "" || strings.Contains(body, "") { if draftIdx >= 0 && draftIdx < len(st.finalParts) && st.finalParts[draftIdx].Type == core.PartText { st.finalParts[draftIdx].Process = false } return false } _ = st.rc.Emit(core.EvMessageDelta, core.MessageDeltaPayload{Index: st.textIdx, Text: body}) st.finalParts = append(st.finalParts, core.Part{Type: core.PartText, Text: body}) st.textIdx++ return true } func (st *directRun) composeUserPrompt(draft, notice string) string { var b strings.Builder b.WriteString("用户的原始请求:\n") if user := strings.TrimSpace(currentUserText(st.rc)); user == "" { b.WriteString("(无)\n") } else { b.WriteString(user) b.WriteString("\n") } b.WriteString("\n工作过程中的草稿回复(不要照抄过程,把它整理成给用户的回复):\n") if strings.TrimSpace(draft) == "" { b.WriteString("(无)\n") } else { b.WriteString(scrubInternal(draft)) b.WriteString("\n") } b.WriteString("\n工具过程:\n") if len(st.evidence) == 0 { b.WriteString("(无)\n") } for i, ev := range st.evidence { fmt.Fprintf(&b, "#%d %s %s %s\n", i+1, ev.Name, ev.Status, scrubInternal(ev.Summary)) } b.WriteString("\n交付物:\n") names := 0 for _, ref := range st.latestDeliverables() { if ref == nil || strings.TrimSpace(ref.Name) == "" { continue } fmt.Fprintf(&b, "- %s\n", ref.Name) names++ } if names == 0 { b.WriteString("(无)\n") } b.WriteString("\n检查结论:\n") if len(st.checks) == 0 { b.WriteString("(无)\n") } for _, c := range st.checks { if c.Passed { fmt.Fprintf(&b, "- 通过:%s\n", scrubInternal(c.Reason)) continue } fmt.Fprintf(&b, "- 未通过:%s\n", scrubInternal(c.Reason)) } if strings.TrimSpace(notice) != "" { b.WriteString("\n仍未完成(必须在回复里如实说明,不要假装已经完成):\n") b.WriteString(scrubInternal(notice)) b.WriteString("\n") } return b.String() } func scrubInternal(s string) string { s = internalRef.ReplaceAllString(s, "") s = strings.ReplaceAll(s, "()", "") s = strings.ReplaceAll(s, "()", "") return strings.TrimSpace(s) }