Adopt PromptKit profile inheritance
This commit is contained in:
@@ -175,15 +175,38 @@ model: file-light
|
||||
}
|
||||
assertProfile(t, fileAdapter, "weather-light", "", "file-light")
|
||||
|
||||
directory := testProfileDirectory(t, `id: weather-light
|
||||
directory := testProfileDirectory(t, map[string]string{"profile.yml": `id: weather-light
|
||||
backend: local
|
||||
model: directory-light
|
||||
`)
|
||||
`})
|
||||
directoryAdapter, err := New(Config{ProfileDirectory: directory, LocalEndpoint: "https://local-directory.example/v1"})
|
||||
if err != nil {
|
||||
t.Fatalf("New(profile directory) error = %v", err)
|
||||
}
|
||||
assertProfile(t, directoryAdapter, "weather-light", promptkit.BackendLocal, "directory-light")
|
||||
|
||||
derived := writeProfileFile(t, `id: weather-light
|
||||
base_profile: gemini-flash-latest
|
||||
`)
|
||||
derivedAdapter, err := New(Config{ProfileFile: derived})
|
||||
if err != nil {
|
||||
t.Fatalf("New(derived profile) error = %v", err)
|
||||
}
|
||||
assertProfile(t, derivedAdapter, "weather-light", "openrouter", "~google/gemini-flash-latest")
|
||||
}
|
||||
|
||||
func TestConfiguredBaseProfileOverridesEmbeddedProfileTarget(t *testing.T) {
|
||||
directory := testProfileDirectory(t, map[string]string{
|
||||
"deepseek.yml": `id: deepseek-4-flash
|
||||
backend: local
|
||||
model: shadowed-deepseek
|
||||
`,
|
||||
})
|
||||
adapter, err := New(Config{ProfileDirectory: directory, LocalEndpoint: "https://local-directory.example/v1"})
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
}
|
||||
assertProfile(t, adapter, "weather-light", promptkit.BackendLocal, "shadowed-deepseek")
|
||||
}
|
||||
|
||||
func TestMaintainedWeatherLightLocalProfileExampleInspectsOffline(t *testing.T) {
|
||||
@@ -195,18 +218,18 @@ func TestMaintainedWeatherLightLocalProfileExampleInspectsOffline(t *testing.T)
|
||||
}
|
||||
|
||||
func TestProfileResolutionFallsThroughOnlyWhenTheConfiguredIDIsAbsent(t *testing.T) {
|
||||
absentAdapter, err := New(Config{ProfileDirectory: testProfileDirectory(t, `id: other-profile
|
||||
absentAdapter, err := New(Config{ProfileDirectory: testProfileDirectory(t, map[string]string{"profile.yml": `id: other-profile
|
||||
backend: openrouter
|
||||
model: other-model
|
||||
`)})
|
||||
`})})
|
||||
if err != nil {
|
||||
t.Fatalf("New(absent profile) error = %v", err)
|
||||
}
|
||||
assertProfile(t, absentAdapter, "weather-light", "openrouter", "deepseek/deepseek-v4-flash")
|
||||
|
||||
malformedAdapter, err := New(Config{ProfileDirectory: testProfileDirectory(t, `id: weather-light
|
||||
malformedAdapter, err := New(Config{ProfileDirectory: testProfileDirectory(t, map[string]string{"profile.yml": `id: weather-light
|
||||
backend: openrouter
|
||||
`)})
|
||||
`})})
|
||||
if err != nil {
|
||||
t.Fatalf("New(malformed profile) error = %v", err)
|
||||
}
|
||||
@@ -215,6 +238,78 @@ backend: openrouter
|
||||
}
|
||||
}
|
||||
|
||||
func TestProfileResolutionReturnsConfiguredInheritanceFailures(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
profile string
|
||||
profiles map[string]string
|
||||
}{
|
||||
{
|
||||
name: "missing base",
|
||||
profile: "missing-base",
|
||||
profiles: map[string]string{"missing.yml": `id: missing-base
|
||||
base_profile: unavailable
|
||||
`},
|
||||
},
|
||||
{
|
||||
name: "cyclic bases",
|
||||
profile: "first",
|
||||
profiles: map[string]string{
|
||||
"first.yml": `id: first
|
||||
base_profile: second
|
||||
`,
|
||||
"second.yml": `id: second
|
||||
base_profile: first
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "malformed base",
|
||||
profile: "child",
|
||||
profiles: map[string]string{
|
||||
"child.yml": `id: child
|
||||
base_profile: malformed
|
||||
`,
|
||||
"malformed.yml": `id: malformed
|
||||
base_profile: [not-a-profile]
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "incomplete target",
|
||||
profile: "incomplete",
|
||||
profiles: map[string]string{"incomplete.yml": `id: incomplete
|
||||
backend: openrouter
|
||||
`},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
adapter, err := New(Config{ProfileDirectory: testProfileDirectory(t, test.profiles)})
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
}
|
||||
if _, err := adapter.InspectProfile(context.Background(), test.profile); err == nil {
|
||||
t.Fatal("InspectProfile() error = nil, want configured inheritance error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRakestrawhomeBuiltInProfileInspectsOffline(t *testing.T) {
|
||||
adapter, err := New(Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
}
|
||||
profile, err := adapter.InspectProfile(context.Background(), "rakestrawhome-gemma-4-31b")
|
||||
if err != nil {
|
||||
t.Fatalf("InspectProfile() error = %v", err)
|
||||
}
|
||||
if profile.ProfileID != "rakestrawhome-gemma-4-31b" || profile.BackendID != "rakestrawhome" || profile.ModelName == "" {
|
||||
t.Fatalf("profile = %#v", profile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProfileResolutionPreservesBuiltInAndExplicitPrecedence(t *testing.T) {
|
||||
adapter, err := New(Config{})
|
||||
if err != nil {
|
||||
@@ -509,10 +604,10 @@ func TestNewValidatesConfiguration(t *testing.T) {
|
||||
|
||||
func TestLocalBackendAndOptionalCredentialSourceBehavior(t *testing.T) {
|
||||
t.Setenv("WEATHERREPORTER_TEST_MISSING_KEY", "")
|
||||
profiles := testProfileDirectory(t, `id: local-profile
|
||||
profiles := testProfileDirectory(t, map[string]string{"profile.yml": `id: local-profile
|
||||
backend: local
|
||||
model: local-model
|
||||
`)
|
||||
`})
|
||||
adapter, err := newAdapterForTest(Config{
|
||||
ProfileDirectory: profiles,
|
||||
LocalEndpoint: "https://local.example/v1",
|
||||
@@ -529,11 +624,11 @@ model: local-model
|
||||
t.Fatalf("capacity classification = %v", got)
|
||||
}
|
||||
|
||||
credentialProfiles := testProfileDirectory(t, `id: credential-profile
|
||||
credentialProfiles := testProfileDirectory(t, map[string]string{"profile.yml": `id: credential-profile
|
||||
endpoint: https://profile.example/v1
|
||||
model: test-model
|
||||
api_key_env: WEATHERREPORTER_TEST_MISSING_KEY
|
||||
`)
|
||||
`})
|
||||
client := &fakeClient{response: validResponse()}
|
||||
credentialAdapter, err := newAdapterForTest(Config{ProfileDirectory: credentialProfiles}, client)
|
||||
if err != nil {
|
||||
@@ -568,14 +663,14 @@ func assertProfile(t *testing.T, adapter *Adapter, id string, backend string, mo
|
||||
|
||||
func newTestAdapterWithOptions(t *testing.T, client promptkit.LLMClient, options ...promptkit.Option) *Adapter {
|
||||
t.Helper()
|
||||
profiles := testProfileDirectory(t, `id: test-profile
|
||||
profiles := testProfileDirectory(t, map[string]string{"profile.yml": `id: test-profile
|
||||
endpoint: https://profile.example/v1
|
||||
model: test-model
|
||||
temperature: 0.2
|
||||
max_tokens: 300
|
||||
top_p: 1
|
||||
timeout_seconds: 30
|
||||
`)
|
||||
`})
|
||||
options = append(options, promptkit.WithLLMClient(client))
|
||||
adapter, err := newAdapter(Config{ProfileDirectory: profiles, Timeout: time.Second}, options...)
|
||||
if err != nil {
|
||||
@@ -584,13 +679,15 @@ timeout_seconds: 30
|
||||
return adapter
|
||||
}
|
||||
|
||||
func testProfileDirectory(t *testing.T, profile string) string {
|
||||
func testProfileDirectory(t *testing.T, profiles map[string]string) string {
|
||||
t.Helper()
|
||||
profiles := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(profiles, "profile.yml"), []byte(profile), 0o600); err != nil {
|
||||
t.Fatalf("write profile: %v", err)
|
||||
directory := t.TempDir()
|
||||
for name, profile := range profiles {
|
||||
if err := os.WriteFile(filepath.Join(directory, name), []byte(profile), 0o600); err != nil {
|
||||
t.Fatalf("write profile: %v", err)
|
||||
}
|
||||
}
|
||||
return profiles
|
||||
return directory
|
||||
}
|
||||
|
||||
func writeProfileFile(t *testing.T, profile string) string {
|
||||
|
||||
Reference in New Issue
Block a user