diff --git a/.env.example b/.env.example index 97f4bf7..84145ae 100644 --- a/.env.example +++ b/.env.example @@ -75,11 +75,12 @@ JWT_SECRET_KEY=your_jwt_secret_key_here # ============================================================================= # Embedding provider for the semantic_search tool. -# Pick exactly one of: OpenRouter (hosted) or Local (your own server). +# Pick exactly one of: OpenRouter (hosted), OrcaRouter (hosted), or Local. # --- Option A: OpenRouter (hosted, default) ----------------------------------- # Get your API key from: https://openrouter.ai/keys -# If neither this nor EMBEDDING_PROVIDER=local is set, semantic search is off. +# If neither this, nor ORCAROUTER_API_KEY, nor EMBEDDING_PROVIDER=local is set, +# semantic search is off. OPENROUTER_API_KEY=sk-or-v1-your_openrouter_api_key_here # Optional: override the OpenRouter embedding model and dimension. @@ -89,6 +90,17 @@ OPENROUTER_API_KEY=sk-or-v1-your_openrouter_api_key_here # OPENROUTER_EMBEDDING_MODEL=google/gemini-embedding-001 # OPENROUTER_EMBEDDING_DIMENSION=3072 +# --- Option A2: OrcaRouter (hosted) ------------------------------------------ +# OrcaRouter is a production AI gateway with one OpenAI-compatible endpoint +# (https://api.orcarouter.ai/v1). Get your API key from: https://www.orcarouter.ai +# Set ORCAROUTER_API_KEY instead of OPENROUTER_API_KEY to use it. +# ORCAROUTER_API_KEY=sk-orca-your_orcarouter_api_key_here + +# Optional: override the OrcaRouter embedding model and dimension. +# Defaults: google/gemini-embedding-001 at 3072 dims (multilingual). +# ORCAROUTER_EMBEDDING_MODEL=google/gemini-embedding-001 +# ORCAROUTER_EMBEDDING_DIMENSION=3072 + # --- Option B: Local OpenAI-compatible server (no API key required) ---------- # Recommended for Turkish: intfloat/multilingual-e5-large served by HuggingFace # Text Embeddings Inference (TEI). One-line setup: diff --git a/README.md b/README.md index 65f8ae1..167bd4d 100644 --- a/README.md +++ b/README.md @@ -277,7 +277,7 @@ Yargı MCP'yi Gemini CLI ile kullanmak için: Yargı MCP, **semantik arama** özelliği ile kararları anlamsal olarak sıralayabilir. Opsiyoneldir; iki yoldan biri yapılandırıldığında otomatik etkinleşir: - **Yerel** (önerilen, ücretsiz): kendi makinenizdeki OpenAI-uyumlu embedding sunucusu (HuggingFace TEI, llama.cpp, Ollama, vLLM, LM Studio…) -- **Hosted**: OpenRouter API anahtarı +- **Hosted**: OpenRouter ya da [OrcaRouter](https://www.orcarouter.ai) API anahtarı ### Semantik Arama Nasıl Çalışır? 1. `initial_keyword` ile Bedesten API'den 100 karar çekilir @@ -353,11 +353,25 @@ OPENROUTER_API_KEY=sk-or-v1-xxx... API anahtarınızı [openrouter.ai/keys](https://openrouter.ai/keys) adresinden alın. Varsayılan model `google/gemini-embedding-001` artık ücretli — ücretsiz bir model seçerseniz `OPENROUTER_EMBEDDING_MODEL`, `OPENROUTER_EMBEDDING_DIMENSION` ve uygun `EMBEDDING_PROMPT_STYLE` değerlerini birlikte ayarlayın. +### Alternatif 3: OrcaRouter (hosted) + +[OrcaRouter](https://www.orcarouter.ai), 200+ modeli tek OpenAI-uyumlu uçta toplayan bir üretim AI ağ geçididir (ağ geçidi seviyesinde, sıfır-güven AI ajan güvenliği de içerir). Mevcut SDK kodu `base_url` değiştirilerek aynen çalışır. + +```bash +ORCAROUTER_API_KEY=sk-orca-xxx... +# İsteğe bağlı — varsayılan google/gemini-embedding-001 (3072 dim, çok dilli) +# ORCAROUTER_EMBEDDING_MODEL=... +# ORCAROUTER_EMBEDDING_DIMENSION=... +# EMBEDDING_PROMPT_STYLE=gemini # varsayılan +``` + +API anahtarınızı [www.orcarouter.ai](https://www.orcarouter.ai) adresinden alın. `OPENROUTER_API_KEY` yerine `ORCAROUTER_API_KEY` ayarlamanız yeterli — semantik arama aynı OpenAI-uyumlu akışı OrcaRouter ucu üzerinden kullanır. + ### Yapılandırma Referansı | Env Var | Açıklama | Örnek | |---|---|---| -| `EMBEDDING_PROVIDER` | `local` ise yerel sunucu, boş ise OpenRouter | `local` | +| `EMBEDDING_PROVIDER` | `local` ise yerel sunucu, boş ise hosted (OpenRouter/OrcaRouter) | `local` | | `EMBEDDING_PROMPT_STYLE` | `gemini` / `e5` / `raw` — modelin beklediği önek | `e5` | | `LOCAL_EMBEDDING_BASE_URL` | Yerel sunucunun OpenAI-uyumlu URL'i | `http://localhost:8080/v1` | | `LOCAL_EMBEDDING_MODEL` | Model adı | `intfloat/multilingual-e5-large` | @@ -365,6 +379,9 @@ API anahtarınızı [openrouter.ai/keys](https://openrouter.ai/keys) adresinden | `OPENROUTER_API_KEY` | OpenRouter anahtarı (sadece hosted için) | `sk-or-v1-…` | | `OPENROUTER_EMBEDDING_MODEL` | OpenRouter model id'si | `google/gemini-embedding-001` | | `OPENROUTER_EMBEDDING_DIMENSION` | OpenRouter modelinin çıktı boyutu | `3072` | +| `ORCAROUTER_API_KEY` | OrcaRouter anahtarı (sadece hosted için) | `sk-orca-…` | +| `ORCAROUTER_EMBEDDING_MODEL` | OrcaRouter model id'si | `google/gemini-embedding-001` | +| `ORCAROUTER_EMBEDDING_DIMENSION` | OrcaRouter modelinin çıktı boyutu | `3072` | > 💡 **Not:** Hiçbir embedding sağlayıcı yapılandırılmazsa semantik arama aracı görünmez, diğer 28 araç normal şekilde çalışır. diff --git a/mcp_server_main.py b/mcp_server_main.py index 3a53607..3e017e7 100644 --- a/mcp_server_main.py +++ b/mcp_server_main.py @@ -266,14 +266,24 @@ from bedesten_mcp_module.models import ( from bedesten_mcp_module.enums import BirimAdiEnum # Semantic Search Module Imports (enabled if any embedding provider is configured) -from semantic_search.embedder import is_semantic_search_available, is_local_embedding_configured +from semantic_search.embedder import ( + is_semantic_search_available, + is_local_embedding_configured, + is_openrouter_available, + is_orcarouter_available, +) SEMANTIC_SEARCH_AVAILABLE = is_semantic_search_available() if SEMANTIC_SEARCH_AVAILABLE: from semantic_search.embedder import get_embedder from semantic_search.vector_store import VectorStore from semantic_search.processor import DocumentProcessor - provider = "local" if is_local_embedding_configured() else "openrouter" + if is_local_embedding_configured(): + provider = "local" + elif is_orcarouter_available(): + provider = "orcarouter" + elif is_openrouter_available(): + provider = "openrouter" logger.info(f"Semantic search enabled (provider={provider})") else: logger.info("Semantic search disabled (no embedding provider configured)") diff --git a/semantic_search/__init__.py b/semantic_search/__init__.py index 5d3eae3..c6ea28d 100644 --- a/semantic_search/__init__.py +++ b/semantic_search/__init__.py @@ -2,9 +2,11 @@ from .embedder import ( OpenRouterEmbedder, + OrcaRouterEmbedder, LocalEmbedder, get_embedder, is_openrouter_available, + is_orcarouter_available, is_local_embedding_configured, is_semantic_search_available, ) @@ -13,9 +15,11 @@ from .processor import DocumentProcessor __all__ = [ 'OpenRouterEmbedder', + 'OrcaRouterEmbedder', 'LocalEmbedder', 'get_embedder', 'is_openrouter_available', + 'is_orcarouter_available', 'is_local_embedding_configured', 'is_semantic_search_available', 'VectorStore', diff --git a/semantic_search/embedder.py b/semantic_search/embedder.py index 67e0c24..e54af29 100644 --- a/semantic_search/embedder.py +++ b/semantic_search/embedder.py @@ -64,6 +64,11 @@ def is_openrouter_available() -> bool: return bool(os.getenv("OPENROUTER_API_KEY")) +def is_orcarouter_available() -> bool: + """Check if OrcaRouter API key is available.""" + return bool(os.getenv("ORCAROUTER_API_KEY")) + + def is_local_embedding_configured() -> bool: """Check if the user opted into a local embedding endpoint.""" return os.getenv("EMBEDDING_PROVIDER", "").strip().lower() == "local" @@ -71,7 +76,11 @@ def is_local_embedding_configured() -> bool: def is_semantic_search_available() -> bool: """Returns True if any embedding provider is configured.""" - return is_local_embedding_configured() or is_openrouter_available() + return ( + is_local_embedding_configured() + or is_openrouter_available() + or is_orcarouter_available() + ) def _coerce_dimension(value, env_name: str, default: int) -> int: @@ -261,6 +270,59 @@ class OpenRouterEmbedder(_BaseOpenAICompatibleEmbedder): ) +class OrcaRouterEmbedder(_BaseOpenAICompatibleEmbedder): + """ + Embedder using OrcaRouter's OpenAI-compatible embedding API. + + OrcaRouter is a production AI gateway that proxies 200+ models on a single + OpenAI-compatible endpoint. The model and dimension are configurable so + users can pick any embedding model the gateway routes. Configuration + precedence: explicit constructor args > environment variables > defaults. + + Environment variables: + ORCAROUTER_API_KEY (required): OrcaRouter credential (sk-orca-...) + ORCAROUTER_EMBEDDING_MODEL (optional): override the embedding model id + ORCAROUTER_EMBEDDING_DIMENSION (optional): override the vector size + + Defaults: ``google/gemini-embedding-001`` at 3072 dimensions (multilingual, + matches the OpenRouter default — good for Turkish legal text). + """ + + def __init__( + self, + model: Optional[str] = None, + dimension: Optional[int] = None, + prompt_style: Optional[str] = None, + ): + api_key = os.getenv("ORCAROUTER_API_KEY") + if not api_key: + raise ValueError("ORCAROUTER_API_KEY environment variable is not set") + + try: + from openai import OpenAI + except ImportError: + raise ImportError("openai package is required. Install with: pip install openai") + + self.client = OpenAI( + base_url="https://api.orcarouter.ai/v1", + api_key=api_key, + ) + self.model = model or os.getenv("ORCAROUTER_EMBEDDING_MODEL") or DEFAULT_MODEL + self.dimension = _coerce_dimension( + dimension if dimension is not None else os.getenv("ORCAROUTER_EMBEDDING_DIMENSION"), + "ORCAROUTER_EMBEDDING_DIMENSION", + DEFAULT_DIMENSION, + ) + # Same gemini-style default as the OpenRouter embedder — matches the + # multilingual google/gemini-embedding-001 default model. + self.prompt_style = _resolve_prompt_style(prompt_style, "gemini") + + logger.info( + f"OrcaRouter Embedder initialized with model: {self.model} " + f"(dimension={self.dimension}, prompt_style={self.prompt_style})" + ) + + class LocalEmbedder(_BaseOpenAICompatibleEmbedder): """ Embedder for a local OpenAI-compatible embedding server — Ollama, @@ -332,17 +394,22 @@ def get_embedder(): Factory that picks the embedder based on EMBEDDING_PROVIDER. - ``EMBEDDING_PROVIDER=local`` -> ``LocalEmbedder`` + - ``ORCAROUTER_API_KEY`` set -> ``OrcaRouterEmbedder`` - otherwise -> ``OpenRouterEmbedder`` (requires OPENROUTER_API_KEY) Raises: - ValueError: If no provider is configured (neither local nor OpenRouter). + ValueError: If no provider is configured (neither local, OpenRouter, + nor OrcaRouter). """ if is_local_embedding_configured(): return LocalEmbedder() + if is_orcarouter_available(): + return OrcaRouterEmbedder() if is_openrouter_available(): return OpenRouterEmbedder() raise ValueError( - "No embedding provider configured. Set OPENROUTER_API_KEY for hosted " - "embeddings, or EMBEDDING_PROVIDER=local (with LOCAL_EMBEDDING_* " - "env vars) for a local OpenAI-compatible server like Ollama." + "No embedding provider configured. Set OPENROUTER_API_KEY or " + "ORCAROUTER_API_KEY for hosted embeddings, or EMBEDDING_PROVIDER=local " + "(with LOCAL_EMBEDDING_* env vars) for a local OpenAI-compatible " + "server like Ollama." )