587 lines
22 KiB
Go
587 lines
22 KiB
Go
package coordinator
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"chat-agent-system/internal/bizdev"
|
|
"chat-agent-system/internal/intent"
|
|
"chat-agent-system/internal/squad"
|
|
"chat-agent-system/internal/stream"
|
|
)
|
|
|
|
// CoordinationStatus 协调裁决状态
|
|
type CoordinationStatus string
|
|
|
|
const (
|
|
StatusAccepted CoordinationStatus = "ACCEPTED"
|
|
StatusRejected CoordinationStatus = "REJECTED"
|
|
StatusClarificationRequired CoordinationStatus = "CLARIFICATION_REQUIRED"
|
|
)
|
|
|
|
// CoordinationResult 协调层给对话层的回执
|
|
type CoordinationResult struct {
|
|
Status CoordinationStatus `json:"status"`
|
|
Intent intent.IntentType `json:"intent"`
|
|
Branch intent.ResolutionBranch `json:"branch,omitempty"`
|
|
RejectReason string `json:"reject_reason,omitempty"`
|
|
ClarificationPrompt string `json:"clarification_prompt,omitempty"`
|
|
NaturalReply string `json:"natural_reply"`
|
|
AdvisorBrief *squad.AdvisorBriefing `json:"advisor_brief,omitempty"`
|
|
ArtifactPath string `json:"artifact_path,omitempty"`
|
|
UpdatedContext *UserProjectContext `json:"updated_context"`
|
|
NextExpectation *intent.PendingExpectation `json:"next_expectation,omitempty"`
|
|
ClearExpectation bool `json:"clear_expectation,omitempty"`
|
|
}
|
|
|
|
// CoordinatorEngine L2 协调引擎
|
|
type CoordinatorEngine struct {
|
|
bizdevSvc bizdev.BizdevService
|
|
assembler *ContextAssembler
|
|
}
|
|
|
|
func NewCoordinatorEngine(bizdevSvc bizdev.BizdevService) *CoordinatorEngine {
|
|
return &CoordinatorEngine{
|
|
bizdevSvc: bizdevSvc,
|
|
assembler: NewContextAssembler(),
|
|
}
|
|
}
|
|
|
|
// ProcessIntent 校验候选意图合法性并驱动调度与响应
|
|
func (e *CoordinatorEngine) ProcessIntent(
|
|
ctx context.Context,
|
|
recRes *intent.IntentRecognitionResult,
|
|
userCtx *UserProjectContext,
|
|
) (*CoordinationResult, error) {
|
|
if userCtx == nil {
|
|
userCtx = &UserProjectContext{
|
|
User: UserInfo{ID: "guest", Name: "用户", Role: "engineer", ProjectRole: "composer"},
|
|
BlackboardFacts: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
top := recRes.TopIntent
|
|
if top == nil {
|
|
top = &intent.CandidateIntent{
|
|
Intent: intent.IntentCasualChat,
|
|
Confidence: 1.0,
|
|
}
|
|
}
|
|
|
|
streamer := stream.FromContext(ctx)
|
|
if streamer != nil {
|
|
streamer.EmitStatus(string(top.Intent), string(recRes.Branch), string(userCtx.Project.CurrentStage), fmt.Sprintf("意图判定: %s (置信度: %.0f%%)", top.Intent, top.Confidence*100), top.FilledSlots)
|
|
}
|
|
|
|
// 1. 若为分支 3 (模糊/分忧引导),触发参谋主动分析与建议
|
|
if recRes.Branch == intent.BranchAmbiguousFallback {
|
|
return e.handleAmbiguousConsult(top, userCtx)
|
|
}
|
|
|
|
// 2. 若缺少核心槽位且需澄清
|
|
if recRes.RequiresClarifying && top.ClarificationQuestion != "" {
|
|
return &CoordinationResult{
|
|
Status: StatusClarificationRequired,
|
|
Intent: top.Intent,
|
|
Branch: recRes.Branch,
|
|
ClarificationPrompt: top.ClarificationQuestion,
|
|
NaturalReply: top.ClarificationQuestion,
|
|
UpdatedContext: userCtx,
|
|
}, nil
|
|
}
|
|
|
|
// 3. 根据候选意图执行分支
|
|
switch top.Intent {
|
|
case intent.IntentDeclineExpectation:
|
|
return e.handleDeclineExpectation(userCtx)
|
|
|
|
case intent.IntentQueryProgress:
|
|
return e.handleQueryProgress(top, userCtx)
|
|
|
|
case intent.IntentSupplementInfo:
|
|
return e.handleSupplementInfo(top, userCtx)
|
|
|
|
case intent.IntentAdvanceStage:
|
|
return e.handleAdvanceStage(ctx, top, userCtx)
|
|
|
|
case intent.IntentTriggerTask:
|
|
return e.handleTriggerTask(ctx, top, userCtx)
|
|
|
|
case intent.IntentCasualChat:
|
|
return e.handleCasualChat(userCtx)
|
|
|
|
default:
|
|
nextExp := BuildNextExpectation(userCtx.Project.CurrentStage, userCtx.Project.Name)
|
|
return &CoordinationResult{
|
|
Status: StatusAccepted,
|
|
Intent: top.Intent,
|
|
Branch: recRes.Branch,
|
|
NaturalReply: "我已收到您的指示。针对当前项目,请问需要我为您查询进展、补充技术事实,还是推进方案论证?随时吩咐我。",
|
|
UpdatedContext: userCtx,
|
|
NextExpectation: nextExp,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
// handleDeclineExpectation 处理用户暂缓或取消上轮建议 (分支 1 放弃)
|
|
func (e *CoordinatorEngine) handleDeclineExpectation(userCtx *UserProjectContext) (*CoordinationResult, error) {
|
|
projName := userCtx.Project.Name
|
|
if projName == "" {
|
|
projName = "当前项目"
|
|
}
|
|
userCtx.PendingExpectation = nil
|
|
|
|
reply := fmt.Sprintf("收到,听您的!那我们先不急着推进。\n针对「%s」,您手头还有哪些细节、技术顾虑或商务考量想先聊聊?我随时待命为您解答。", projName)
|
|
|
|
return &CoordinationResult{
|
|
Status: StatusAccepted,
|
|
Intent: intent.IntentDeclineExpectation,
|
|
Branch: intent.BranchExpectationHit,
|
|
NaturalReply: reply,
|
|
UpdatedContext: userCtx,
|
|
ClearExpectation: true,
|
|
}, nil
|
|
}
|
|
|
|
// handleAmbiguousConsult 处理用户犹豫或征询建议 (分支 3 模糊分忧)
|
|
func (e *CoordinatorEngine) handleAmbiguousConsult(cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
|
currentStage := userCtx.Project.CurrentStage
|
|
projName := userCtx.Project.Name
|
|
if projName == "" {
|
|
projName = "当前项目"
|
|
}
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString(fmt.Sprintf("我非常理解您的审慎考量!针对「%s」目前的决策节点,我为您做个客观分析:\n\n", projName))
|
|
|
|
if currentStage == bizdev.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("当前各项工作进展平稳。建议您可以随时把手头的零散想法告诉我,我来为您沉淀事实;或者等您时机成熟时,随时吩咐我推进下一阶段。")
|
|
}
|
|
|
|
nextExp := BuildNextExpectation(currentStage, projName)
|
|
userCtx.PendingExpectation = nextExp
|
|
|
|
return &CoordinationResult{
|
|
Status: StatusAccepted,
|
|
Intent: cand.Intent,
|
|
Branch: intent.BranchAmbiguousFallback,
|
|
NaturalReply: sb.String(),
|
|
AdvisorBrief: userCtx.LatestAdvisorBrief,
|
|
UpdatedContext: userCtx,
|
|
NextExpectation: nextExp,
|
|
}, nil
|
|
}
|
|
|
|
func (e *CoordinatorEngine) handleQueryProgress(cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
|
brief := userCtx.LatestAdvisorBrief
|
|
topic := cand.FilledSlots[intent.SlotQueryTopic]
|
|
nextExp := BuildNextExpectation(userCtx.Project.CurrentStage, userCtx.Project.Name)
|
|
userCtx.PendingExpectation = nextExp
|
|
|
|
// 1. 若为特定技术/架构/功耗方案咨询,直接调取参谋专业知识库解答,不强依赖阶段简报
|
|
if topic == "technical_design" {
|
|
var sb strings.Builder
|
|
sb.WriteString(fmt.Sprintf("关于「%s」的技术与系统架构考量,我为您梳理以下核心要点:\n", userCtx.Project.Name))
|
|
sb.WriteString("• 供电与功耗设计:建议选用高能效前端模拟芯片与智能休眠调度,兼顾便携连续工作时间与温升控制;\n")
|
|
sb.WriteString("• 电气安全与标准:设备需符合 GB 9706.1-2020 通用安全与医用电气专项要求,重点评估外壳防护等级与绝缘耐压;\n")
|
|
sb.WriteString("• 供应链选型:关键芯片与传感器优先考虑国内高成熟度元器件,缩短备料周期与型检验证周期。\n\n")
|
|
sb.WriteString("这项考量已为您收录,后续进入方案成型(V0.5)时,小队将作为核心约束直接带入论证。")
|
|
|
|
return &CoordinationResult{
|
|
Status: StatusAccepted,
|
|
Intent: cand.Intent,
|
|
Branch: cand.Branch,
|
|
NaturalReply: sb.String(),
|
|
AdvisorBrief: brief,
|
|
UpdatedContext: userCtx,
|
|
NextExpectation: nextExp,
|
|
}, nil
|
|
}
|
|
|
|
if brief == nil {
|
|
reply := fmt.Sprintf("您好,项目「%s」当前处于初始准备期。我建议先行为您启动【V0.1 闪电摸底】,快速沉淀第一版立项画像并为您准备沟通话术。您希望现在开始吗?",
|
|
userCtx.Project.Name)
|
|
return &CoordinationResult{
|
|
Status: StatusAccepted,
|
|
Intent: cand.Intent,
|
|
Branch: cand.Branch,
|
|
NaturalReply: reply,
|
|
UpdatedContext: userCtx,
|
|
NextExpectation: nextExp,
|
|
}, nil
|
|
}
|
|
|
|
var sb strings.Builder
|
|
switch topic {
|
|
|
|
case "risk":
|
|
sb.WriteString(fmt.Sprintf("关于「%s」的潜在风险,我为您提前预警以下关键卡点:\n", userCtx.Project.Name))
|
|
for _, r := range brief.PreemptiveRisks {
|
|
sb.WriteString(fmt.Sprintf("• ⚠️ %s\n", r))
|
|
}
|
|
sb.WriteString("\n建议您在后续与客户沟通时预先留出安全边际。")
|
|
|
|
case "talk_script":
|
|
sb.WriteString("这是为您草拟的对外商务沟通话术,您可以直接使用或微调:\n\n")
|
|
sb.WriteString(brief.CommunicationKit)
|
|
|
|
case "progress":
|
|
sb.WriteString(fmt.Sprintf("【进展汇报】 项目当前成熟度等级为 %s(评分 %d/100)。\n", brief.MaturityLevel, brief.MaturityScore))
|
|
sb.WriteString(fmt.Sprintf("诊断结论:%s\n\n", brief.MaturityHeadline))
|
|
if len(brief.NextBestActions) > 0 {
|
|
sb.WriteString("建议下一步选项:\n")
|
|
for idx, a := range brief.NextBestActions {
|
|
sb.WriteString(fmt.Sprintf("[%d] %s\n", idx+1, a))
|
|
}
|
|
}
|
|
|
|
default: // all
|
|
sb.WriteString(fmt.Sprintf("您好!项目「%s」当前处于【%s】阶段(成熟度 %d/100)。\n",
|
|
userCtx.Project.Name, brief.MaturityLevel, brief.MaturityScore))
|
|
sb.WriteString(fmt.Sprintf("核心研判:%s\n\n", brief.MaturityHeadline))
|
|
if len(brief.PreemptiveRisks) > 0 {
|
|
sb.WriteString("⚠️ 关键风险预警:\n")
|
|
for _, r := range brief.PreemptiveRisks {
|
|
sb.WriteString(fmt.Sprintf(" • %s\n", r))
|
|
}
|
|
sb.WriteString("\n")
|
|
}
|
|
if len(brief.NextBestActions) > 0 {
|
|
sb.WriteString("您可以选择下一步动作:\n")
|
|
for idx, a := range brief.NextBestActions {
|
|
sb.WriteString(fmt.Sprintf(" [%d] %s\n", idx+1, a))
|
|
}
|
|
}
|
|
}
|
|
|
|
return &CoordinationResult{
|
|
Status: StatusAccepted,
|
|
Intent: cand.Intent,
|
|
Branch: cand.Branch,
|
|
NaturalReply: sb.String(),
|
|
AdvisorBrief: brief,
|
|
UpdatedContext: userCtx,
|
|
NextExpectation: nextExp,
|
|
}, nil
|
|
}
|
|
|
|
func (e *CoordinatorEngine) handleSupplementInfo(cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
|
factKey := cand.FilledSlots[intent.SlotFactKey]
|
|
factVal := cand.FilledSlots[intent.SlotFactValue]
|
|
rawDoc := cand.FilledSlots[intent.SlotSupplementDoc]
|
|
|
|
if userCtx.BlackboardFacts == nil {
|
|
userCtx.BlackboardFacts = make(map[string]string)
|
|
}
|
|
|
|
summaryKey := ""
|
|
if factKey != "" && factVal != "" {
|
|
userCtx.BlackboardFacts[factKey] = factVal
|
|
summaryKey = fmt.Sprintf("「%s」已更新为「%s」", factKey, factVal)
|
|
if factKey == "target_market" {
|
|
userCtx.Project.TargetMarket = factVal
|
|
}
|
|
} else {
|
|
summaryKey = "已补充新的背景信息"
|
|
}
|
|
|
|
streamer := stream.FromContext(context.Background())
|
|
if streamer != nil {
|
|
streamer.EmitStatus(string(cand.Intent), string(cand.Branch), string(userCtx.Project.CurrentStage), fmt.Sprintf("已吸收最新事实设定:%s", summaryKey), cand.FilledSlots)
|
|
}
|
|
|
|
// 持久化到 blackboard.json
|
|
if userCtx.WorkDir != "" {
|
|
bbPath := filepath.Join(userCtx.WorkDir, "blackboard.json")
|
|
var bb squad.BlackboardState
|
|
if data, err := os.ReadFile(bbPath); err == nil {
|
|
_ = json.Unmarshal(data, &bb)
|
|
}
|
|
if bb.Fields == nil {
|
|
bb.Fields = make(map[string]string)
|
|
}
|
|
for k, v := range userCtx.BlackboardFacts {
|
|
bb.Fields[k] = v
|
|
}
|
|
if b, err := json.MarshalIndent(bb, "", " "); err == nil {
|
|
_ = os.WriteFile(bbPath, b, 0o644)
|
|
}
|
|
}
|
|
|
|
nextExp := BuildNextExpectation(userCtx.Project.CurrentStage, userCtx.Project.Name)
|
|
userCtx.PendingExpectation = nextExp
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString(fmt.Sprintf("已收到并为您记录最新信息:%s。\n", summaryKey))
|
|
sb.WriteString("这些事实已沉淀至项目黑板,后续论证与核价将直接吸收这些设定。\n\n")
|
|
sb.WriteString(fmt.Sprintf("针对「%s」,我为您梳理了接下来的推进选项:\n", userCtx.Project.Name))
|
|
for idx, opt := range nextExp.Options {
|
|
recMark := ""
|
|
if idx == 0 {
|
|
recMark = "(推荐,可直接回复“好”或“1”)"
|
|
}
|
|
sb.WriteString(fmt.Sprintf("[%s] %s %s\n", opt.OptionID, opt.Title, recMark))
|
|
}
|
|
|
|
if rawDoc != "" && summaryKey == "已补充新的背景信息" {
|
|
sb.Reset()
|
|
sb.WriteString("已为您收录补充材料,并已关联至项目事实黑板。\n在后续的文档修订和阶段演进中,智能体小队将自动引用这部分事实。")
|
|
}
|
|
|
|
return &CoordinationResult{
|
|
Status: StatusAccepted,
|
|
Intent: cand.Intent,
|
|
Branch: cand.Branch,
|
|
NaturalReply: sb.String(),
|
|
AdvisorBrief: userCtx.LatestAdvisorBrief,
|
|
UpdatedContext: userCtx,
|
|
NextExpectation: nextExp,
|
|
}, nil
|
|
}
|
|
|
|
func (e *CoordinatorEngine) handleAdvanceStage(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
|
targetStageStr := cand.FilledSlots[intent.SlotTargetStage]
|
|
targetStage := bizdev.MaturityStage(targetStageStr)
|
|
if targetStage == "" {
|
|
targetStage = bizdev.StageV05_Feasibility
|
|
}
|
|
|
|
streamer := stream.FromContext(ctx)
|
|
if streamer != nil {
|
|
streamer.EmitPhase("stage_transition", fmt.Sprintf("准备推进项目成熟度阶段至【%s】...", targetStage), "coordinator", 0, 0, 0)
|
|
}
|
|
|
|
// 1. 若携带补丁槽位 (例如 "好,但目标市场改成欧洲CE"),先行持久化更新
|
|
patchSummary := ""
|
|
if len(cand.PatchSlots) > 0 {
|
|
var patches []string
|
|
for k, v := range cand.PatchSlots {
|
|
userCtx.BlackboardFacts[k] = v
|
|
patches = append(patches, fmt.Sprintf("%s=%s", k, v))
|
|
if k == "target_market" {
|
|
userCtx.Project.TargetMarket = v
|
|
}
|
|
}
|
|
patchSummary = strings.Join(patches, ", ")
|
|
|
|
if userCtx.WorkDir != "" {
|
|
bbPath := filepath.Join(userCtx.WorkDir, "blackboard.json")
|
|
var bb squad.BlackboardState
|
|
if data, err := os.ReadFile(bbPath); err == nil {
|
|
_ = json.Unmarshal(data, &bb)
|
|
}
|
|
if bb.Fields == nil {
|
|
bb.Fields = make(map[string]string)
|
|
}
|
|
for k, v := range cand.PatchSlots {
|
|
bb.Fields[k] = v
|
|
}
|
|
if b, err := json.MarshalIndent(bb, "", " "); err == nil {
|
|
_ = os.WriteFile(bbPath, b, 0o644)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. 状态机合法性检查
|
|
if userCtx.Project.CurrentStage == targetStage {
|
|
return &CoordinationResult{
|
|
Status: StatusRejected,
|
|
Intent: cand.Intent,
|
|
Branch: cand.Branch,
|
|
RejectReason: "ALREADY_IN_STAGE",
|
|
NaturalReply: fmt.Sprintf("项目目前已经在【%s】阶段中,无需重复推进。您可以吩咐我进行详细论证或查询当前进展。", targetStage),
|
|
UpdatedContext: userCtx,
|
|
}, nil
|
|
}
|
|
|
|
if e.bizdevSvc == nil {
|
|
userCtx.Project.CurrentStage = targetStage
|
|
userCtx.AvailableActions = ComputeAvailableActions(targetStage, userCtx.User.ProjectRole)
|
|
nextExp := BuildNextExpectation(targetStage, userCtx.Project.Name)
|
|
userCtx.PendingExpectation = nextExp
|
|
|
|
reply := fmt.Sprintf("已为您模拟推进到阶段【%s】。", targetStage)
|
|
if patchSummary != "" {
|
|
reply = fmt.Sprintf("已为您吸收最新设定(%s),并模拟推进到阶段【%s】。", patchSummary, targetStage)
|
|
}
|
|
return &CoordinationResult{
|
|
Status: StatusAccepted,
|
|
Intent: cand.Intent,
|
|
Branch: cand.Branch,
|
|
NaturalReply: reply,
|
|
UpdatedContext: userCtx,
|
|
NextExpectation: nextExp,
|
|
}, nil
|
|
}
|
|
|
|
// 3. 调用底层 L3 调度器执行阶段跃迁
|
|
stageRes, err := e.bizdevSvc.ExecuteStage(ctx, bizdev.StageRunRequest{
|
|
Stage: targetStage,
|
|
ProjectName: userCtx.Project.Name,
|
|
CustomerName: userCtx.Project.CustomerName,
|
|
DeviceType: userCtx.Project.DeviceType,
|
|
IntendedUse: userCtx.Project.IntendedUse,
|
|
TargetMarket: userCtx.Project.TargetMarket,
|
|
Fields: userCtx.BlackboardFacts,
|
|
WorkDir: userCtx.WorkDir,
|
|
})
|
|
if err != nil {
|
|
return &CoordinationResult{
|
|
Status: StatusRejected,
|
|
Intent: cand.Intent,
|
|
Branch: cand.Branch,
|
|
RejectReason: err.Error(),
|
|
NaturalReply: fmt.Sprintf("阶段推进失败: %v", err),
|
|
UpdatedContext: userCtx,
|
|
}, err
|
|
}
|
|
|
|
userCtx.Project.CurrentStage = targetStage
|
|
userCtx.LatestAdvisorBrief = stageRes.AdvisorBrief
|
|
userCtx.AvailableActions = ComputeAvailableActions(targetStage, userCtx.User.ProjectRole)
|
|
if stageRes.WorkDir != "" {
|
|
userCtx.WorkDir = stageRes.WorkDir
|
|
}
|
|
|
|
artifactPath := ""
|
|
if stageRes.PrimaryArtifact != nil {
|
|
artifactPath = filepath.Join(stageRes.PrimaryArtifact.WorkDir, "output.md")
|
|
}
|
|
|
|
nextExp := BuildNextExpectation(targetStage, userCtx.Project.Name)
|
|
userCtx.PendingExpectation = nextExp
|
|
|
|
var sb strings.Builder
|
|
if patchSummary != "" {
|
|
sb.WriteString(fmt.Sprintf("已为您吸收最新设定(%s),并成功推进并完成了【%s】!\n", patchSummary, stageRes.StageTitle))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("已为您成功推进并完成了【%s】!\n", stageRes.StageTitle))
|
|
}
|
|
sb.WriteString("智能体小队已协同完成相应论证与文档沉淀。\n\n")
|
|
sb.WriteString(fmt.Sprintf("💡 最新进展诊断:%s\n", stageRes.AdvisorBrief.MaturityHeadline))
|
|
sb.WriteString(fmt.Sprintf("建议您可以审阅正式交付文档(%s),或按最新参谋建议开展后续商务洽谈。\n\n", artifactPath))
|
|
sb.WriteString("后续推进选项(供您掌舵决策):\n")
|
|
for idx, opt := range nextExp.Options {
|
|
recMark := ""
|
|
if idx == 0 {
|
|
recMark = "(推荐,可直接回复“好”或“1”)"
|
|
}
|
|
sb.WriteString(fmt.Sprintf("[%s] %s %s\n", opt.OptionID, opt.Title, recMark))
|
|
}
|
|
|
|
return &CoordinationResult{
|
|
Status: StatusAccepted,
|
|
Intent: cand.Intent,
|
|
Branch: cand.Branch,
|
|
NaturalReply: sb.String(),
|
|
AdvisorBrief: stageRes.AdvisorBrief,
|
|
ArtifactPath: artifactPath,
|
|
UpdatedContext: userCtx,
|
|
NextExpectation: nextExp,
|
|
}, nil
|
|
}
|
|
|
|
func (e *CoordinatorEngine) handleTriggerTask(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
|
taskKey := cand.FilledSlots[intent.SlotTargetTask]
|
|
if taskKey == "" {
|
|
taskKey = "intake"
|
|
}
|
|
|
|
nextExp := BuildNextExpectation(userCtx.Project.CurrentStage, userCtx.Project.Name)
|
|
userCtx.PendingExpectation = nextExp
|
|
|
|
if e.bizdevSvc == nil {
|
|
return &CoordinationResult{
|
|
Status: StatusAccepted,
|
|
Intent: cand.Intent,
|
|
Branch: cand.Branch,
|
|
NaturalReply: fmt.Sprintf("已收到生成【%s】的指令。", taskKey),
|
|
UpdatedContext: userCtx,
|
|
NextExpectation: nextExp,
|
|
}, nil
|
|
}
|
|
|
|
taskRes, err := e.bizdevSvc.ExecuteTask(ctx, bizdev.TaskRunRequest{
|
|
TaskKey: taskKey,
|
|
ProjectName: userCtx.Project.Name,
|
|
CustomerName: userCtx.Project.CustomerName,
|
|
DeviceType: userCtx.Project.DeviceType,
|
|
IntendedUse: userCtx.Project.IntendedUse,
|
|
TargetMarket: userCtx.Project.TargetMarket,
|
|
Fields: userCtx.BlackboardFacts,
|
|
})
|
|
if err != nil {
|
|
return &CoordinationResult{
|
|
Status: StatusRejected,
|
|
Intent: cand.Intent,
|
|
Branch: cand.Branch,
|
|
RejectReason: err.Error(),
|
|
NaturalReply: fmt.Sprintf("任务执行失败: %v", err),
|
|
UpdatedContext: userCtx,
|
|
}, err
|
|
}
|
|
|
|
userCtx.LatestAdvisorBrief = taskRes.AdvisorBrief
|
|
outPath := filepath.Join(taskRes.WorkDir, "output.md")
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString(fmt.Sprintf("已为您自适应规划并完成生成【%s】!\n交付物正文已保存至:%s\n耗时: %v\n",
|
|
taskRes.ArtifactTitle, outPath, taskRes.Duration))
|
|
|
|
if taskRes.AdvisorBrief != nil && len(taskRes.AdvisorBrief.BaselineAssumptions) > 0 {
|
|
sb.WriteString("\n📌 智能体小队预设的基准假设 (已融入方案,您可随时修正):\n")
|
|
for _, a := range taskRes.AdvisorBrief.BaselineAssumptions {
|
|
sb.WriteString(fmt.Sprintf(" • %s\n", a))
|
|
}
|
|
}
|
|
|
|
reply := sb.String()
|
|
|
|
return &CoordinationResult{
|
|
Status: StatusAccepted,
|
|
Intent: cand.Intent,
|
|
Branch: cand.Branch,
|
|
NaturalReply: reply,
|
|
AdvisorBrief: taskRes.AdvisorBrief,
|
|
ArtifactPath: outPath,
|
|
UpdatedContext: userCtx,
|
|
NextExpectation: nextExp,
|
|
}, nil
|
|
}
|
|
|
|
func (e *CoordinatorEngine) handleCasualChat(userCtx *UserProjectContext) (*CoordinationResult, error) {
|
|
projName := userCtx.Project.Name
|
|
if projName == "" {
|
|
projName = "当前医疗器械研发项目"
|
|
}
|
|
|
|
nextExp := BuildNextExpectation(userCtx.Project.CurrentStage, userCtx.Project.Name)
|
|
userCtx.PendingExpectation = nextExp
|
|
|
|
reply := fmt.Sprintf("您好!我是您的医疗器械立项与商务对接贴身参谋。\n\n针对「%s」,我可以协助您:\n• 随时查询项目成熟度、进展与预见性风险;\n• 补充或调整客户沟通事实与技术指标;\n• 自动起草面向客户负责人的微信/邮件沟通话术;\n• 驱动小队推进方案论证与最终 CDO 报价核算。\n\n请问您现在想从哪里开始?",
|
|
projName)
|
|
|
|
return &CoordinationResult{
|
|
Status: StatusAccepted,
|
|
Intent: intent.IntentCasualChat,
|
|
Branch: intent.BranchNewIntentPreempt,
|
|
NaturalReply: reply,
|
|
AdvisorBrief: userCtx.LatestAdvisorBrief,
|
|
UpdatedContext: userCtx,
|
|
NextExpectation: nextExp,
|
|
}, nil
|
|
}
|