Use ConfigKit mapping values directly
This commit is contained in:
@@ -78,7 +78,8 @@ type AuthenticationProbeConfig struct {
|
||||
// It encodes fields without validating them; NewEngine validates the final
|
||||
// effective configuration after all selected source layers are combined.
|
||||
// Mapping captures a copy of supported input values at creation; unsupported
|
||||
// values fail when the source is loaded.
|
||||
// values fail when the source is loaded. ExtraParams must use ConfigKit Mapping
|
||||
// input forms; callers must explicitly convert numbers or containers as needed.
|
||||
func BackendSource(label string, backends []BackendConfig) configkit.Source {
|
||||
definitions := make([]any, 0, len(backends))
|
||||
for _, config := range backends {
|
||||
@@ -96,7 +97,7 @@ func BackendSource(label string, backends []BackendConfig) configkit.Source {
|
||||
fields["queue_capacity"] = *config.QueueCapacity
|
||||
}
|
||||
if config.ExtraParams != nil {
|
||||
fields["extra_params"] = providerMapForMapping(config.ExtraParams)
|
||||
fields["extra_params"] = config.ExtraParams
|
||||
}
|
||||
if config.SmokeTests != nil {
|
||||
smokeTests := make(map[string]any)
|
||||
|
||||
@@ -178,7 +178,12 @@ input := configkit.Input{Source: document}
|
||||
```
|
||||
|
||||
`ProfileSource` and `BackendSource` are smaller typed conveniences when null
|
||||
is not needed. Backend and profile definitions are supplied by the caller;
|
||||
is not needed. Their provider maps use ConfigKit Mapping input forms: built-in
|
||||
scalars, `[]any`, and `map[string]any` recursively. Convert `json.Number` or
|
||||
typed containers explicitly to an appropriate supported value, choosing numeric
|
||||
precision for your application. Mapping captures supported values immediately;
|
||||
unsupported or cyclic values are reported when loaded. Runtime override values
|
||||
follow their separate rules. Backend and profile definitions are supplied by the caller;
|
||||
there is no registration-only backend namespace.
|
||||
|
||||
When configuration or message files change, build and validate a new engine,
|
||||
|
||||
@@ -93,6 +93,10 @@ source-helper GoDoc and source/construction contract tests.
|
||||
capture and deferred upstream diagnostics, not acceptance of `json.Number`,
|
||||
typed containers, named scalars, or nested pointers by the removed adapter.
|
||||
|
||||
Completion: convenience sources pass provider maps directly to ConfigKit;
|
||||
the converter was removed. Focused source and construction tests passed with
|
||||
Go 1.27.3, including nested capture and deferred unsupported/cyclic errors.
|
||||
|
||||
## Stage 3: Represent The Owning Error Operation
|
||||
|
||||
**Catalog:** 7, first part. **Files:** use-case error/runner/preparation boundaries.
|
||||
|
||||
@@ -25,7 +25,8 @@ type ProfileConfig struct {
|
||||
// It encodes fields without validating them; NewEngine validates the final
|
||||
// effective configuration after all selected source layers are combined.
|
||||
// Mapping captures a copy of supported input values at creation; unsupported
|
||||
// values fail when the source is loaded.
|
||||
// values fail when the source is loaded. ExtraParams must use ConfigKit Mapping
|
||||
// input forms; callers must explicitly convert numbers or containers as needed.
|
||||
func ProfileSource(label string, profiles []ProfileConfig) configkit.Source {
|
||||
definitions := make([]any, 0, len(profiles))
|
||||
for _, config := range profiles {
|
||||
@@ -67,7 +68,7 @@ func ProfileSource(label string, profiles []ProfileConfig) configkit.Source {
|
||||
fields["api_key_required"] = *config.APIKeyRequired
|
||||
}
|
||||
if config.ExtraParams != nil {
|
||||
fields["extra_params"] = providerMapForMapping(config.ExtraParams)
|
||||
fields["extra_params"] = config.ExtraParams
|
||||
}
|
||||
definitions = append(definitions, fields)
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
package promptkit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type providerVisit struct {
|
||||
typ reflect.Type
|
||||
ptr uintptr
|
||||
}
|
||||
|
||||
type invalidProviderNumber struct{}
|
||||
|
||||
// providerMapForMapping converts JSON-shaped Go containers to ConfigKit's
|
||||
// built-in mapping containers. Unsupported values remain for Mapping to report
|
||||
// as deferred source diagnostics during construction.
|
||||
func providerMapForMapping(values map[string]any) map[string]any {
|
||||
if values == nil {
|
||||
return nil
|
||||
}
|
||||
converted := make(map[string]any, len(values))
|
||||
active := make(map[providerVisit]bool)
|
||||
for key, value := range values {
|
||||
converted[key] = providerValueForMapping(reflect.ValueOf(value), active, 0)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func providerValueForMapping(value reflect.Value, active map[providerVisit]bool, depth int) any {
|
||||
if !value.IsValid() {
|
||||
return nil
|
||||
}
|
||||
if depth > 100 {
|
||||
return value.Interface()
|
||||
}
|
||||
for value.Kind() == reflect.Interface || value.Kind() == reflect.Pointer {
|
||||
if value.IsNil() {
|
||||
return nil
|
||||
}
|
||||
if value.Kind() == reflect.Pointer {
|
||||
visit := providerVisit{typ: value.Type(), ptr: value.Pointer()}
|
||||
if active[visit] {
|
||||
return value.Interface()
|
||||
}
|
||||
active[visit] = true
|
||||
defer delete(active, visit)
|
||||
}
|
||||
value = value.Elem()
|
||||
}
|
||||
if value.CanInterface() {
|
||||
if number, ok := value.Interface().(json.Number); ok {
|
||||
if _, err := json.Marshal(number); err != nil {
|
||||
return invalidProviderNumber{}
|
||||
}
|
||||
if integer, err := strconv.ParseInt(string(number), 10, 64); err == nil {
|
||||
return integer
|
||||
}
|
||||
if integer, err := strconv.ParseUint(string(number), 10, 64); err == nil {
|
||||
return integer
|
||||
}
|
||||
if decimal, err := strconv.ParseFloat(string(number), 64); err == nil {
|
||||
return decimal
|
||||
}
|
||||
return number
|
||||
}
|
||||
}
|
||||
switch value.Kind() {
|
||||
case reflect.Map:
|
||||
if value.IsNil() {
|
||||
return nil
|
||||
}
|
||||
if value.Type().Key().Kind() != reflect.String {
|
||||
return value.Interface()
|
||||
}
|
||||
visit := providerVisit{typ: value.Type(), ptr: value.Pointer()}
|
||||
if active[visit] {
|
||||
return value.Interface()
|
||||
}
|
||||
active[visit] = true
|
||||
defer delete(active, visit)
|
||||
converted := make(map[string]any, value.Len())
|
||||
iter := value.MapRange()
|
||||
for iter.Next() {
|
||||
converted[iter.Key().String()] = providerValueForMapping(iter.Value(), active, depth+1)
|
||||
}
|
||||
return converted
|
||||
case reflect.Slice, reflect.Array:
|
||||
if value.Kind() == reflect.Slice {
|
||||
if value.IsNil() {
|
||||
return nil
|
||||
}
|
||||
visit := providerVisit{typ: value.Type(), ptr: value.Pointer()}
|
||||
if visit.ptr != 0 {
|
||||
if active[visit] {
|
||||
return value.Interface()
|
||||
}
|
||||
active[visit] = true
|
||||
defer delete(active, visit)
|
||||
}
|
||||
}
|
||||
converted := make([]any, value.Len())
|
||||
for index := range converted {
|
||||
converted[index] = providerValueForMapping(value.Index(index), active, depth+1)
|
||||
}
|
||||
return converted
|
||||
case reflect.String:
|
||||
return value.String()
|
||||
case reflect.Bool:
|
||||
return value.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return value.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return value.Uint()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return value.Float()
|
||||
default:
|
||||
return value.Interface()
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package promptkit_test
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -73,3 +74,51 @@ func TestProgrammaticProfileVariantInherits(t *testing.T) {
|
||||
t.Fatalf("inherited profile = %+v, %v", profile, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgrammaticSourcesCaptureNestedValuesAndDeferInvalidValues(t *testing.T) {
|
||||
endpoint := "https://example.invalid/v1"
|
||||
type namedString string
|
||||
cyclic := map[string]any{}
|
||||
cyclic["self"] = cyclic
|
||||
for _, kind := range []string{"backend", "profile"} {
|
||||
makeSource := func(params map[string]any) configkit.Source {
|
||||
if kind == "backend" {
|
||||
return promptkit.BackendSource("backend", []promptkit.BackendConfig{{ID: "local", Endpoint: &endpoint, ExtraParams: params}})
|
||||
}
|
||||
return promptkit.ProfileSource("profile", []promptkit.ProfileConfig{{ID: "local", ExtraParams: params}})
|
||||
}
|
||||
load := func(source configkit.Source) (*promptkit.Engine, error) {
|
||||
return promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{Source: source}}}, promptkit.WithBundledOpenRouter(false))
|
||||
}
|
||||
t.Run(kind+"/capture", func(t *testing.T) {
|
||||
values := map[string]any{"nested": map[string]any{"items": []any{"original", int64(42), true, nil}}}
|
||||
source := makeSource(values)
|
||||
values["nested"].(map[string]any)["items"].([]any)[0] = "mutated"
|
||||
engine, err := load(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
view, err := engine.InspectConfiguration()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var output bytes.Buffer
|
||||
if err := view.WriteEffective(&output); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(output.String(), "original") || strings.Contains(output.String(), "mutated") {
|
||||
t.Fatalf("captured configuration = %s", output.String())
|
||||
}
|
||||
})
|
||||
for name, value := range map[string]any{"number": json.Number("1.25"), "typed slice": []string{"value"}, "named scalar": namedString("value"), "pointer": &endpoint, "cycle": cyclic} {
|
||||
t.Run(kind+"/"+name, func(t *testing.T) {
|
||||
source := makeSource(map[string]any{"value": value})
|
||||
_, err := load(source)
|
||||
var loadErr *configkit.LoadError
|
||||
if !errors.As(err, &loadErr) || len(loadErr.Diagnostics()) == 0 {
|
||||
t.Fatalf("source error = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user