From 52c36e6a873efd88e5f1008dfbde3b73bcdc7256 Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Mon, 7 Sep 2026 21:34:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(cli):=20CLI=E6=94=AF=E6=8C=81-chat?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=E5=BC=8F=E5=AF=B9=E8=AF=9DREPL=E4=B8=8E?= =?UTF-8?q?=E5=8D=95=E6=AC=A1=E8=87=AA=E7=84=B6=E8=AF=AD=E8=A8=80=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=E8=B0=83=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- cmd/bizdev-cli/main.go | 154 +++++++++++++++++++++++++++++++++++- cmd/bizdev-cli/main_test.go | 86 ++++++++++++++++++++ 2 files changed, 239 insertions(+), 1 deletion(-) diff --git a/cmd/bizdev-cli/main.go b/cmd/bizdev-cli/main.go index 36de9a2d..0bd3dcad 100644 --- a/cmd/bizdev-cli/main.go +++ b/cmd/bizdev-cli/main.go @@ -1,15 +1,20 @@ package main import ( + "bufio" "context" "flag" "fmt" "os" "path/filepath" "strings" + "time" "chat-agent-system/internal/bizdev" + "chat-agent-system/internal/chat" "chat-agent-system/internal/config" + "chat-agent-system/internal/coordinator" + "chat-agent-system/internal/intent" "chat-agent-system/internal/squad" ) @@ -30,6 +35,13 @@ func main() { listTasks := flag.Bool("list-tasks", false, "列出所有受支持的商务对接任务清单") listStages := flag.Bool("list-stages", false, "列出所有受支持的商务成熟度阶段清单") addStep := flag.String("add-step", "", "动态向工作区追加任务步骤 (格式: role:title,配合 -inspect)") + + // 对话协同与意图识别相关命令行参数 + chatMode := flag.Bool("chat", false, "启动与项目参谋的自然语言交互式对话 (REPL)") + chatQuery := flag.String("query", "", "直接在命令行传入单次提问/指令并输出参谋诊断与调度结果 (配合 -chat)") + workDirFlag := flag.String("work-dir", "", "关联现有工作区目录 (如 sandbox/intake_xxx)") + userName := flag.String("user", "张工", "用户姓名") + userRole := flag.String("role", "engineer", "用户角色") flag.Parse() cfg, err := config.Load(*envPath) @@ -69,7 +81,25 @@ func main() { return } - // 3. 执行阶段或常规任务 + // 3. 交互式对话参谋模式 + if *chatMode || *chatQuery != "" { + handleChatMode( + context.Background(), + svc, + *chatQuery, + *workDirFlag, + *project, + *customer, + *device, + *use, + *market, + *userName, + *userRole, + ) + return + } + + // 4. 执行阶段或常规任务 activeBackend := cfg.AgentBackend if *backend != "" { activeBackend = *backend @@ -308,3 +338,125 @@ func handleInspect(ctx context.Context, svc bizdev.BizdevService, workDir string } } } + +func handleChatMode( + ctx context.Context, + svc bizdev.BizdevService, + chatQuery string, + workDir string, + project string, + customer string, + device string, + use string, + market string, + userName string, + userRole string, +) { + u := coordinator.UserInfo{ + ID: "u_local", + Name: userName, + Role: userRole, + ProjectRole: "composer", + } + + assembler := coordinator.NewContextAssembler() + var userCtx *coordinator.UserProjectContext + var err error + + if workDir != "" { + userCtx, err = assembler.AssembleFromWorkspace(workDir, u) + if err != nil { + fmt.Fprintf(os.Stderr, "从工作区装载上下文失败 %s: %v,将初始化新上下文\n", workDir, err) + } + } + + if userCtx == nil { + userCtx = &coordinator.UserProjectContext{ + SnapshotID: fmt.Sprintf("ctx_%d", time.Now().UnixNano()), + AssembledAt: time.Now(), + User: u, + Project: coordinator.ProjectInfo{ + Name: project, + CustomerName: customer, + DeviceType: device, + IntendedUse: use, + TargetMarket: market, + CurrentWorkflow: "intake", + CurrentStage: bizdev.StageV01_Triage, + }, + BlackboardFacts: make(map[string]string), + AvailableActions: coordinator.ComputeAvailableActions(bizdev.StageV01_Triage, u.ProjectRole), + } + if market != "" { + userCtx.BlackboardFacts["target_market"] = market + } + if device != "" { + userCtx.BlackboardFacts["device_type"] = device + } + } + + rec := intent.NewRecognizer() + engine := coordinator.NewCoordinatorEngine(svc) + session := chat.NewSession("cli_chat_sess", userCtx, rec, engine) + + // 单次提问模式 (支持脚本与自动化测试) + if chatQuery != "" { + resp, err := session.SendMessage(ctx, chatQuery) + if err != nil { + fmt.Fprintf(os.Stderr, "对话处理失败: %v\n", err) + os.Exit(1) + } + top := resp.IntentResult.TopIntent + fmt.Printf("[意图识别: %s (置信度: %.0f%%)] - %s\n", top.Intent, top.Confidence*100, top.Reasoning) + fmt.Printf("\n%s\n", resp.ReplyText) + if resp.AdvisorBrief != nil && (top.Intent == intent.IntentAdvanceStage || top.Intent == intent.IntentQueryProgress) { + printAdvisorBrief(resp.AdvisorBrief) + } + return + } + + // 交互式 REPL 模式 + fmt.Println("==================================================================") + fmt.Println(" 🤖 医疗器械商务与研发智能参谋(L1/L2 协作对话终端) ") + fmt.Println("==================================================================") + fmt.Printf("项目名称: %s | 客户单位: %s\n", userCtx.Project.Name, userCtx.Project.CustomerName) + fmt.Printf("当前阶段: %s | 用户身份: %s (%s)\n", userCtx.Project.CurrentStage, userCtx.User.Name, userCtx.User.Role) + fmt.Printf("可用动作: %v\n", userCtx.AvailableActions) + fmt.Println("------------------------------------------------------------------") + fmt.Println("💡 提示:您可以直接输入自然语言,例如:") + fmt.Println(" • \"现在这个便携超声立项评估到哪一步了?有啥风险没?\"") + fmt.Println(" • \"客户补充说目标市场除了国内还想考虑欧洲 CE 认证\"") + fmt.Println(" • \"我看摸底结果可以,进入方案论证阶段吧\"") + fmt.Println(" • 输入 'exit' 或 'quit' 退出") + fmt.Println("==================================================================") + + scanner := bufio.NewScanner(os.Stdin) + for { + fmt.Print("\n[您] > ") + if !scanner.Scan() { + break + } + input := strings.TrimSpace(scanner.Text()) + if input == "" { + continue + } + if input == "exit" || input == "quit" { + fmt.Println("已退出参谋对话。") + break + } + + resp, err := session.SendMessage(ctx, input) + if err != nil { + fmt.Fprintf(os.Stderr, "处理异常: %v\n", err) + continue + } + + top := resp.IntentResult.TopIntent + fmt.Printf("\n[智能参谋] (识别意图: %s | 置信度: %.0f%%)\n", top.Intent, top.Confidence*100) + fmt.Println(resp.ReplyText) + + if resp.AdvisorBrief != nil && (top.Intent == intent.IntentAdvanceStage || top.Intent == intent.IntentQueryProgress) { + printAdvisorBrief(resp.AdvisorBrief) + } + } +} diff --git a/cmd/bizdev-cli/main_test.go b/cmd/bizdev-cli/main_test.go index 65e3f2ce..913217bf 100644 --- a/cmd/bizdev-cli/main_test.go +++ b/cmd/bizdev-cli/main_test.go @@ -149,3 +149,89 @@ func TestBizdevCLIStageExecution(t *testing.T) { t.Errorf("missing customer name in communication kit: %s", outStr) } } + +func TestBizdevCLIChatQueries(t *testing.T) { + tmpDir := t.TempDir() + envContent := "ENV=dev\nDATA_DIR=" + tmpDir + "\nAGENT_BACKEND=stub\n" + envPath := filepath.Join(tmpDir, ".env") + _ = os.WriteFile(envPath, []byte(envContent), 0o644) + + // 1. 测试闲聊问候 + cmd1 := exec.Command("go", "run", "./cmd/bizdev-cli", + "-env", envPath, + "-backend", "stub", + "-chat", + "-query", "你好,请问你是谁?", + "-project", "掌上便携超声", + ) + cmd1.Dir = moduleRoot(t) + out1, err := cmd1.CombinedOutput() + if err != nil { + t.Fatalf("chat query 1 failed: %v, out: %s", err, string(out1)) + } + if !strings.Contains(string(out1), "意图识别: CASUAL_CHAT") { + t.Errorf("expected intent CASUAL_CHAT, got: %s", string(out1)) + } + if !strings.Contains(string(out1), "掌上便携超声") { + t.Errorf("expected greeting to mention project: %s", string(out1)) + } + + // 2. 测试进度与风险查询 + cmd2 := exec.Command("go", "run", "./cmd/bizdev-cli", + "-env", envPath, + "-backend", "stub", + "-chat", + "-query", "现在这个便携超声立项评估到哪一步了?有啥风险没?", + "-project", "掌上便携超声", + ) + cmd2.Dir = moduleRoot(t) + out2, err := cmd2.CombinedOutput() + if err != nil { + t.Fatalf("chat query 2 failed: %v, out: %s", err, string(out2)) + } + if !strings.Contains(string(out2), "意图识别: QUERY_PROGRESS") { + t.Errorf("expected intent QUERY_PROGRESS, got: %s", string(out2)) + } + + // 3. 测试信息补充 + cmd3 := exec.Command("go", "run", "./cmd/bizdev-cli", + "-env", envPath, + "-backend", "stub", + "-chat", + "-query", "客户刚才补充说,目标市场除了国内还想考虑欧洲 CE 认证", + "-project", "掌上便携超声", + ) + cmd3.Dir = moduleRoot(t) + out3, err := cmd3.CombinedOutput() + if err != nil { + t.Fatalf("chat query 3 failed: %v, out: %s", err, string(out3)) + } + if !strings.Contains(string(out3), "意图识别: SUPPLEMENT_INFO") { + t.Errorf("expected intent SUPPLEMENT_INFO, got: %s", string(out3)) + } + if !strings.Contains(string(out3), "已收到并为您记录最新信息") { + t.Errorf("expected acknowledgement in reply: %s", string(out3)) + } + + // 4. 测试阶段推进 + cmd4 := exec.Command("go", "run", "./cmd/bizdev-cli", + "-env", envPath, + "-backend", "stub", + "-chat", + "-query", "摸底表我看过了,没问题,可以进入方案论证阶段了", + "-project", "掌上便携超声", + "-customer", "水木清研", + "-device", "二类掌上超声", + ) + cmd4.Dir = moduleRoot(t) + out4, err := cmd4.CombinedOutput() + if err != nil { + t.Fatalf("chat query 4 failed: %v, out: %s", err, string(out4)) + } + if !strings.Contains(string(out4), "意图识别: ADVANCE_STAGE") { + t.Errorf("expected intent ADVANCE_STAGE, got: %s", string(out4)) + } + if !strings.Contains(string(out4), "已为您成功推进并完成了【V0.5 方案成型与法规对齐】") { + t.Errorf("expected stage advance success, got: %s", string(out4)) + } +}