Files
Cyrene_v2/backend/services/ai-core/subsession/subsession.go
T
Aska 3b3e5a0697 feat(ai-core): add LLM orchestration engine
- model: shared data types (Message, Intent, StreamToken, LLMMessage)
- persona: YAML/Raw loading + system prompt injection
- llm: OpenAI-compatible adapter with streaming + semantic cache
- context: sliding window context store with token budgeting
- subsession: parallel provider dispatch + review message parser
- orchestrator: intent analysis pipeline (IoT/greeting/chat/memory)
- background: autonomous thinking engine
- 20+ tests, build + vet clean, ~130 total tests
2026-08-09 21:21:27 +08:00

164 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package subsession manages parallel sub-session providers for dialogue enrichment.
package subsession
import (
"context"
"fmt"
"strings"
"sync"
"time"
"git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/model"
)
// Provider executes a specific sub-session task in parallel with others.
type Provider interface {
Name() string
Execute(ctx context.Context, input ProviderInput) (*model.SubSessionResult, error)
}
// ProviderInput holds the context for sub-session execution.
type ProviderInput struct {
UserMessage string
History []model.Message
Intent *model.Intent
}
// Manager orchestrates parallel sub-session execution.
type Manager struct {
providers []Provider
}
// NewManager creates a sub-session manager.
func NewManager(providers ...Provider) *Manager {
return &Manager{providers: providers}
}
// Dispatch runs all providers in parallel and returns results within the timeout.
func (m *Manager) Dispatch(ctx context.Context, input ProviderInput, timeout time.Duration) []model.SubSessionResult {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
var wg sync.WaitGroup
results := make([]model.SubSessionResult, 0, len(m.providers))
var mu sync.Mutex
for _, p := range m.providers {
wg.Add(1)
go func(provider Provider) {
defer wg.Done()
result, err := provider.Execute(ctx, input)
mu.Lock()
defer mu.Unlock()
if err != nil {
results = append(results, model.SubSessionResult{
Provider: provider.Name(),
Error: err.Error(),
})
} else if result != nil {
results = append(results, *result)
}
}(p)
}
wg.Wait()
return results
}
// --- Built-in Providers ---
// GeneralProvider handles general conversation intent.
type GeneralProvider struct{}
func (p *GeneralProvider) Name() string { return "general" }
func (p *GeneralProvider) Execute(_ context.Context, input ProviderInput) (*model.SubSessionResult, error) {
return &model.SubSessionResult{
Provider: "general",
Content: fmt.Sprintf("Intent: %s (confidence: %.2f)", input.Intent.Type, input.Intent.Confidence),
}, nil
}
// MemoryProvider retrieves relevant memories (placeholder — wired to Memory Service later).
type MemoryProvider struct{}
func (p *MemoryProvider) Name() string { return "memory" }
func (p *MemoryProvider) Execute(_ context.Context, _ ProviderInput) (*model.SubSessionResult, error) {
return &model.SubSessionResult{Provider: "memory", Content: "no relevant memories"}, nil
}
// IoTProvider executes IoT device queries and commands.
type IoTProvider struct {
// GetDeviceState is injected by the main app to query IoT devices.
GetDeviceState func(ctx context.Context) (string, error)
// ControlDevice is injected to execute IoT commands.
ControlDevice func(ctx context.Context, device, action string) (string, error)
}
func (p *IoTProvider) Name() string { return "iot" }
func (p *IoTProvider) Execute(ctx context.Context, input ProviderInput) (*model.SubSessionResult, error) {
if input.Intent == nil || (input.Intent.Type != model.IntentIoTControl && input.Intent.Type != model.IntentIoTQuery) {
return &model.SubSessionResult{Provider: "iot", Content: ""}, nil
}
if p.GetDeviceState != nil {
state, err := p.GetDeviceState(ctx)
if err == nil {
return &model.SubSessionResult{Provider: "iot", Content: state}, nil
}
}
return &model.SubSessionResult{Provider: "iot", Content: "iot devices available"}, nil
}
// ReviewProvider parses LLM output into action/chat segments.
type ReviewProvider struct{}
func (p *ReviewProvider) Name() string { return "review" }
func (p *ReviewProvider) Execute(_ context.Context, _ ProviderInput) (*model.SubSessionResult, error) {
return &model.SubSessionResult{Provider: "review", Content: ""}, nil
}
// ParseReviewMessages splits a message with action markers into segments.
// Input: "(歪着头看) 客厅灯已经开啦♪"
// Output: [{type:"action", content:"歪着头看"}, {type:"chat", content:"客厅灯已经开啦♪"}]
func ParseReviewMessages(content string) []model.ReviewSegment {
var segments []model.ReviewSegment
var current strings.Builder
inAction := false
runes := []rune(content)
for i := 0; i < len(runes); i++ {
r := runes[i]
// Chinese bracket detection
if r == '(' || r == '(' {
if current.Len() > 0 && !inAction {
segments = append(segments, model.ReviewSegment{Type: "chat", Content: current.String()})
current.Reset()
}
inAction = true
continue
}
if r == ')' || r == ')' {
if inAction && current.Len() > 0 {
segments = append(segments, model.ReviewSegment{Type: "action", Content: current.String()})
current.Reset()
}
inAction = false
continue
}
current.WriteRune(r)
}
if current.Len() > 0 {
segType := "chat"
if inAction {
segType = "action"
}
segments = append(segments, model.ReviewSegment{Type: segType, Content: current.String()})
}
return segments
}