pjpjq commited on
Commit
e65fffa
·
1 Parent(s): de0ad43

Support custom OPENAI_BASE_URL and document HF secret

Browse files
Files changed (5) hide show
  1. .env.example +2 -1
  2. README.md +5 -2
  3. config.py +9 -8
  4. llm.py +35 -24
  5. 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
- GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
9
- ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
10
- OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL")
11
- OPENROUTER_BASE_URL = os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1")
12
- OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
13
- LLAMA_CPP_BASE_URL= os.getenv("LLAMA_CPP_BASE_URL")
 
 
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
- ANTHROPIC_API_KEY,
9
- GOOGLE_API_KEY,
10
- OPENROUTER_API_KEY,
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 _require(key_value, env_var, provider_name):
52
- if key_value:
53
- return
54
- raise ValueError(
55
- f"{provider_name} model '{model_choice}' selected but `{env_var}` is not set.\n"
56
- "Add it to your .env file or export it before running the app."
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
- else:
70
- _require(OPENAI_API_KEY, "OPENAI_API_KEY", "OpenAI")
 
 
 
 
 
 
 
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': {'model_name': 'gpt-4.1'}
60
  },
61
  'gpt-5.2': {
62
  'class': ChatOpenAI,
63
- 'constructor_params': {'model_name': 'gpt-5.2'}
64
  },
65
  'gpt-5.1': {
66
  'class': ChatOpenAI,
67
- 'constructor_params': {'model_name': 'gpt-5.1'}
68
  },
69
  'gpt-5-mini': {
70
  'class': ChatOpenAI,
71
- 'constructor_params': {'model_name': 'gpt-5-mini'}
72
  },
73
  'gpt-5-nano': {
74
  'class': ChatOpenAI,
75
- 'constructor_params': {'model_name': 'gpt-5-nano'}
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