This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/ai-compliance-sdk/internal/llm/provider.go
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

240 lines
7.0 KiB
Go

package llm
import (
"context"
"errors"
"time"
)
// Provider names
const (
ProviderOllama = "ollama"
ProviderAnthropic = "anthropic"
ProviderOpenAI = "openai"
)
var (
ErrProviderUnavailable = errors.New("LLM provider unavailable")
ErrModelNotFound = errors.New("model not found")
ErrContextTooLong = errors.New("context too long for model")
ErrRateLimited = errors.New("rate limited")
ErrInvalidRequest = errors.New("invalid request")
)
// Provider defines the interface for LLM providers
type Provider interface {
// Name returns the provider name
Name() string
// IsAvailable checks if the provider is currently available
IsAvailable(ctx context.Context) bool
// ListModels returns available models
ListModels(ctx context.Context) ([]Model, error)
// Complete performs text completion
Complete(ctx context.Context, req *CompletionRequest) (*CompletionResponse, error)
// Chat performs chat completion
Chat(ctx context.Context, req *ChatRequest) (*ChatResponse, error)
// Embed creates embeddings for text
Embed(ctx context.Context, req *EmbedRequest) (*EmbedResponse, error)
}
// Model represents an available LLM model
type Model struct {
ID string `json:"id"`
Name string `json:"name"`
Provider string `json:"provider"`
Description string `json:"description,omitempty"`
ContextSize int `json:"context_size"`
Parameters map[string]any `json:"parameters,omitempty"`
Capabilities []string `json:"capabilities,omitempty"` // "chat", "completion", "embedding"
}
// Message represents a chat message
type Message struct {
Role string `json:"role"` // "system", "user", "assistant"
Content string `json:"content"`
}
// CompletionRequest represents a text completion request
type CompletionRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"`
Stop []string `json:"stop,omitempty"`
Options map[string]any `json:"options,omitempty"`
}
// CompletionResponse represents a text completion response
type CompletionResponse struct {
ID string `json:"id"`
Model string `json:"model"`
Provider string `json:"provider"`
Text string `json:"text"`
FinishReason string `json:"finish_reason,omitempty"`
Usage UsageStats `json:"usage"`
Duration time.Duration `json:"duration"`
}
// ChatRequest represents a chat completion request
type ChatRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"`
Stop []string `json:"stop,omitempty"`
Options map[string]any `json:"options,omitempty"`
}
// ChatResponse represents a chat completion response
type ChatResponse struct {
ID string `json:"id"`
Model string `json:"model"`
Provider string `json:"provider"`
Message Message `json:"message"`
FinishReason string `json:"finish_reason,omitempty"`
Usage UsageStats `json:"usage"`
Duration time.Duration `json:"duration"`
}
// EmbedRequest represents an embedding request
type EmbedRequest struct {
Model string `json:"model"`
Input []string `json:"input"`
}
// EmbedResponse represents an embedding response
type EmbedResponse struct {
ID string `json:"id"`
Model string `json:"model"`
Provider string `json:"provider"`
Embeddings [][]float64 `json:"embeddings"`
Usage UsageStats `json:"usage"`
Duration time.Duration `json:"duration"`
}
// UsageStats represents token usage statistics
type UsageStats struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
}
// ProviderRegistry manages multiple LLM providers
type ProviderRegistry struct {
providers map[string]Provider
primaryProvider string
fallbackProvider string
}
// NewProviderRegistry creates a new provider registry
func NewProviderRegistry(primary, fallback string) *ProviderRegistry {
return &ProviderRegistry{
providers: make(map[string]Provider),
primaryProvider: primary,
fallbackProvider: fallback,
}
}
// Register registers a provider
func (r *ProviderRegistry) Register(provider Provider) {
r.providers[provider.Name()] = provider
}
// GetProvider returns a provider by name
func (r *ProviderRegistry) GetProvider(name string) (Provider, bool) {
p, ok := r.providers[name]
return p, ok
}
// GetPrimary returns the primary provider
func (r *ProviderRegistry) GetPrimary() (Provider, bool) {
return r.GetProvider(r.primaryProvider)
}
// GetFallback returns the fallback provider
func (r *ProviderRegistry) GetFallback() (Provider, bool) {
return r.GetProvider(r.fallbackProvider)
}
// GetAvailable returns the first available provider (primary, then fallback)
func (r *ProviderRegistry) GetAvailable(ctx context.Context) (Provider, error) {
if p, ok := r.GetPrimary(); ok && p.IsAvailable(ctx) {
return p, nil
}
if p, ok := r.GetFallback(); ok && p.IsAvailable(ctx) {
return p, nil
}
return nil, ErrProviderUnavailable
}
// ListAllModels returns models from all available providers
func (r *ProviderRegistry) ListAllModels(ctx context.Context) ([]Model, error) {
var allModels []Model
for _, p := range r.providers {
if p.IsAvailable(ctx) {
models, err := p.ListModels(ctx)
if err == nil {
allModels = append(allModels, models...)
}
}
}
return allModels, nil
}
// Complete performs completion with automatic fallback
func (r *ProviderRegistry) Complete(ctx context.Context, req *CompletionRequest) (*CompletionResponse, error) {
provider, err := r.GetAvailable(ctx)
if err != nil {
return nil, err
}
resp, err := provider.Complete(ctx, req)
if err != nil && r.fallbackProvider != "" {
// Try fallback
if fallback, ok := r.GetFallback(); ok && fallback.Name() != provider.Name() {
return fallback.Complete(ctx, req)
}
}
return resp, err
}
// Chat performs chat completion with automatic fallback
func (r *ProviderRegistry) Chat(ctx context.Context, req *ChatRequest) (*ChatResponse, error) {
provider, err := r.GetAvailable(ctx)
if err != nil {
return nil, err
}
resp, err := provider.Chat(ctx, req)
if err != nil && r.fallbackProvider != "" {
// Try fallback
if fallback, ok := r.GetFallback(); ok && fallback.Name() != provider.Name() {
return fallback.Chat(ctx, req)
}
}
return resp, err
}
// Embed creates embeddings with automatic fallback
func (r *ProviderRegistry) Embed(ctx context.Context, req *EmbedRequest) (*EmbedResponse, error) {
provider, err := r.GetAvailable(ctx)
if err != nil {
return nil, err
}
return provider.Embed(ctx, req)
}