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
This commit is contained in:
1 parent
99806fefad
commit
3b3e5a0697
9 files changed
+1529
No files matched your search
@@ -0,0 +1,163 @@
|
||||
// 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
|
||||
}
|
||||
Reference in new issue
Block a user