package llm import ( "context" "errors" "fmt" "strings" "gitea.maximumdirect.net/eric/promptkit" ) type PromptKitProfileSourceConfig struct { ProfileDir string ProfileFile string LocalBackend *PromptKitLocalBackendConfig } type PromptKitProfileInspectorConfig struct { Source PromptKitProfileSourceConfig Assets *AssetRegistry } func (c PromptKitProfileSourceConfig) localEndpoint() string { if c.LocalBackend == nil { return "" } return c.LocalBackend.Endpoint } type PromptKitProfileInspector struct { engine *promptkit.Engine } type PromptKitProfileInspection struct { ProfileID string BackendID string Model string CredentialEnvironment string CredentialRequired bool } type PromptKitProfileInspectionError struct { ProfileID string err error } func (e *PromptKitProfileInspectionError) Error() string { switch { case errors.Is(e.err, promptkit.ErrProfileNotFound): return fmt.Sprintf("PromptKit profile %q is not configured", e.ProfileID) case errors.Is(e.err, promptkit.ErrInvalidRequest): return fmt.Sprintf("PromptKit profile ID %q is invalid", e.ProfileID) case errors.Is(e.err, promptkit.ErrProfileLoad): return fmt.Sprintf("PromptKit profile %q is invalid or unreadable", e.ProfileID) default: return fmt.Sprintf("PromptKit profile %q could not be inspected", e.ProfileID) } } func (e *PromptKitProfileInspectionError) Unwrap() error { return e.err } type promptKitProfileConfigurationError struct { err error } func (e *promptKitProfileConfigurationError) Error() string { return "PromptKit profile configuration is invalid or unreadable" } func (e *promptKitProfileConfigurationError) Unwrap() error { return e.err } func NewPromptKitProfileInspector(cfg PromptKitProfileInspectorConfig) (*PromptKitProfileInspector, error) { source, options, err := promptKitProfileSourceEngineOptions(cfg.Source) if err != nil { return nil, err } if cfg.Assets != nil { fallbackOption, hasFallback, err := cfg.Assets.promptKitFallbackProfileOption() if err != nil { return nil, err } if hasFallback { options = append(options, fallbackOption) } } engine, err := promptkit.NewEngine(promptkit.Config{ PromptDir: ".", ProfileDir: source.ProfileDir, }, options...) if err != nil { return nil, &promptKitProfileConfigurationError{err: err} } return &PromptKitProfileInspector{engine: engine}, nil } func (i *PromptKitProfileInspector) InspectProfile(ctx context.Context, profileID string) (PromptKitProfileInspection, error) { if i == nil || i.engine == nil { return PromptKitProfileInspection{}, fmt.Errorf("PromptKit profile inspector must not be nil") } profileID = strings.TrimSpace(profileID) inspection, err := i.engine.InspectProfile(ctx, profileID) if err != nil { if ctxErr := ctx.Err(); ctxErr != nil { return PromptKitProfileInspection{}, ctxErr } return PromptKitProfileInspection{}, &PromptKitProfileInspectionError{ ProfileID: profileID, err: err, } } return PromptKitProfileInspection{ ProfileID: inspection.ProfileID, BackendID: strings.TrimSpace(inspection.EffectiveModelParams.BackendID), Model: strings.TrimSpace(inspection.EffectiveModelParams.Model), CredentialEnvironment: strings.TrimSpace(inspection.EffectiveModelParams.APIKeyEnv), CredentialRequired: inspection.APIKeyRequired, }, nil } func promptKitProfileSourceEngineOptions(cfg PromptKitProfileSourceConfig) (PromptKitProfileSourceConfig, []promptkit.Option, error) { source := PromptKitProfileSourceConfig{ ProfileDir: strings.TrimSpace(cfg.ProfileDir), ProfileFile: strings.TrimSpace(cfg.ProfileFile), } if source.ProfileDir != "" && source.ProfileFile != "" { return PromptKitProfileSourceConfig{}, nil, fmt.Errorf("PromptKit profile_dir and profile_file are mutually exclusive") } if cfg.LocalBackend != nil { localBackend := *cfg.LocalBackend localBackend.Endpoint = strings.TrimSpace(localBackend.Endpoint) source.LocalBackend = &localBackend } var options []promptkit.Option if source.ProfileFile != "" { options = append(options, promptkit.WithProfileFile(source.ProfileFile)) } if source.LocalBackend != nil { options = append(options, PromptKitLocalBackendOption(*source.LocalBackend)) } return source, options, nil }