Support custom OPENAI_BASE_URL and document HF secret
Browse files- .env.example +2 -1
- README.md +5 -2
- config.py +9 -8
- llm.py +35 -24
- llm_utils.py +29 -8
.env.example
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
OPENAI_API_KEY=your_openai_api_key
|
|
|
|
| 2 |
ANTHROPIC_API_KEY=your_anthropic_api_key
|
| 3 |
GOOGLE_API_KEY=your_google_api_key
|
| 4 |
OLLAMA_BASE_URL=your_ollama_url
|
| 5 |
OPENROUTER_BASE_URL=your_openrouter_url
|
| 6 |
OPENROUTER_API_KEY=your_openrouter_api_key
|
| 7 |
-
LLAMA_CPP_BASE_URL=your_llama_cpp_url
|
|
|
|
| 1 |
OPENAI_API_KEY=your_openai_api_key
|
| 2 |
+
OPENAI_BASE_URL=your_openai_compatible_base_url
|
| 3 |
ANTHROPIC_API_KEY=your_anthropic_api_key
|
| 4 |
GOOGLE_API_KEY=your_google_api_key
|
| 5 |
OLLAMA_BASE_URL=your_ollama_url
|
| 6 |
OPENROUTER_BASE_URL=your_openrouter_url
|
| 7 |
OPENROUTER_API_KEY=your_openrouter_api_key
|
| 8 |
+
LLAMA_CPP_BASE_URL=your_llama_cpp_url
|
README.md
CHANGED
|
@@ -39,8 +39,10 @@ license: mit
|
|
| 39 |
> [!NOTE]
|
| 40 |
> The tool needs Tor to do the searches. You can install Tor using `apt install tor` on Linux/Windows(WSL) or `brew install tor` on Mac. Once installed, confirm if Tor is running in the background.
|
| 41 |
|
| 42 |
-
> [!TIP]
|
| 43 |
-
> You can provide OpenAI or Anthropic or Google API key by either creating .env file (refer to sample env file in the repo) or by setting env variables in PATH.
|
|
|
|
|
|
|
| 44 |
>
|
| 45 |
> For Ollama, provide `http://host.docker.internal:11434` as `OLLAMA_BASE_URL` in your env if running using docker method or `http://127.0.0.1:11434` for other methods. You might need to serve Ollama on 0.0.0.0 depending on your OS. You can do by running `OLLAMA_HOST=0.0.0.0 ollama serve &` in your terminal.
|
| 46 |
|
|
@@ -66,6 +68,7 @@ docker run --rm \
|
|
| 66 |
2. Push this repository to the Space git remote.
|
| 67 |
3. In Space settings, add required secrets:
|
| 68 |
- `OPENAI_API_KEY` (for OpenAI models)
|
|
|
|
| 69 |
- `ANTHROPIC_API_KEY` (for Claude models)
|
| 70 |
- `GOOGLE_API_KEY` (for Gemini models)
|
| 71 |
- `OPENROUTER_API_KEY` (for OpenRouter models)
|
|
|
|
| 39 |
> [!NOTE]
|
| 40 |
> The tool needs Tor to do the searches. You can install Tor using `apt install tor` on Linux/Windows(WSL) or `brew install tor` on Mac. Once installed, confirm if Tor is running in the background.
|
| 41 |
|
| 42 |
+
> [!TIP]
|
| 43 |
+
> You can provide OpenAI or Anthropic or Google API key by either creating .env file (refer to sample env file in the repo) or by setting env variables in PATH.
|
| 44 |
+
>
|
| 45 |
+
> If you use an OpenAI-compatible proxy/gateway, set `OPENAI_BASE_URL` (for example: `https://your-openai-gateway/v1`).
|
| 46 |
>
|
| 47 |
> For Ollama, provide `http://host.docker.internal:11434` as `OLLAMA_BASE_URL` in your env if running using docker method or `http://127.0.0.1:11434` for other methods. You might need to serve Ollama on 0.0.0.0 depending on your OS. You can do by running `OLLAMA_HOST=0.0.0.0 ollama serve &` in your terminal.
|
| 48 |
|
|
|
|
| 68 |
2. Push this repository to the Space git remote.
|
| 69 |
3. In Space settings, add required secrets:
|
| 70 |
- `OPENAI_API_KEY` (for OpenAI models)
|
| 71 |
+
- `OPENAI_BASE_URL` (optional, for custom OpenAI-compatible endpoint URL)
|
| 72 |
- `ANTHROPIC_API_KEY` (for Claude models)
|
| 73 |
- `GOOGLE_API_KEY` (for Gemini models)
|
| 74 |
- `OPENROUTER_API_KEY` (for OpenRouter models)
|
config.py
CHANGED
|
@@ -3,11 +3,12 @@ from dotenv import load_dotenv
|
|
| 3 |
|
| 4 |
load_dotenv()
|
| 5 |
|
| 6 |
-
# Configuration variables loaded from the .env file
|
| 7 |
-
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
| 3 |
|
| 4 |
load_dotenv()
|
| 5 |
|
| 6 |
+
# Configuration variables loaded from the .env file
|
| 7 |
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
| 8 |
+
OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL")
|
| 9 |
+
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
| 10 |
+
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
|
| 11 |
+
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL")
|
| 12 |
+
OPENROUTER_BASE_URL = os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1")
|
| 13 |
+
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
|
| 14 |
+
LLAMA_CPP_BASE_URL= os.getenv("LLAMA_CPP_BASE_URL")
|
llm.py
CHANGED
|
@@ -3,12 +3,13 @@ import openai
|
|
| 3 |
from langchain_core.prompts import ChatPromptTemplate
|
| 4 |
from langchain_core.output_parsers import StrOutputParser
|
| 5 |
from llm_utils import _common_llm_params, resolve_model_config, get_model_choices
|
| 6 |
-
from config import (
|
| 7 |
-
OPENAI_API_KEY,
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
| 12 |
import logging
|
| 13 |
import re
|
| 14 |
|
|
@@ -45,29 +46,39 @@ def get_llm(model_choice):
|
|
| 45 |
return llm_instance
|
| 46 |
|
| 47 |
|
| 48 |
-
def _ensure_credentials(model_choice: str, llm_class, model_params: dict) -> None:
|
| 49 |
-
"""Raise a clear error if the user selects a hosted model without a key."""
|
| 50 |
-
|
| 51 |
-
def
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
class_name = getattr(llm_class, "__name__", str(llm_class))
|
| 60 |
|
| 61 |
if "ChatAnthropic" in class_name:
|
| 62 |
_require(ANTHROPIC_API_KEY, "ANTHROPIC_API_KEY", "Anthropic")
|
| 63 |
-
elif "ChatGoogleGenerativeAI" in class_name:
|
| 64 |
-
_require(GOOGLE_API_KEY, "GOOGLE_API_KEY", "Google Gemini")
|
| 65 |
-
elif "ChatOpenAI" in class_name:
|
| 66 |
-
base_url = (model_params or {}).get("base_url", "").lower()
|
| 67 |
-
if "openrouter" in base_url:
|
| 68 |
-
_require(OPENROUTER_API_KEY, "OPENROUTER_API_KEY", "OpenRouter")
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
|
| 73 |
def refine_query(llm, user_input):
|
|
|
|
| 3 |
from langchain_core.prompts import ChatPromptTemplate
|
| 4 |
from langchain_core.output_parsers import StrOutputParser
|
| 5 |
from llm_utils import _common_llm_params, resolve_model_config, get_model_choices
|
| 6 |
+
from config import (
|
| 7 |
+
OPENAI_API_KEY,
|
| 8 |
+
OPENAI_BASE_URL,
|
| 9 |
+
ANTHROPIC_API_KEY,
|
| 10 |
+
GOOGLE_API_KEY,
|
| 11 |
+
OPENROUTER_API_KEY,
|
| 12 |
+
)
|
| 13 |
import logging
|
| 14 |
import re
|
| 15 |
|
|
|
|
| 46 |
return llm_instance
|
| 47 |
|
| 48 |
|
| 49 |
+
def _ensure_credentials(model_choice: str, llm_class, model_params: dict) -> None:
|
| 50 |
+
"""Raise a clear error if the user selects a hosted model without a key."""
|
| 51 |
+
|
| 52 |
+
def _is_configured(value):
|
| 53 |
+
return bool(value and str(value).strip() and "your_" not in str(value).lower())
|
| 54 |
+
|
| 55 |
+
def _require(key_value, env_var, provider_name):
|
| 56 |
+
if _is_configured(key_value):
|
| 57 |
+
return
|
| 58 |
+
raise ValueError(
|
| 59 |
+
f"{provider_name} model '{model_choice}' selected but `{env_var}` is not set.\n"
|
| 60 |
+
"Add it to your .env file or export it before running the app."
|
| 61 |
+
)
|
| 62 |
|
| 63 |
class_name = getattr(llm_class, "__name__", str(llm_class))
|
| 64 |
|
| 65 |
if "ChatAnthropic" in class_name:
|
| 66 |
_require(ANTHROPIC_API_KEY, "ANTHROPIC_API_KEY", "Anthropic")
|
| 67 |
+
elif "ChatGoogleGenerativeAI" in class_name:
|
| 68 |
+
_require(GOOGLE_API_KEY, "GOOGLE_API_KEY", "Google Gemini")
|
| 69 |
+
elif "ChatOpenAI" in class_name:
|
| 70 |
+
base_url = str((model_params or {}).get("base_url", "")).lower()
|
| 71 |
+
if "openrouter" in base_url:
|
| 72 |
+
_require(OPENROUTER_API_KEY, "OPENROUTER_API_KEY", "OpenRouter")
|
| 73 |
+
return
|
| 74 |
+
|
| 75 |
+
if _is_configured(OPENAI_API_KEY) or _is_configured(OPENAI_BASE_URL):
|
| 76 |
+
return
|
| 77 |
+
|
| 78 |
+
raise ValueError(
|
| 79 |
+
f"OpenAI model '{model_choice}' selected but neither `OPENAI_API_KEY` nor `OPENAI_BASE_URL` is configured.\n"
|
| 80 |
+
"Set `OPENAI_API_KEY` for OpenAI cloud, or set `OPENAI_BASE_URL` for an OpenAI-compatible endpoint."
|
| 81 |
+
)
|
| 82 |
|
| 83 |
|
| 84 |
def refine_query(llm, user_input):
|
llm_utils.py
CHANGED
|
@@ -6,13 +6,13 @@ from typing import Callable, Optional, List
|
|
| 6 |
from langchain_anthropic import ChatAnthropic
|
| 7 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 8 |
from langchain_core.callbacks.base import BaseCallbackHandler
|
| 9 |
-
import os
|
| 10 |
from config import (
|
| 11 |
OLLAMA_BASE_URL,
|
| 12 |
OPENROUTER_BASE_URL,
|
| 13 |
OPENROUTER_API_KEY,
|
| 14 |
GOOGLE_API_KEY,
|
| 15 |
OPENAI_API_KEY,
|
|
|
|
| 16 |
ANTHROPIC_API_KEY,
|
| 17 |
LLAMA_CPP_BASE_URL,
|
| 18 |
)
|
|
@@ -51,28 +51,49 @@ _common_llm_params = {
|
|
| 51 |
"callbacks": _common_callbacks,
|
| 52 |
}
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
# Map input model choices (lowercased) to their configuration
|
| 55 |
# Each config includes the class and any model-specific constructor parameters
|
| 56 |
_llm_config_map = {
|
| 57 |
'gpt-4.1': {
|
| 58 |
'class': ChatOpenAI,
|
| 59 |
-
'constructor_params':
|
| 60 |
},
|
| 61 |
'gpt-5.2': {
|
| 62 |
'class': ChatOpenAI,
|
| 63 |
-
'constructor_params':
|
| 64 |
},
|
| 65 |
'gpt-5.1': {
|
| 66 |
'class': ChatOpenAI,
|
| 67 |
-
'constructor_params':
|
| 68 |
},
|
| 69 |
'gpt-5-mini': {
|
| 70 |
'class': ChatOpenAI,
|
| 71 |
-
'constructor_params':
|
| 72 |
},
|
| 73 |
'gpt-5-nano': {
|
| 74 |
'class': ChatOpenAI,
|
| 75 |
-
'constructor_params':
|
| 76 |
},
|
| 77 |
'claude-sonnet-4-5': {
|
| 78 |
'class': ChatAnthropic,
|
|
@@ -244,7 +265,7 @@ def get_model_choices() -> List[str]:
|
|
| 244 |
"""
|
| 245 |
gated_base_models: List[str] = []
|
| 246 |
|
| 247 |
-
openai_ok = _is_set(OPENAI_API_KEY)
|
| 248 |
anthropic_ok = _is_set(ANTHROPIC_API_KEY)
|
| 249 |
google_ok = _is_set(GOOGLE_API_KEY)
|
| 250 |
openrouter_ok = _is_set(OPENROUTER_API_KEY) and _is_set(OPENROUTER_BASE_URL)
|
|
@@ -333,4 +354,4 @@ def resolve_model_config(model_choice: str):
|
|
| 333 |
"constructor_params": {"model": ollama_model, "base_url": OLLAMA_BASE_URL},
|
| 334 |
}
|
| 335 |
|
| 336 |
-
return None
|
|
|
|
| 6 |
from langchain_anthropic import ChatAnthropic
|
| 7 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 8 |
from langchain_core.callbacks.base import BaseCallbackHandler
|
|
|
|
| 9 |
from config import (
|
| 10 |
OLLAMA_BASE_URL,
|
| 11 |
OPENROUTER_BASE_URL,
|
| 12 |
OPENROUTER_API_KEY,
|
| 13 |
GOOGLE_API_KEY,
|
| 14 |
OPENAI_API_KEY,
|
| 15 |
+
OPENAI_BASE_URL,
|
| 16 |
ANTHROPIC_API_KEY,
|
| 17 |
LLAMA_CPP_BASE_URL,
|
| 18 |
)
|
|
|
|
| 51 |
"callbacks": _common_callbacks,
|
| 52 |
}
|
| 53 |
|
| 54 |
+
|
| 55 |
+
def _clean_optional_url(value: Optional[str]) -> Optional[str]:
|
| 56 |
+
if not value:
|
| 57 |
+
return None
|
| 58 |
+
cleaned = value.strip().rstrip("/")
|
| 59 |
+
if not cleaned or "your_" in cleaned.lower():
|
| 60 |
+
return None
|
| 61 |
+
return cleaned
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
_DIRECT_OPENAI_BASE_URL = _clean_optional_url(OPENAI_BASE_URL)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def _openai_constructor_params(model_name: str) -> dict:
|
| 68 |
+
params = {"model_name": model_name}
|
| 69 |
+
if _DIRECT_OPENAI_BASE_URL:
|
| 70 |
+
params["base_url"] = _DIRECT_OPENAI_BASE_URL
|
| 71 |
+
# Some OpenAI-compatible gateways accept any placeholder key.
|
| 72 |
+
params["api_key"] = OPENAI_API_KEY or "sk-local"
|
| 73 |
+
return params
|
| 74 |
+
|
| 75 |
# Map input model choices (lowercased) to their configuration
|
| 76 |
# Each config includes the class and any model-specific constructor parameters
|
| 77 |
_llm_config_map = {
|
| 78 |
'gpt-4.1': {
|
| 79 |
'class': ChatOpenAI,
|
| 80 |
+
'constructor_params': _openai_constructor_params('gpt-4.1')
|
| 81 |
},
|
| 82 |
'gpt-5.2': {
|
| 83 |
'class': ChatOpenAI,
|
| 84 |
+
'constructor_params': _openai_constructor_params('gpt-5.2')
|
| 85 |
},
|
| 86 |
'gpt-5.1': {
|
| 87 |
'class': ChatOpenAI,
|
| 88 |
+
'constructor_params': _openai_constructor_params('gpt-5.1')
|
| 89 |
},
|
| 90 |
'gpt-5-mini': {
|
| 91 |
'class': ChatOpenAI,
|
| 92 |
+
'constructor_params': _openai_constructor_params('gpt-5-mini')
|
| 93 |
},
|
| 94 |
'gpt-5-nano': {
|
| 95 |
'class': ChatOpenAI,
|
| 96 |
+
'constructor_params': _openai_constructor_params('gpt-5-nano')
|
| 97 |
},
|
| 98 |
'claude-sonnet-4-5': {
|
| 99 |
'class': ChatAnthropic,
|
|
|
|
| 265 |
"""
|
| 266 |
gated_base_models: List[str] = []
|
| 267 |
|
| 268 |
+
openai_ok = _is_set(OPENAI_API_KEY) or _is_set(OPENAI_BASE_URL)
|
| 269 |
anthropic_ok = _is_set(ANTHROPIC_API_KEY)
|
| 270 |
google_ok = _is_set(GOOGLE_API_KEY)
|
| 271 |
openrouter_ok = _is_set(OPENROUTER_API_KEY) and _is_set(OPENROUTER_BASE_URL)
|
|
|
|
| 354 |
"constructor_params": {"model": ollama_model, "base_url": OLLAMA_BASE_URL},
|
| 355 |
}
|
| 356 |
|
| 357 |
+
return None
|