-
Notifications
You must be signed in to change notification settings - Fork 79
Add APIpie provider #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
actus-wirtenberger
wants to merge
2
commits into
charmbracelet:main
Choose a base branch
from
actus-ag:apipie-provider
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,3 +34,6 @@ go.work.sum | |
| # crush | ||
| .crush | ||
| dist/ | ||
|
|
||
| # apipie model name cache | ||
| cmd/apipie/cache.db | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # APIpie Model Configuration Generator | ||
|
|
||
| This tool fetches models from APIpie.ai and generates a configuration file for the provider. | ||
|
|
||
| ## LLM-Enhanced Display Names | ||
|
|
||
| This tool includes an optional feature to generate professional display names for AI models using APIpie.ai's LLM service. This feature is **sponsored** to improve the user experience of this open source project. | ||
|
|
||
| ### Configuration | ||
|
|
||
| Set the following environment variable: | ||
|
|
||
| ```bash | ||
| # Required for LLM-enhanced display names (donated API key) | ||
| export APIPIE_DISPLAY_NAME_API_KEY="your-apipie-api-key" | ||
| ``` | ||
|
|
||
| ### Behavior | ||
|
|
||
| - **With API key**: Uses Claude Sonnet 4.5 via APIpie.ai to generate professional display names | ||
| - Example: `gpt-4o-2024-11-20` → `"GPT-4o (2024-11-20)"` | ||
| - Example: `claude-3-5-sonnet` → `"Claude 3.5 Sonnet"` | ||
|
|
||
| - **Without API key or on failure**: Falls back to using the raw model ID as display name | ||
| - Example: `gpt-4o-2024-11-20` → `"gpt-4o-2024-11-20"` | ||
| - This ensures the tool **never breaks** due to API issues | ||
|
|
||
| ### Usage | ||
|
|
||
| ```bash | ||
| # Generate configuration with LLM-enhanced names | ||
| go run cmd/apipie/main.go | ||
|
|
||
| # The generated config will be saved to: | ||
| # internal/providers/configs/apipie.json | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "crypto/sha256" | ||
| "database/sql" | ||
| "fmt" | ||
| "log" | ||
| "time" | ||
|
|
||
| _ "modernc.org/sqlite" | ||
| ) | ||
|
|
||
| // CacheEntry represents a cached display name for a model | ||
| type CacheEntry struct { | ||
| ModelID string | ||
| DescriptionHash string | ||
| DisplayName string | ||
| CreatedAt time.Time | ||
| } | ||
|
|
||
| // Cache manages the SQLite database for caching LLM-generated display names | ||
| type Cache struct { | ||
| db *sql.DB | ||
| } | ||
|
|
||
| // NewCache creates a new cache instance and initializes the database | ||
| func NewCache(dbPath string) (*Cache, error) { | ||
| db, err := sql.Open("sqlite", dbPath) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to open database: %w", err) | ||
| } | ||
|
|
||
| cache := &Cache{db: db} | ||
| if err := cache.initSchema(); err != nil { | ||
| db.Close() | ||
| return nil, fmt.Errorf("failed to initialize schema: %w", err) | ||
| } | ||
|
|
||
| return cache, nil | ||
| } | ||
|
|
||
| // Close closes the database connection | ||
| func (c *Cache) Close() error { | ||
| return c.db.Close() | ||
| } | ||
|
|
||
| // initSchema creates the cache table if it doesn't exist | ||
| func (c *Cache) initSchema() error { | ||
| query := ` | ||
| CREATE TABLE IF NOT EXISTS display_name_cache ( | ||
| model_id TEXT NOT NULL, | ||
| description_hash TEXT NOT NULL, | ||
| display_name TEXT NOT NULL, | ||
| created_at DATETIME NOT NULL, | ||
| PRIMARY KEY (model_id, description_hash) | ||
| ); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_model_id ON display_name_cache(model_id); | ||
| CREATE INDEX IF NOT EXISTS idx_created_at ON display_name_cache(created_at); | ||
|
|
||
| CREATE TABLE IF NOT EXISTS reasoning_effort_cache ( | ||
| description_hash TEXT NOT NULL PRIMARY KEY, | ||
| has_reasoning_effort BOOLEAN NOT NULL, | ||
| created_at DATETIME NOT NULL | ||
| ); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_reasoning_created_at ON reasoning_effort_cache(created_at); | ||
| ` | ||
|
|
||
| _, err := c.db.Exec(query) | ||
| return err | ||
| } | ||
|
|
||
|
|
||
|
|
||
| // hashDescription creates a SHA256 hash of the model description (legacy function) | ||
| // This allows us to detect when descriptions change and invalidate cache | ||
| func hashDescription(description string) string { | ||
| hash := sha256.Sum256([]byte(description)) | ||
| return fmt.Sprintf("%x", hash) | ||
| } | ||
|
|
||
| // Get retrieves a cached display name for a model | ||
| // Returns empty string if not found or metadata has changed | ||
| func (c *Cache) Get(model Model) string { | ||
| metadataHash := hashModelMetadata(model) | ||
|
|
||
| var displayName string | ||
| query := `SELECT display_name FROM display_name_cache | ||
| WHERE model_id = ? AND description_hash = ?` | ||
|
|
||
| err := c.db.QueryRow(query, model.ID, metadataHash).Scan(&displayName) | ||
| if err != nil { | ||
| if err != sql.ErrNoRows { | ||
| log.Printf("Cache get error for model %s: %v", model.ID, err) | ||
| } | ||
| return "" | ||
| } | ||
|
|
||
| return displayName | ||
| } | ||
|
|
||
| // Set stores a display name in the cache | ||
| func (c *Cache) Set(model Model, displayName string) error { | ||
| metadataHash := hashModelMetadata(model) | ||
|
|
||
| query := `INSERT OR REPLACE INTO display_name_cache | ||
| (model_id, description_hash, display_name, created_at) | ||
| VALUES (?, ?, ?, ?)` | ||
|
|
||
| _, err := c.db.Exec(query, model.ID, metadataHash, displayName, time.Now()) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to cache display name for model %s: %w", model.ID, err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // GetStats returns cache statistics | ||
| func (c *Cache) GetStats() (int, error) { | ||
| var count int | ||
| err := c.db.QueryRow("SELECT COUNT(*) FROM display_name_cache").Scan(&count) | ||
| return count, err | ||
| } | ||
|
|
||
| // CleanOldEntries removes cache entries older than the specified duration | ||
| // This helps keep the cache size manageable | ||
| func (c *Cache) CleanOldEntries(maxAge time.Duration) error { | ||
| cutoff := time.Now().Add(-maxAge) | ||
|
|
||
| // Clean display name cache | ||
| query := `DELETE FROM display_name_cache WHERE created_at < ?` | ||
| result, err := c.db.Exec(query, cutoff) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to clean old display name entries: %w", err) | ||
| } | ||
|
|
||
| rowsAffected, _ := result.RowsAffected() | ||
| if rowsAffected > 0 { | ||
| log.Printf("Cleaned %d old display name cache entries", rowsAffected) | ||
| } | ||
|
|
||
| // Clean reasoning effort cache | ||
| query = `DELETE FROM reasoning_effort_cache WHERE created_at < ?` | ||
| result, err = c.db.Exec(query, cutoff) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to clean old reasoning effort entries: %w", err) | ||
| } | ||
|
|
||
| rowsAffected, _ = result.RowsAffected() | ||
| if rowsAffected > 0 { | ||
| log.Printf("Cleaned %d old reasoning effort cache entries", rowsAffected) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // GetReasoningEffort retrieves cached reasoning effort analysis for a description | ||
| func (c *Cache) GetReasoningEffort(description string) (bool, bool) { | ||
| if description == "" { | ||
| return false, false | ||
| } | ||
|
|
||
| hash := hashDescription(description) | ||
|
|
||
| var hasEffort bool | ||
| err := c.db.QueryRow( | ||
| "SELECT has_reasoning_effort FROM reasoning_effort_cache WHERE description_hash = ?", | ||
| hash, | ||
| ).Scan(&hasEffort) | ||
|
|
||
| if err != nil { | ||
| return false, false // Cache miss | ||
| } | ||
|
|
||
| return hasEffort, true // Cache hit | ||
| } | ||
|
|
||
| // SetReasoningEffort stores reasoning effort analysis result in cache | ||
| func (c *Cache) SetReasoningEffort(description string, hasEffort bool) error { | ||
| if description == "" { | ||
| return nil | ||
| } | ||
|
|
||
| hash := hashDescription(description) | ||
|
|
||
| _, err := c.db.Exec( | ||
| "INSERT OR REPLACE INTO reasoning_effort_cache (description_hash, has_reasoning_effort, created_at) VALUES (?, ?, ?)", | ||
| hash, hasEffort, time.Now(), | ||
| ) | ||
|
|
||
| if err != nil { | ||
| return fmt.Errorf("failed to cache reasoning effort: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably just rename this to say Generate Models and just add another line for the APIpie script.