185 lines
6.3 KiB
Go
185 lines
6.3 KiB
Go
package chat
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/coordinator"
|
|
"chat-agent-system/internal/ctxmgr"
|
|
"chat-agent-system/internal/intent"
|
|
"chat-agent-system/internal/squad"
|
|
"chat-agent-system/internal/stream"
|
|
)
|
|
|
|
// Message 历史消息结构
|
|
type Message struct {
|
|
ID string `json:"id"`
|
|
Role string `json:"role"` // "user" | "assistant"
|
|
Content string `json:"content"`
|
|
Intent intent.IntentType `json:"intent,omitempty"`
|
|
Branch intent.ResolutionBranch `json:"branch,omitempty"`
|
|
AdvisorBrief *squad.AdvisorBriefing `json:"advisor_brief,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// ChatResponse 会话单轮响应
|
|
type ChatResponse struct {
|
|
ReplyText string `json:"reply_text"`
|
|
Branch intent.ResolutionBranch `json:"branch"`
|
|
IntentResult *intent.IntentRecognitionResult `json:"intent_result"`
|
|
CoordinationResult *coordinator.CoordinationResult `json:"coordination_result"`
|
|
AdvisorBrief *squad.AdvisorBriefing `json:"advisor_brief,omitempty"`
|
|
PendingExpectation *intent.PendingExpectation `json:"pending_expectation,omitempty"`
|
|
}
|
|
|
|
// Session 多轮对话与协同会话管理器
|
|
type Session struct {
|
|
ID string `json:"id"`
|
|
UserID int64 `json:"user_id"`
|
|
ProjectID int64 `json:"project_id"`
|
|
UserCtx *coordinator.UserProjectContext `json:"user_ctx"`
|
|
Recognizer intent.Recognizer `json:"-"`
|
|
Coordinator *coordinator.CoordinatorEngine `json:"-"`
|
|
CtxMgr *ctxmgr.ContextManager `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())
|
|
}
|
|
// 若上下文尚未挂起预期且当前已具有阶段,则初始化默认选项草稿
|
|
if userCtx != nil && userCtx.PendingExpectation == nil && userCtx.Project.CurrentStage != "" {
|
|
userCtx.PendingExpectation = coordinator.BuildNextExpectation(userCtx.Project.CurrentStage, userCtx.Project.Name)
|
|
}
|
|
|
|
return &Session{
|
|
ID: id,
|
|
UserID: 1,
|
|
ProjectID: 1,
|
|
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) {
|
|
streamer := stream.FromContext(ctx)
|
|
return s.sendMessageInternal(ctx, userInput, streamer)
|
|
}
|
|
|
|
// SendMessageStream 处理一轮用户输入并实时流式推送事件 (支持 SSE / 终端流式监听)
|
|
func (s *Session) SendMessageStream(ctx context.Context, userInput string, listener stream.Listener, includeDetails bool) (*ChatResponse, error) {
|
|
ctxWithS, streamer := stream.WithListener(ctx, includeDetails, listener)
|
|
return s.sendMessageInternal(ctxWithS, userInput, streamer)
|
|
}
|
|
|
|
func (s *Session) sendMessageInternal(ctx context.Context, userInput string, streamer *stream.Streamer) (*ChatResponse, error) {
|
|
start := time.Now()
|
|
if streamer != nil {
|
|
streamer.EmitPhase("intent_recognizing", "正在研判您的意图与项目上下文...", "coordinator", 0, 0, 0)
|
|
}
|
|
|
|
userMsg := Message{
|
|
ID: fmt.Sprintf("msg_%d_u", time.Now().UnixNano()),
|
|
Role: "user",
|
|
Content: userInput,
|
|
Timestamp: time.Now(),
|
|
}
|
|
s.Messages = append(s.Messages, userMsg)
|
|
|
|
if s.CtxMgr != nil {
|
|
_ = s.CtxMgr.RecordUserMessage(ctx, s.ID, s.UserID, "user", userInput, "", "", nil)
|
|
}
|
|
|
|
// 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 {
|
|
if streamer != nil {
|
|
streamer.EmitError(err)
|
|
}
|
|
return nil, fmt.Errorf("intent recognition failed: %w", err)
|
|
}
|
|
|
|
if streamer != nil && recRes.TopIntent != nil {
|
|
streamer.EmitStatus(
|
|
string(recRes.TopIntent.Intent),
|
|
string(recRes.Branch),
|
|
string(s.UserCtx.Project.CurrentStage),
|
|
fmt.Sprintf("识别分支: %s (置信度: %.0f%%)", recRes.Branch, recRes.TopIntent.Confidence*100),
|
|
recRes.TopIntent.FilledSlots,
|
|
)
|
|
}
|
|
|
|
// 2. 协调调度与校验 (L2 协调层)
|
|
coordRes, err := s.Coordinator.ProcessIntent(ctx, recRes, s.UserCtx)
|
|
if err != nil {
|
|
if streamer != nil {
|
|
streamer.EmitError(err)
|
|
}
|
|
return nil, fmt.Errorf("coordination execution failed: %w", err)
|
|
}
|
|
|
|
// 3. 更新上下文与挂起预期生命周期
|
|
if coordRes.UpdatedContext != nil {
|
|
s.UserCtx = coordRes.UpdatedContext
|
|
}
|
|
|
|
if coordRes.ClearExpectation {
|
|
s.UserCtx.PendingExpectation = nil
|
|
} else if coordRes.NextExpectation != nil {
|
|
s.UserCtx.PendingExpectation = coordRes.NextExpectation
|
|
} else if recRes.Branch == intent.BranchExpectationHit {
|
|
s.UserCtx.PendingExpectation = nil
|
|
}
|
|
// 若为 BranchNewIntentPreempt 且未产生新预期,原挂起预期在焦点底静默保留,不阻拦用户
|
|
|
|
asstMsg := Message{
|
|
ID: fmt.Sprintf("msg_%d_a", time.Now().UnixNano()),
|
|
Role: "assistant",
|
|
Content: coordRes.NaturalReply,
|
|
Intent: coordRes.Intent,
|
|
Branch: recRes.Branch,
|
|
AdvisorBrief: coordRes.AdvisorBrief,
|
|
Timestamp: time.Now(),
|
|
}
|
|
s.Messages = append(s.Messages, asstMsg)
|
|
|
|
if s.CtxMgr != nil {
|
|
_ = s.CtxMgr.RecordUserMessage(ctx, s.ID, 0, "assistant", coordRes.NaturalReply, string(coordRes.Intent), string(recRes.Branch), nil)
|
|
}
|
|
|
|
// 4. 流式推送参谋自然语言回复与决策锦囊
|
|
if streamer != nil {
|
|
if coordRes.NaturalReply != "" {
|
|
streamer.EmitDelta(coordRes.NaturalReply)
|
|
}
|
|
if coordRes.AdvisorBrief != nil {
|
|
streamer.EmitBriefing(coordRes.AdvisorBrief)
|
|
}
|
|
streamer.EmitDone(s.ID, time.Since(start))
|
|
}
|
|
|
|
return &ChatResponse{
|
|
ReplyText: coordRes.NaturalReply,
|
|
Branch: recRes.Branch,
|
|
IntentResult: recRes,
|
|
CoordinationResult: coordRes,
|
|
AdvisorBrief: coordRes.AdvisorBrief,
|
|
PendingExpectation: s.UserCtx.PendingExpectation,
|
|
}, nil
|
|
}
|