Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,21 @@ jobs:
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- run: go run ./cmd/openrouter/main.go
- name: Restore APIpie cache
uses: actions/cache@v4
with:
path: cmd/apipie/cache.db
key: apipie-cache-${{ hashFiles('cmd/apipie/cache.go') }}
restore-keys: |
apipie-cache-
- name: Generate OpenRouter models
run: go run ./cmd/openrouter/main.go
# we need to add this back when we know that the providers/models all work
# - run: go run ./cmd/huggingface/main.go
- name: Generate APIpie models
env:
APIPIE_DISPLAY_NAME_API_KEY: ${{ secrets.APIPIE_DISPLAY_NAME_API_KEY }}
run: go run ./cmd/apipie/main.go ./cmd/apipie/cache.go
- uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v5
with:
commit_message: "chore: auto-update generated files"
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ go.work.sum
# crush
.crush
dist/

# apipie model name cache
cmd/apipie/cache.db
6 changes: 6 additions & 0 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ tasks:
cmds:
- go run cmd/openrouter/main.go

generate:apipie:
desc: Generate APIpie models
aliases: [gen:apipie]
cmds:
- go run cmd/apipie/main.go

lint:
desc: Run linters
cmds:
Expand Down
36 changes: 36 additions & 0 deletions cmd/apipie/README.md
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
```
197 changes: 197 additions & 0 deletions cmd/apipie/cache.go
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
}
Loading