package coordinator import ( "context" "fmt" "strings" "chat-agent-system/internal/intent" "chat-agent-system/internal/maturity" ) // handleAmbiguousConsult 处理用户犹豫或征询建议 (分支 3 模糊分忧) func (e *CoordinatorEngine) handleAmbiguousConsult(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext, rawInput string) (*CoordinationResult, error) { currentStage := userCtx.Project.CurrentStage projName := userCtx.Project.Name if projName == "" { projName = "当前项目" } // 只在低置信度的模糊咨询里,才用关键词改走测试分析。高置信度保持原意图。 if cand.Confidence < 0.6 && (strings.Contains(rawInput, "测试") || strings.Contains(rawInput, "验证") || strings.Contains(rawInput, "型检") || strings.Contains(rawInput, "难点") || strings.Contains(rawInput, "注意") || strings.Contains(rawInput, "比较难") || strings.Contains(rawInput, "方案")) { if cand.FilledSlots == nil { cand.FilledSlots = make(map[string]string) } cand.FilledSlots[intent.SlotQueryTopic] = "testing_analysis" return e.handleQueryProgress(ctx, cand, userCtx, rawInput) } var sb strings.Builder sb.WriteString(fmt.Sprintf("完全理解您想再掂量一下。针对「%s」目前的节点,我帮您快速对照一下:\n\n", projName)) if currentStage == maturity.StageV01_Triage { sb.WriteString("• 若推进 V0.5(方案成型与法规对齐):\n") sb.WriteString(" 我可帮您梳理初步架构与注册分类路径,并预估型检成本,便于与客户深入沟通;\n") sb.WriteString("• 若先放一放 / 多做调研:\n") sb.WriteString(" 可先补齐预算、目标市场或使用场景等信息,降低后续返工风险。\n\n") sb.WriteString("💡 如果您接下来更希望先有一份可审阅的材料,我可以先整理一版 V0.5 方案框架与法规路径;节奏仍由您决定。\n\n") sb.WriteString("您更倾向于:\n") sb.WriteString("[1] 先做 V0.5 方案框架(可直接回复“好”或“1”)\n") sb.WriteString("[2] 先说明您当前最关心的细节,我做针对性分析") } else { sb.WriteString("当前各项工作进展平稳。您可以把零散想法随时告诉我,我来帮您沉淀;时机成熟时,再一起推进下一阶段也完全可以。") } reply := sb.String() source := ReplySourceTemplate if llmReply, llmSource := e.generateDialogueReply(ctx, rawInput, userCtx); llmReply != "" { reply = llmReply source = llmSource } nextExp := BuildNextExpectation(currentStage, projName) // 路由层:复杂自由任务画像命中时,先给即时初答,再附升级建议(复用挂起期望机制,含每会话冷却) reply, nextExp = maybeAttachUpgradeHint(rawInput, userCtx, reply, nextExp) userCtx.applyPending(nextExp) return &CoordinationResult{ Status: StatusAccepted, Intent: cand.Intent, Branch: intent.BranchAmbiguousFallback, NaturalReply: reply, AdvisorBrief: userCtx.LatestAdvisorBrief, UpdatedContext: userCtx, NextExpectation: nextExp, ReplySource: source, }, nil } func (e *CoordinatorEngine) handleCasualChat(ctx context.Context, userCtx *UserProjectContext, rawInput string) (*CoordinationResult, error) { projName := userCtx.Project.Name if projName == "" { projName = "当前医疗器械研发项目" } nextExp := BuildNextExpectation(userCtx.Project.CurrentStage, userCtx.Project.Name) userCtx.applyPending(nextExp) nameSuffix := "" if userCtx.User.Name != "" { nameSuffix = "," + userCtx.User.Name } reply := fmt.Sprintf("您好%s!我是您的项目助手,很高兴协助您推进医疗器械立项与研发协同。\n\n针对「%s」,我可以帮您:\n• 查询进展、成熟度与风险;\n• 沉淀客户需求与技术指标;\n• 起草沟通话术与方案要点;\n• 推进方案论证与报价核算。\n\n如果您愿意,我们可以先从您当前最关心的一块开始——您想先看进展、补事实,还是直接聊方案?", nameSuffix, projName) source := ReplySourceTemplate if llmReply, llmSource := e.generateDialogueReply(ctx, rawInput, userCtx); llmReply != "" { reply = llmReply source = llmSource } // 路由层:复杂自由任务画像命中时,先给即时初答,再附升级建议(复用挂起期望机制,含每会话冷却) reply, nextExp = maybeAttachUpgradeHint(rawInput, userCtx, reply, nextExp) userCtx.applyPending(nextExp) return &CoordinationResult{ Status: StatusAccepted, Intent: intent.IntentCasualChat, Branch: intent.BranchNewIntentPreempt, NaturalReply: reply, AdvisorBrief: userCtx.LatestAdvisorBrief, UpdatedContext: userCtx, NextExpectation: nextExp, ReplySource: source, }, nil } // handleSecurityForbidden 处理安全红线与合规拦截 (ISO 13485 / 21 CFR Part 11) func (e *CoordinatorEngine) handleSecurityForbidden(userCtx *UserProjectContext) (*CoordinationResult, error) { reply := "⚠️ **合规权限拦截**:根据医疗器械质量管理规范(ISO 13485 / 21 CFR Part 11),企业人员的法定身份(姓名、工号、职位、部门)与系统控制台属于基线受控资产,不支持通过对话动态修改。您可通过‘查询我的权限’查看您在当前各文档中的法定职责。" return &CoordinationResult{ Status: StatusRejected, Intent: intent.IntentSecurityForbidden, Branch: intent.BranchNewIntentPreempt, RejectReason: "SECURITY_FORBIDDEN_BASELINE_ASSET", NaturalReply: reply, UpdatedContext: userCtx, }, nil }