package coordinator import ( "context" "fmt" "strings" "chat-agent-system/internal/intent" ) func candidateFromFrame(frame intent.IntentFrameV2) *intent.CandidateIntent { slots := make(map[string]string, len(frame.Slots)) for k, v := range frame.Slots { slots[k] = v.Value } return &intent.CandidateIntent{ Intent: frame.LegacyIntent, Confidence: frame.Score, FilledSlots: slots, Reasoning: fmt.Sprintf("IntentPlan step action=%s", frame.ActionKey), } } // executeIntentPlan 顺序执行有限多意图计划(Phase 5:仅安全只读组合自动执行) func (e *CoordinatorEngine) executeIntentPlan( ctx context.Context, recRes *intent.IntentRecognitionResult, userCtx *UserProjectContext, ) (*CoordinationResult, error) { plan := recRes.IntentPlan if plan == nil || len(plan.Steps) == 0 || !intent.CurrentFeatureFlags().MultiIntentEnabled { return nil, nil } var parts []string var last *CoordinationResult var pendingConfirm *intent.IntentPlanStep for _, step := range plan.Steps { cand := candidateFromFrame(step.Frame) if step.Frame.Relation == intent.RelationExpectationHit || step.Frame.Goal == intent.GoalDecide || step.Frame.Goal == intent.GoalControl && isConfirmRequiredStep(&step) { pendingConfirm = &step continue } if !isAutoExecutableQueryStep(&step) { return nil, nil } stepRes, err := e.dispatchIntentHandler(ctx, cand, userCtx, recRes.RawInput) if err != nil { if plan.FailureMode == "stop_on_first_failure" { return nil, err } continue } if stepRes == nil { continue } last = stepRes if stepRes.NaturalReply != "" { parts = append(parts, fmt.Sprintf("**步骤 %s** %s", step.StepID, stepRes.NaturalReply)) } } if len(parts) == 0 && pendingConfirm == nil { return nil, nil } reply := strings.Join(parts, "\n\n") if pendingConfirm != nil { confirmNote := fmt.Sprintf( "另:您同时提到了「%s」。该操作需单独确认后再执行,请回复「确认」或选择对应选项。", pendingConfirm.Frame.ActionKey, ) if reply == "" { reply = confirmNote } else { reply += "\n\n" + confirmNote } } if last == nil { last = &CoordinationResult{UpdatedContext: userCtx} } last.NaturalReply = reply last.Intent = recRes.TopIntent.Intent last.Branch = recRes.Branch last.UpdatedContext = userCtx if pendingConfirm != nil && userCtx.PendingExpectation != nil { last.NextExpectation = userCtx.PendingExpectation } slots := map[string]string{} if recRes.TopIntent != nil && len(recRes.TopIntent.FilledSlots) > 0 { slots = recRes.TopIntent.FilledSlots } if last.AdvisorBrief == nil { last.AdvisorBrief = userCtx.LatestAdvisorBrief } if last.AdvisorBrief == nil { last.AdvisorBrief = synthesizeBaselineBrief(userCtx) } e.enrichAdvisorBriefWithCases(ctx, last.AdvisorBrief, userCtx, slots) return last, nil } func isAutoExecutableQueryStep(step *intent.IntentPlanStep) bool { if step == nil { return false } return step.Frame.Goal == intent.GoalQuery || intent.IsQueryLegacyIntent(step.Frame.LegacyIntent) } func isConfirmRequiredStep(step *intent.IntentPlanStep) bool { if step == nil { return false } spec, ok := intent.DefaultRegistry().Get(step.Frame.ActionKey) return ok && spec.RequiresConfirm } func (e *CoordinatorEngine) dispatchIntentHandler( ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext, rawInput string, ) (*CoordinationResult, error) { if cand == nil { return nil, nil } switch cand.Intent { case intent.IntentQueryProgress: return e.handleQueryProgress(ctx, cand, userCtx, rawInput) case intent.IntentQueryRegulations: return e.handleQueryRegulations(ctx, cand, userCtx) case intent.IntentQuerySimilarCases: if cand.FilledSlots[intent.SlotQueryTopic] == "materials" { return e.handleQueryMaterials(ctx, cand, userCtx, rawInput) } return e.handleQuerySimilarCases(ctx, cand, userCtx) case intent.IntentQueryUserRights: return e.handleQueryUserRights(ctx, cand, userCtx) case intent.IntentQueryWorkflowStatus: return e.handleQueryWorkflowStatus(ctx, cand, userCtx) case intent.IntentQueryDocStatus: return e.handleQueryDocStatus(ctx, cand, userCtx) case intent.IntentRunStatusQuery: return e.handleRunStatusQuery(ctx, cand, userCtx) case intent.IntentHelpCapabilities: return e.handleHelpCapabilities(ctx, cand, userCtx) default: return nil, nil } }