Files
Agent-System/internal/coordinator/engine_consult.go
T
weichen db859e185e fix(intent): 让「帮我定任务书」进入任务并说清失败
写动词补上制定/拟定与祈使「帮我定」,避免落到阶段对照模板。质量未通过不再声称正文已保存到宿主机路径;研发前置未满足时把中文原因直接回复给用户。
2026-09-28 04:01:02 +00:00

136 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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) {
if cand != nil && strings.TrimSpace(cand.ClarificationQuestion) != "" {
return &CoordinationResult{
Status: StatusClarificationRequired,
Intent: cand.Intent,
Branch: cand.Branch,
ClarificationPrompt: cand.ClarificationQuestion,
NaturalReply: cand.ClarificationQuestion,
UpdatedContext: userCtx,
}, nil
}
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)
}
// 通用非业务问句不得吃阶段推销模板;改道闲聊处理器(LLM 优先,失败回退问候模板)。
if intent.IsSmallTalkOrGeneralQuestion(rawInput) {
return e.handleCasualChat(ctx, 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 = maybeAttachUpgradeHintWithEngine(e, ctx, 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 = maybeAttachUpgradeHintWithEngine(e, ctx, 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
}