This repository has been archived on 2026-08-12. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Cyrene/backend/ai-core/internal/subsession/manager.go
T
AskaEth 7e475bdfa6 feat: 子会话结果增加创建/结束时间戳
- SubSessionResult 新增 CreatedAt / FinishedAt 字段
- manager.Dispatch 在各路径统一设置时间戳:成功/失败/超时

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-22 20:31:48 +08:00

211 lines
5.5 KiB
Go

package subsession
import (
"context"
"crypto/rand"
"fmt"
"git.yeij.top/AskaEth/Cyrene/pkg/logger"
"sync"
"time"
"git.yeij.top/AskaEth/Cyrene/ai-core/internal/bus"
"git.yeij.top/AskaEth/Cyrene/ai-core/internal/llm"
"git.yeij.top/AskaEth/Cyrene/ai-core/internal/model"
)
// Manager 子会话管理器
// 负责注册 Provider、分派任务、并行执行、超时控制、结果收集
type Manager struct {
mu sync.RWMutex
providers map[model.SubSessionType]Provider
llmClient LLMClient
eventBus bus.Bus
}
// NewManager 创建子会话管理器
func NewManager(llmClient LLMClient) *Manager {
return &Manager{
providers: make(map[model.SubSessionType]Provider),
llmClient: llmClient,
}
}
// SetBus sets the event bus (optional, for Phase 1).
func (m *Manager) SetBus(b bus.Bus) {
m.eventBus = b
}
func (m *Manager) getBus() bus.Bus {
if m.eventBus == nil {
return &bus.NopBus{}
}
return m.eventBus
}
// Register 注册子会话提供者
func (m *Manager) Register(provider Provider) {
m.mu.Lock()
defer m.mu.Unlock()
m.providers[provider.Type()] = provider
logger.Printf("[subsession] 注册子会话提供者: %s (优先级=%d, 超时=%v)", provider.Type(), provider.Priority(), provider.Timeout())
}
// RegisterWithOverride 注册或覆盖子会话提供者
func (m *Manager) RegisterWithOverride(provider Provider) {
m.mu.Lock()
defer m.mu.Unlock()
m.providers[provider.Type()] = provider
logger.Printf("[subsession] 注册(覆盖)子会话提供者: %s (优先级=%d, 超时=%v)", provider.Type(), provider.Priority(), provider.Timeout())
}
// GetProvider 获取指定类型的 Provider
func (m *Manager) GetProvider(t model.SubSessionType) (Provider, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
p, ok := m.providers[t]
return p, ok
}
// ListProviders 列出所有已注册的 Provider 类型
func (m *Manager) ListProviders() []model.SubSessionType {
m.mu.RLock()
defer m.mu.RUnlock()
types := make([]model.SubSessionType, 0, len(m.providers))
for t := range m.providers {
types = append(types, t)
}
return types
}
// Dispatch 分派任务到子会话,并行执行,返回结果通道
func (m *Manager) Dispatch(
ctx context.Context,
intent *model.IntentResult,
userMessage string,
params CreateContextParams,
) <-chan model.SubSessionResult {
m.mu.RLock()
providers := make([]Provider, 0, len(m.providers))
for _, p := range m.providers {
providers = append(providers, p)
}
m.mu.RUnlock()
resultCh := make(chan model.SubSessionResult, len(providers))
var wg sync.WaitGroup
for _, provider := range providers {
if !provider.CanHandle(ctx, intent, userMessage) {
logger.Printf("[subsession] 跳过子会话 %s: CanHandle 返回 false", provider.Type())
continue
}
wg.Add(1)
go func(p Provider) {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
logger.Printf("[subsession] dispatch goroutine panic 恢复 (type=%s): %v", p.Type(), r)
}
}()
createdAt := time.Now()
result := model.SubSessionResult{Type: p.Type(), CreatedAt: createdAt}
m.getBus().Publish(bus.BusEvent{
Type: bus.EventSubSessionStarted,
Payload: bus.SubSessionPayload{SubType: p.Type(), Status: "started"},
})
// 创建带超时的 context
subCtx, cancel := context.WithTimeout(ctx, p.Timeout())
defer cancel()
// 构建 LLM 上下文
llmMessages, err := p.CreateContext(subCtx, params)
if err != nil {
result.Error = fmt.Sprintf("创建上下文失败: %v", err)
logger.Printf("[subsession] %s 创建上下文失败: %v", p.Type(), err)
result.FinishedAt = time.Now()
resultCh <- result
return
}
logger.Printf("[subsession] %s 开始执行 (上下文 %d 条消息)", p.Type(), len(llmMessages))
// 执行子会话
subResult, execErr := p.Execute(subCtx, llmMessages)
if execErr != nil {
result.FinishedAt = time.Now()
result.Error = fmt.Sprintf("执行失败: %v", execErr)
logger.Printf("[subsession] %s 执行失败: %v", p.Type(), execErr)
resultCh <- result
return
}
// 检查超时
select {
case <-subCtx.Done():
result.FinishedAt = time.Now()
result.Error = "子会话超时"
logger.Printf("[subsession] %s 超时 (limit=%v)", p.Type(), p.Timeout())
default:
if subResult != nil {
result = *subResult
result.Type = p.Type()
result.CreatedAt = createdAt
logger.Printf("[subsession] %s 完成: 摘要=%s", p.Type(), truncate(result.Summary, 50))
}
}
m.getBus().Publish(bus.BusEvent{
Type: bus.EventSubSessionCompleted,
Payload: bus.SubSessionPayload{SubType: p.Type(), Status: resultSummaryStatus(result), Summary: result.Summary, Details: result.Details},
})
resultCh <- result
}(provider)
}
// 等待所有子会话完成,关闭通道
go func() {
defer func() {
if r := recover(); r != nil {
logger.Printf("[subsession] wait goroutine panic 恢复: %v", r)
}
}()
wg.Wait()
close(resultCh)
}()
return resultCh
}
// generateID 生成随机 ID
func generateID() string {
b := make([]byte, 12)
rand.Read(b)
return fmt.Sprintf("sub-%x", b)
}
// resultSummaryStatus returns "completed" or "failed" for bus events.
func resultSummaryStatus(r model.SubSessionResult) string {
if r.Error != "" {
return "failed"
}
return "completed"
}
// truncate 截断字符串
func truncate(s string, maxLen int) string {
runes := []rune(s)
if len(runes) <= maxLen {
return s
}
return string(runes[:maxLen]) + "..."
}
// Ensure llm is used
var _ = llm.NewAdapter