129 lines
4.2 KiB
Go
129 lines
4.2 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
|
)
|
|
|
|
type ResolveInput struct {
|
|
PipelineID string
|
|
Only []string
|
|
Catalog pipeline.ModuleCatalog
|
|
LLMProfileOverride string
|
|
ReferenceOverrides []pipeline.ReferenceBinding
|
|
ReferenceUnbinds []pipeline.ReferenceUnbind
|
|
}
|
|
|
|
type EffectiveConfig struct {
|
|
Config Config
|
|
PipelineID string
|
|
Only []string
|
|
ReferenceOverrides []pipeline.ReferenceBinding
|
|
ReferenceUnbinds []pipeline.ReferenceUnbind
|
|
ResolvedPipeline pipeline.ResolvedPipeline
|
|
}
|
|
|
|
func (c Config) Resolve(input ResolveInput) (EffectiveConfig, error) {
|
|
if err := c.Validate(); err != nil {
|
|
return EffectiveConfig{}, err
|
|
}
|
|
|
|
pipelineID := strings.TrimSpace(input.PipelineID)
|
|
if pipelineID == "" {
|
|
return EffectiveConfig{}, fmt.Errorf("pipeline id must not be empty")
|
|
}
|
|
|
|
profile, ok := lookupPipelineProfile(c.Pipelines, pipelineID)
|
|
if !ok {
|
|
return EffectiveConfig{}, fmt.Errorf("pipeline %q is not configured", pipelineID)
|
|
}
|
|
profile = clonePipelineProfile(profile)
|
|
profile.ID = pipelineID
|
|
if override := strings.TrimSpace(input.LLMProfileOverride); override != "" {
|
|
if !hasLLMProfile(c.LLMProfiles, override) {
|
|
return EffectiveConfig{}, fmt.Errorf("LLM profile override %q is not configured", override)
|
|
}
|
|
applyLLMProfileOverride(&profile, override)
|
|
}
|
|
|
|
resolved, err := pipeline.ResolvePipeline(profile, pipeline.ResolveOptions{
|
|
Only: input.Only,
|
|
ReferenceOverrides: append([]pipeline.ReferenceBinding(nil), input.ReferenceOverrides...),
|
|
ReferenceUnbinds: append([]pipeline.ReferenceUnbind(nil), input.ReferenceUnbinds...),
|
|
}, input.Catalog)
|
|
if err != nil {
|
|
return EffectiveConfig{}, fmt.Errorf("resolve pipeline %q: %w", pipelineID, err)
|
|
}
|
|
|
|
return EffectiveConfig{
|
|
Config: cloneConfig(c),
|
|
PipelineID: pipelineID,
|
|
Only: append([]string(nil), input.Only...),
|
|
ReferenceOverrides: append([]pipeline.ReferenceBinding(nil), input.ReferenceOverrides...),
|
|
ReferenceUnbinds: append([]pipeline.ReferenceUnbind(nil), input.ReferenceUnbinds...),
|
|
ResolvedPipeline: resolved,
|
|
}, nil
|
|
}
|
|
|
|
func applyLLMProfileOverride(profile *pipeline.PipelineProfile, profileID string) {
|
|
profile.Input.LLMProfile = profileID
|
|
profile.Chunk.LLMProfile = profileID
|
|
profile.Output.LLMProfile = profileID
|
|
for laneID, lane := range profile.Artifacts {
|
|
lane.Extract.LLMProfile = profileID
|
|
lane.Merge.LLMProfile = profileID
|
|
lane.Normalize.LLMProfile = profileID
|
|
for i := range lane.Validators {
|
|
lane.Validators[i].LLMProfile = profileID
|
|
}
|
|
profile.Artifacts[laneID] = lane
|
|
}
|
|
}
|
|
|
|
func lookupPipelineProfile(profiles map[string]pipeline.PipelineProfile, pipelineID string) (pipeline.PipelineProfile, bool) {
|
|
pipelineID = strings.TrimSpace(pipelineID)
|
|
for rawID, profile := range profiles {
|
|
if strings.TrimSpace(rawID) == pipelineID {
|
|
return profile, true
|
|
}
|
|
}
|
|
return pipeline.PipelineProfile{}, false
|
|
}
|
|
|
|
func (c Config) OpenAICompatibleClientConfig(profileID string) (llm.OpenAICompatibleClientConfig, error) {
|
|
trimmedID := strings.TrimSpace(profileID)
|
|
profile, ok := c.LLMProfile(trimmedID)
|
|
if !ok {
|
|
return llm.OpenAICompatibleClientConfig{}, fmt.Errorf("LLM profile %q is not configured", trimmedID)
|
|
}
|
|
|
|
provider := strings.TrimSpace(profile.Provider)
|
|
if provider == "" {
|
|
provider = providerOpenAICompatible
|
|
}
|
|
if provider != providerOpenAICompatible {
|
|
return llm.OpenAICompatibleClientConfig{}, fmt.Errorf("LLM profile %q provider %q is not supported", trimmedID, provider)
|
|
}
|
|
|
|
baseURL := strings.TrimSpace(profile.BaseURL)
|
|
if baseURL == "" {
|
|
return llm.OpenAICompatibleClientConfig{}, fmt.Errorf("LLM profile %q base URL must not be empty", trimmedID)
|
|
}
|
|
model := strings.TrimSpace(profile.Model)
|
|
if model == "" {
|
|
return llm.OpenAICompatibleClientConfig{}, fmt.Errorf("LLM profile %q model must not be empty", trimmedID)
|
|
}
|
|
|
|
return llm.OpenAICompatibleClientConfig{
|
|
BaseURL: baseURL,
|
|
Model: model,
|
|
APIKey: profile.APIKey,
|
|
MaxRetries: profile.MaxRetries,
|
|
RequestTimeout: time.Duration(profile.TimeoutSeconds) * time.Second,
|
|
}, nil
|
|
}
|