From cf3645e1dd280f386cecc3bcb0004e36f32951a5 Mon Sep 17 00:00:00 2001 From: Ilya Sharov Date: Thu, 10 Apr 2025 20:26:26 +0300 Subject: [PATCH] added model filtering and enchanted app compatibly --- main.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- readme.md | 8 ++++++- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index d259615..dcd8ea1 100644 --- a/main.go +++ b/main.go @@ -1,18 +1,46 @@ package main import ( + "bufio" "encoding/json" "errors" "io" "log/slog" "net/http" "os" + "strings" "time" "github.com/gin-gonic/gin" openai "github.com/sashabaranov/go-openai" ) +var modelFilter map[string]struct{} + +func loadModelFilter(path string) (map[string]struct{}, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + filter := make(map[string]struct{}) + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line != "" { + filter[line] = struct{}{} + } + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return filter, nil +} + func main() { r := gin.Default() // Load the API key from environment variables or command-line arguments. @@ -28,6 +56,23 @@ func main() { provider := NewOpenrouterProvider(apiKey) + filter, err := loadModelFilter("models-filter") + if err != nil { + if os.IsNotExist(err) { + slog.Info("models-filter file not found. Skipping model filtering.") + modelFilter = make(map[string]struct{}) + } else { + slog.Error("Error loading models filter", "Error", err) + return + } + } else { + modelFilter = filter + slog.Info("Loaded models from filter:") + for model := range modelFilter { + slog.Info(" - " + model) + } + } + r.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Ollama is running") }) @@ -42,8 +87,27 @@ func main() { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - // Respond with the list of models - c.JSON(http.StatusOK, gin.H{"models": models}) + filter := modelFilter + // Construct a new array of model objects with extra fields + newModels := make([]map[string]interface{}, 0, len(models)) + for _, m := range models { + // Если фильтр пустой, значит пропускаем проверку и берём все модели + if len(filter) > 0 { + if _, ok := filter[m.Model]; !ok { + continue + } + } + newModels = append(newModels, map[string]interface{}{ + "name": m.Name, + "model": m.Model, + "modified_at": m.ModifiedAt, + "size": 270898672, + "digest": "9077fe9d2ae1a4a41a868836b56b8163731a8fe16621397028c2c76f838c6907", + "details": m.Details, + }) + } + + c.JSON(http.StatusOK, gin.H{"models": newModels}) }) r.POST("/api/show", func(c *gin.Context) { @@ -164,5 +228,5 @@ func main() { } }) - r.Run(":8080") + r.Run(":11434") } diff --git a/readme.md b/readme.md index 9f2442b..0110af1 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,16 @@ -# Ollama Proxy for OpenRouter +# Enchanted Proxy for OpenRouter +This repository is specifically made for use with the [Enchanted project](https://github.com/gluonfield/enchanted/tree/main). +The original author of this proxy is [marknefedov](https://github.com/marknefedov/ollama-openrouter-proxy). ## Description This repository provides a proxy server that emulates [Ollama's REST API](https://github.com/ollama/ollama) but forwards requests to [OpenRouter](https://openrouter.ai/). It uses the [sashabaranov/go-openai](https://github.com/sashabaranov/go-openai) library under the hood, with minimal code changes to keep the Ollama API calls the same. This allows you to use Ollama-compatible tooling and clients, but run your requests on OpenRouter-managed models. Currently, it is enough for usage with [Jetbrains AI assistant](https://blog.jetbrains.com/ai/2024/11/jetbrains-ai-assistant-2024-3/#more-control-over-your-chat-experience-choose-between-gemini,-openai,-and-local-models). ## Features +- **Model Filtering**: You can provide a `models-filter` file in the same directory as the proxy. Each line in this file should contain a single model name. The proxy will only show models that match these entries. If the file doesn’t exist or is empty, no filtering is applied. + + **Note**: OpenRouter model names may sometimes include a vendor prefix, for example `deepseek/deepseek-chat-v3-0324:free`. To make sure filtering works correctly, remove the vendor part when adding the name to your `models-filter` file, e.g. `deepseek-chat-v3-0324:free`. + - **Ollama-like API**: The server listens on `8080` and exposes endpoints similar to Ollama (e.g., `/api/chat`, `/api/tags`). - **Model Listing**: Fetch a list of available models from OpenRouter. - **Model Details**: Retrieve metadata about a specific model.