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:
Aska committed 2026-08-09 21:21:27 +08:00
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
}