From bd33d9645ecb59ce6ba7ea3d06a7bfa6462b1874 Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Mon, 7 Sep 2026 21:34:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=E5=B0=81=E8=A3=85L1=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E7=AE=A1=E7=90=86=E5=99=A8=E6=89=93=E9=80=9A=E8=87=AA?= =?UTF-8?q?=E7=84=B6=E8=AF=AD=E8=A8=80=E5=A4=9A=E8=BD=AE=E4=BA=BA=E6=9C=BA?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=E9=97=AD=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- internal/chat/session.go | 106 ++++++++++++++++++++++++++++++++++ internal/chat/session_test.go | 86 +++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 internal/chat/session.go create mode 100644 internal/chat/session_test.go diff --git a/internal/chat/session.go b/internal/chat/session.go new file mode 100644 index 00000000..869de488 --- /dev/null +++ b/internal/chat/session.go @@ -0,0 +1,106 @@ +package chat + +import ( + "context" + "fmt" + "time" + + "chat-agent-system/internal/coordinator" + "chat-agent-system/internal/intent" + "chat-agent-system/internal/squad" +) + +// Message 历史消息结构 +type Message struct { + ID string `json:"id"` + Role string `json:"role"` // "user" | "assistant" + Content string `json:"content"` + Intent intent.IntentType `json:"intent,omitempty"` + AdvisorBrief *squad.AdvisorBriefing `json:"advisor_brief,omitempty"` + Timestamp time.Time `json:"timestamp"` +} + +// ChatResponse 会话单轮响应 +type ChatResponse struct { + ReplyText string `json:"reply_text"` + IntentResult *intent.IntentRecognitionResult `json:"intent_result"` + CoordinationResult *coordinator.CoordinationResult `json:"coordination_result"` + AdvisorBrief *squad.AdvisorBriefing `json:"advisor_brief,omitempty"` +} + +// Session 多轮对话与协同会话管理器 +type Session struct { + ID string `json:"id"` + UserCtx *coordinator.UserProjectContext `json:"user_ctx"` + Recognizer intent.Recognizer `json:"-"` + Coordinator *coordinator.CoordinatorEngine `json:"-"` + Messages []Message `json:"messages"` + CreatedAt time.Time `json:"created_at"` +} + +// NewSession 创建新对话会话 +func NewSession(id string, userCtx *coordinator.UserProjectContext, rec intent.Recognizer, coord *coordinator.CoordinatorEngine) *Session { + if rec == nil { + rec = intent.NewRecognizer() + } + if id == "" { + id = fmt.Sprintf("sess_%d", time.Now().Unix()) + } + return &Session{ + ID: id, + UserCtx: userCtx, + Recognizer: rec, + Coordinator: coord, + Messages: make([]Message, 0), + CreatedAt: time.Now(), + } +} + +// SendMessage 处理一轮用户输入 +func (s *Session) SendMessage(ctx context.Context, userInput string) (*ChatResponse, error) { + userMsg := Message{ + ID: fmt.Sprintf("msg_%d_u", time.Now().UnixNano()), + Role: "user", + Content: userInput, + Timestamp: time.Now(), + } + s.Messages = append(s.Messages, userMsg) + + // 1. 意图识别 (L1 对话层) + var snap *intent.ContextSnapshot + if s.UserCtx != nil { + snap = s.UserCtx.ToSnapshot() + } + recRes, err := s.Recognizer.Recognize(ctx, userInput, snap) + if err != nil { + return nil, fmt.Errorf("intent recognition failed: %w", err) + } + + // 2. 协调调度与校验 (L2 协调层) + coordRes, err := s.Coordinator.ProcessIntent(ctx, recRes, s.UserCtx) + if err != nil { + return nil, fmt.Errorf("coordination execution failed: %w", err) + } + + // 3. 更新上下文与历史 + if coordRes.UpdatedContext != nil { + s.UserCtx = coordRes.UpdatedContext + } + + asstMsg := Message{ + ID: fmt.Sprintf("msg_%d_a", time.Now().UnixNano()), + Role: "assistant", + Content: coordRes.NaturalReply, + Intent: coordRes.Intent, + AdvisorBrief: coordRes.AdvisorBrief, + Timestamp: time.Now(), + } + s.Messages = append(s.Messages, asstMsg) + + return &ChatResponse{ + ReplyText: coordRes.NaturalReply, + IntentResult: recRes, + CoordinationResult: coordRes, + AdvisorBrief: coordRes.AdvisorBrief, + }, nil +} diff --git a/internal/chat/session_test.go b/internal/chat/session_test.go new file mode 100644 index 00000000..edcf711e --- /dev/null +++ b/internal/chat/session_test.go @@ -0,0 +1,86 @@ +package chat_test + +import ( + "context" + "strings" + "testing" + + "chat-agent-system/internal/chat" + "chat-agent-system/internal/coordinator" + "chat-agent-system/internal/intent" + "chat-agent-system/internal/squad" +) + +func TestChatSession_EndToEndDialog(t *testing.T) { + rec := intent.NewRecognizer() + engine := coordinator.NewCoordinatorEngine(nil) + + userCtx := &coordinator.UserProjectContext{ + SnapshotID: "sess_ctx_1", + User: coordinator.UserInfo{ + ID: "u1", + Name: "张工", + Role: "engineer", + ProjectRole: "composer", + }, + Project: coordinator.ProjectInfo{ + Name: "掌上无线超声系统", + }, + LatestAdvisorBrief: &squad.AdvisorBriefing{ + MaturityScore: 75, + MaturityLevel: "V0.1", + MaturityHeadline: "初步框架已就绪", + PreemptiveRisks: []string{"探头功耗与散热设计"}, + NextBestActions: []string{"补充客户供电参数", "推进到V0.5可行性论证"}, + CommunicationKit: "建议与客户确认供电方式", + }, + } + + session := chat.NewSession("sess_001", userCtx, rec, engine) + + // Turn 1: 问候 + resp1, err := session.SendMessage(context.Background(), "你好,请问你是谁?") + if err != nil { + t.Fatalf("turn 1 failed: %v", err) + } + if resp1.IntentResult.TopIntent.Intent != intent.IntentCasualChat { + t.Errorf("turn 1 expected CASUAL_CHAT, got %s", resp1.IntentResult.TopIntent.Intent) + } + + // Turn 2: 查进展与风险 + resp2, err := session.SendMessage(context.Background(), "现在这个项目进展怎么样?有啥风险没?") + if err != nil { + t.Fatalf("turn 2 failed: %v", err) + } + if resp2.IntentResult.TopIntent.Intent != intent.IntentQueryProgress { + t.Errorf("turn 2 expected QUERY_PROGRESS, got %s", resp2.IntentResult.TopIntent.Intent) + } + if !strings.Contains(resp2.ReplyText, "掌上无线超声系统") { + t.Errorf("turn 2 reply should mention project name: %s", resp2.ReplyText) + } + + // Turn 3: 补充信息 + resp3, err := session.SendMessage(context.Background(), "客户补充说目标市场是国内及欧洲CE") + if err != nil { + t.Fatalf("turn 3 failed: %v", err) + } + if resp3.IntentResult.TopIntent.Intent != intent.IntentSupplementInfo { + t.Errorf("turn 3 expected SUPPLEMENT_INFO, got %s", resp3.IntentResult.TopIntent.Intent) + } + if session.UserCtx.BlackboardFacts["target_market"] != "国内及欧洲CE" { + t.Errorf("target_market not updated: %v", session.UserCtx.BlackboardFacts) + } + + // Turn 4: 推进阶段 + resp4, err := session.SendMessage(context.Background(), "好的,进入方案论证阶段吧") + if err != nil { + t.Fatalf("turn 4 failed: %v", err) + } + if resp4.IntentResult.TopIntent.Intent != intent.IntentAdvanceStage { + t.Errorf("turn 4 expected ADVANCE_STAGE, got %s", resp4.IntentResult.TopIntent.Intent) + } + + if len(session.Messages) != 8 { + t.Errorf("expected 8 messages (4 user + 4 asst), got %d", len(session.Messages)) + } +}