324 lines
10 KiB
Go
324 lines
10 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
|
)
|
|
|
|
// LoadedConfig keeps the finalized runtime configuration together with the
|
|
// private ConfigKit inspection snapshot that produced it.
|
|
type LoadedConfig struct {
|
|
Config Config
|
|
|
|
result *configkit.Result[Document]
|
|
}
|
|
|
|
// RuntimeConfig returns an independently owned runtime configuration.
|
|
func (loaded *LoadedConfig) RuntimeConfig() Config {
|
|
if loaded == nil {
|
|
return Config{}
|
|
}
|
|
return cloneConfig(loaded.Config)
|
|
}
|
|
|
|
// WriteEffective renders ConfigKit's frozen, redacted effective document.
|
|
func (loaded *LoadedConfig) WriteEffective(output io.Writer) error {
|
|
if loaded == nil || loaded.result == nil {
|
|
return fmt.Errorf("loaded configuration is unavailable")
|
|
}
|
|
return loaded.result.WriteEffective(output)
|
|
}
|
|
|
|
// WriteExplanation renders ConfigKit's safe explanation for a JSON-pointer
|
|
// path without exposing ConfigKit types at this package boundary.
|
|
func (loaded *LoadedConfig) WriteExplanation(output io.Writer, pointer string) error {
|
|
if loaded == nil || loaded.result == nil {
|
|
return fmt.Errorf("loaded configuration is unavailable")
|
|
}
|
|
path, err := configkit.ParsePath(pointer)
|
|
if err != nil {
|
|
return fmt.Errorf("parse configuration explanation path: %w", err)
|
|
}
|
|
return loaded.result.WriteExplanation(output, path)
|
|
}
|
|
|
|
func loadConfig(path string, options LoadOptions) (*LoadedConfig, error) {
|
|
result, err := loadDocument(path, options)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newLoadedConfig(result)
|
|
}
|
|
|
|
// Load reads a version 5 configuration file into runtime configuration and a
|
|
// private inspection snapshot.
|
|
func Load(path string, options LoadOptions) (*LoadedConfig, error) {
|
|
return loadConfig(path, options)
|
|
}
|
|
|
|
func loadConfigSource(source configkit.Source, options LoadOptions) (*LoadedConfig, error) {
|
|
result, err := loadDocumentSource(source, options)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newLoadedConfig(result)
|
|
}
|
|
|
|
func newLoadedConfig(result *configkit.Result[Document]) (*LoadedConfig, error) {
|
|
if result == nil {
|
|
return nil, fmt.Errorf("configuration result is unavailable")
|
|
}
|
|
runtime, err := documentToConfig(result.Config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &LoadedConfig{Config: runtime, result: result}, nil
|
|
}
|
|
|
|
func documentToConfig(document Document) (Config, error) {
|
|
config := Default()
|
|
config.Pipelines = documentPipelinesToRuntime(document.Pipelines)
|
|
if document.Concurrency != nil {
|
|
config.Concurrency.StageWorkers = documentIntMap(document.Concurrency.StageWorkers)
|
|
if document.Concurrency.TotalLLM != nil {
|
|
config.Concurrency.TotalLLM = *document.Concurrency.TotalLLM
|
|
}
|
|
}
|
|
if document.PromptKit != nil {
|
|
if document.PromptKit.ProfileDir != nil {
|
|
config.PromptKit.ProfileDir = *document.PromptKit.ProfileDir
|
|
}
|
|
if document.PromptKit.ProfileFile != nil {
|
|
config.PromptKit.ProfileFile = *document.PromptKit.ProfileFile
|
|
}
|
|
if document.PromptKit.LocalBackend != nil {
|
|
backend := PromptKitLocalBackendConfig{}
|
|
if document.PromptKit.LocalBackend.Endpoint != nil {
|
|
backend.Endpoint = *document.PromptKit.LocalBackend.Endpoint
|
|
}
|
|
if document.PromptKit.LocalBackend.ConcurrencyLimit != nil {
|
|
backend.ConcurrencyLimit = *document.PromptKit.LocalBackend.ConcurrencyLimit
|
|
}
|
|
config.PromptKit.LocalBackend = &backend
|
|
}
|
|
}
|
|
if document.Output != nil && document.Output.Directory != nil {
|
|
config.Output.Directory = *document.Output.Directory
|
|
}
|
|
if document.Cache != nil {
|
|
if document.Cache.ChunkPlans != nil {
|
|
if document.Cache.ChunkPlans.Directory != nil {
|
|
config.Cache.ChunkPlans.Directory = *document.Cache.ChunkPlans.Directory
|
|
}
|
|
if document.Cache.ChunkPlans.Mode != nil {
|
|
mode, err := pipeline.ParseChunkCacheMode(*document.Cache.ChunkPlans.Mode)
|
|
if err != nil {
|
|
return Config{}, fmt.Errorf("convert chunk-plan cache mode: %w", err)
|
|
}
|
|
config.Cache.ChunkPlans.Mode = mode
|
|
}
|
|
}
|
|
if document.Cache.Checkpoints != nil {
|
|
if document.Cache.Checkpoints.Enabled != nil {
|
|
config.Cache.Checkpoints.Enabled = *document.Cache.Checkpoints.Enabled
|
|
}
|
|
if document.Cache.Checkpoints.Directory != nil {
|
|
config.Cache.Checkpoints.Directory = *document.Cache.Checkpoints.Directory
|
|
}
|
|
}
|
|
}
|
|
if document.Debug != nil && document.Debug.Directory != nil {
|
|
config.Debug.Directory = *document.Debug.Directory
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
func documentPipelinesToRuntime(documents map[string]PipelineDocument) map[string]pipeline.PipelineProfile {
|
|
if documents == nil {
|
|
return nil
|
|
}
|
|
profiles := make(map[string]pipeline.PipelineProfile, len(documents))
|
|
for id, document := range documents {
|
|
profiles[id] = documentPipelineToRuntime(id, document)
|
|
}
|
|
return profiles
|
|
}
|
|
|
|
func documentPipelineToRuntime(id string, document PipelineDocument) pipeline.PipelineProfile {
|
|
profile := pipeline.PipelineProfile{
|
|
ID: id,
|
|
StructuredOutputRepairAttempts: cloneDocumentInt(document.StructuredOutputRepairAttempts),
|
|
ValidationPolicy: documentPolicyToRuntime(document.ValidationPolicy),
|
|
Input: documentBindingToRuntime(document.Input),
|
|
Artifacts: documentLanesToRuntime(document.Artifacts),
|
|
References: documentReferencesToRuntime(document.References),
|
|
}
|
|
if document.LLMProfile != nil {
|
|
profile.LLMProfile = *document.LLMProfile
|
|
}
|
|
if document.Chunk != nil {
|
|
profile.Chunk = documentBindingToRuntime(*document.Chunk)
|
|
}
|
|
if document.Output != nil {
|
|
profile.Output = documentBindingToRuntime(*document.Output)
|
|
}
|
|
if document.Steps != nil {
|
|
profile.Steps = make([]pipeline.PipelineStepProfile, len(document.Steps))
|
|
for index, step := range document.Steps {
|
|
profile.Steps[index] = pipeline.PipelineStepProfile{
|
|
ID: step.ID,
|
|
Artifacts: documentLanesToRuntime(step.Artifacts),
|
|
References: documentReferencesToRuntime(step.References),
|
|
}
|
|
}
|
|
}
|
|
return profile
|
|
}
|
|
|
|
func documentLanesToRuntime(documents map[string]ArtifactLaneDocument) map[string]pipeline.ArtifactLaneProfile {
|
|
if documents == nil {
|
|
return nil
|
|
}
|
|
lanes := make(map[string]pipeline.ArtifactLaneProfile, len(documents))
|
|
for id, document := range documents {
|
|
lane := pipeline.ArtifactLaneProfile{
|
|
Extract: documentBindingToRuntime(document.Extract),
|
|
References: documentReferencesToRuntime(document.References),
|
|
}
|
|
if document.Merge != nil {
|
|
lane.Merge = documentBindingToRuntime(*document.Merge)
|
|
}
|
|
if document.Normalize != nil {
|
|
lane.Normalize = documentBindingToRuntime(*document.Normalize)
|
|
}
|
|
if document.Validators != nil {
|
|
lane.Validators = make([]pipeline.ModuleBinding, len(document.Validators))
|
|
for index, validator := range document.Validators {
|
|
lane.Validators[index] = documentValidatorToRuntime(validator)
|
|
}
|
|
}
|
|
lanes[id] = lane
|
|
}
|
|
return lanes
|
|
}
|
|
|
|
func documentBindingToRuntime(document BindingDocument) pipeline.ModuleBinding {
|
|
binding := pipeline.ModuleBinding{
|
|
Module: document.Module,
|
|
StructuredOutputRepairAttempts: cloneDocumentInt(document.StructuredOutputRepairAttempts),
|
|
ValidationPolicy: documentPolicyToRuntime(document.ValidationPolicy),
|
|
Retries: document.Retries,
|
|
Options: documentOptions(document.Options),
|
|
References: documentReferencesToRuntime(document.References),
|
|
}
|
|
if document.LLMProfile != nil {
|
|
binding.LLMProfile = *document.LLMProfile
|
|
}
|
|
if document.Validators != nil {
|
|
binding.Validators.Set = true
|
|
binding.Validators.Validators = make([]pipeline.ModuleBinding, len(document.Validators))
|
|
for index, validator := range document.Validators {
|
|
binding.Validators.Validators[index] = documentValidatorToRuntime(validator)
|
|
}
|
|
}
|
|
return binding
|
|
}
|
|
|
|
func documentValidatorToRuntime(document ValidatorDocument) pipeline.ModuleBinding {
|
|
binding := pipeline.ModuleBinding{
|
|
Module: document.Module,
|
|
StructuredOutputRepairAttempts: cloneDocumentInt(document.StructuredOutputRepairAttempts),
|
|
Retries: document.Retries,
|
|
Options: documentOptions(document.Options),
|
|
}
|
|
if document.LLMProfile != nil {
|
|
binding.LLMProfile = *document.LLMProfile
|
|
}
|
|
return binding
|
|
}
|
|
|
|
func documentReferencesToRuntime(documents map[string]ReferenceDocument) map[string]pipeline.ReferenceSource {
|
|
if documents == nil {
|
|
return nil
|
|
}
|
|
references := make(map[string]pipeline.ReferenceSource, len(documents))
|
|
for name, document := range documents {
|
|
if document.Artifact != nil {
|
|
references[name] = pipeline.GeneratedReference(document.Artifact.Step, document.Artifact.Lane)
|
|
continue
|
|
}
|
|
if document.Path != nil {
|
|
references[name] = pipeline.ExternalReference(*document.Path)
|
|
}
|
|
}
|
|
return references
|
|
}
|
|
|
|
func documentPolicyToRuntime(document *ValidationPolicyDocument) *pipeline.ValidationPolicyOverride {
|
|
if document == nil {
|
|
return nil
|
|
}
|
|
policy := pipeline.ValidationPolicyOverride{}
|
|
if document.ProducerStructuralFailure != nil {
|
|
value := *document.ProducerStructuralFailure
|
|
policy.ProducerStructuralFailure = &value
|
|
}
|
|
if document.SemanticRejection != nil {
|
|
value := *document.SemanticRejection
|
|
policy.SemanticRejection = &value
|
|
}
|
|
if document.ValidatorFailure != nil {
|
|
value := *document.ValidatorFailure
|
|
policy.ValidatorFailure = &value
|
|
}
|
|
return &policy
|
|
}
|
|
|
|
func documentIntMap(values map[string]int) map[string]int {
|
|
if values == nil {
|
|
return nil
|
|
}
|
|
result := make(map[string]int, len(values))
|
|
for key, value := range values {
|
|
result[key] = value
|
|
}
|
|
return result
|
|
}
|
|
|
|
func cloneDocumentInt(value *int) *int {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
result := *value
|
|
return &result
|
|
}
|
|
|
|
func documentOptions(values map[string]any) map[string]any {
|
|
if values == nil {
|
|
return nil
|
|
}
|
|
result := make(map[string]any, len(values))
|
|
for key, value := range values {
|
|
result[key] = cloneDocumentOption(value)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func cloneDocumentOption(value any) any {
|
|
switch typed := value.(type) {
|
|
case map[string]any:
|
|
return documentOptions(typed)
|
|
case []any:
|
|
result := make([]any, len(typed))
|
|
for index, item := range typed {
|
|
result[index] = cloneDocumentOption(item)
|
|
}
|
|
return result
|
|
default:
|
|
return typed
|
|
}
|
|
}
|