Skip to content

Commit 4fe2d01

Browse files
authored
Merge pull request unclecode#1440 from unclecode/feature/docker-llm-parameters
feat(docker): Add temperature and base_url parameters for LLM configuration
2 parents 40ab287 + 159207b commit 4fe2d01

File tree

8 files changed

+603
-23
lines changed

8 files changed

+603
-23
lines changed

deploy/docker/.llm.env.example

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,23 @@ GEMINI_API_TOKEN=your_gemini_key_here
1010
# Optional: Override the default LLM provider
1111
# Examples: "openai/gpt-4", "anthropic/claude-3-opus", "deepseek/chat", etc.
1212
# If not set, uses the provider specified in config.yml (default: openai/gpt-4o-mini)
13-
# LLM_PROVIDER=anthropic/claude-3-opus
13+
# LLM_PROVIDER=anthropic/claude-3-opus
14+
15+
# Optional: Global LLM temperature setting (0.0-2.0)
16+
# Controls randomness in responses. Lower = more focused, Higher = more creative
17+
# LLM_TEMPERATURE=0.7
18+
19+
# Optional: Global custom API base URL
20+
# Use this to point to custom endpoints or proxy servers
21+
# LLM_BASE_URL=https://api.custom.com/v1
22+
23+
# Optional: Provider-specific temperature overrides
24+
# These take precedence over the global LLM_TEMPERATURE
25+
# OPENAI_TEMPERATURE=0.5
26+
# ANTHROPIC_TEMPERATURE=0.3
27+
# GROQ_TEMPERATURE=0.8
28+
29+
# Optional: Provider-specific base URL overrides
30+
# Use for provider-specific proxy endpoints
31+
# OPENAI_BASE_URL=https://custom-openai.company.com/v1
32+
# GROQ_BASE_URL=https://custom-groq.company.com/v1

deploy/docker/api.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@
4242
should_cleanup_task,
4343
decode_redis_hash,
4444
get_llm_api_key,
45-
validate_llm_provider
45+
validate_llm_provider,
46+
get_llm_temperature,
47+
get_llm_base_url
4648
)
4749

4850
import psutil, time
@@ -96,7 +98,9 @@ async def handle_llm_qa(
9698
response = perform_completion_with_backoff(
9799
provider=config["llm"]["provider"],
98100
prompt_with_variables=prompt,
99-
api_token=get_llm_api_key(config) # Returns None to let litellm handle it
101+
api_token=get_llm_api_key(config), # Returns None to let litellm handle it
102+
temperature=get_llm_temperature(config),
103+
base_url=get_llm_base_url(config)
100104
)
101105

102106
return response.choices[0].message.content
@@ -115,7 +119,9 @@ async def process_llm_extraction(
115119
instruction: str,
116120
schema: Optional[str] = None,
117121
cache: str = "0",
118-
provider: Optional[str] = None
122+
provider: Optional[str] = None,
123+
temperature: Optional[float] = None,
124+
base_url: Optional[str] = None
119125
) -> None:
120126
"""Process LLM extraction in background."""
121127
try:
@@ -131,7 +137,9 @@ async def process_llm_extraction(
131137
llm_strategy = LLMExtractionStrategy(
132138
llm_config=LLMConfig(
133139
provider=provider or config["llm"]["provider"],
134-
api_token=api_key
140+
api_token=api_key,
141+
temperature=temperature or get_llm_temperature(config, provider),
142+
base_url=base_url or get_llm_base_url(config, provider)
135143
),
136144
instruction=instruction,
137145
schema=json.loads(schema) if schema else None,
@@ -178,7 +186,9 @@ async def handle_markdown_request(
178186
query: Optional[str] = None,
179187
cache: str = "0",
180188
config: Optional[dict] = None,
181-
provider: Optional[str] = None
189+
provider: Optional[str] = None,
190+
temperature: Optional[float] = None,
191+
base_url: Optional[str] = None
182192
) -> str:
183193
"""Handle markdown generation requests."""
184194
try:
@@ -204,6 +214,8 @@ async def handle_markdown_request(
204214
llm_config=LLMConfig(
205215
provider=provider or config["llm"]["provider"],
206216
api_token=get_llm_api_key(config, provider), # Returns None to let litellm handle it
217+
temperature=temperature or get_llm_temperature(config, provider),
218+
base_url=base_url or get_llm_base_url(config, provider)
207219
),
208220
instruction=query or "Extract main content"
209221
)
@@ -248,7 +260,9 @@ async def handle_llm_request(
248260
schema: Optional[str] = None,
249261
cache: str = "0",
250262
config: Optional[dict] = None,
251-
provider: Optional[str] = None
263+
provider: Optional[str] = None,
264+
temperature: Optional[float] = None,
265+
api_base_url: Optional[str] = None
252266
) -> JSONResponse:
253267
"""Handle LLM extraction requests."""
254268
base_url = get_base_url(request)
@@ -279,7 +293,9 @@ async def handle_llm_request(
279293
cache,
280294
base_url,
281295
config,
282-
provider
296+
provider,
297+
temperature,
298+
api_base_url
283299
)
284300

285301
except Exception as e:
@@ -324,7 +340,9 @@ async def create_new_task(
324340
cache: str,
325341
base_url: str,
326342
config: dict,
327-
provider: Optional[str] = None
343+
provider: Optional[str] = None,
344+
temperature: Optional[float] = None,
345+
api_base_url: Optional[str] = None
328346
) -> JSONResponse:
329347
"""Create and initialize a new task."""
330348
decoded_url = unquote(input_path)
@@ -349,7 +367,9 @@ async def create_new_task(
349367
query,
350368
schema,
351369
cache,
352-
provider
370+
provider,
371+
temperature,
372+
api_base_url
353373
)
354374

355375
return JSONResponse({

deploy/docker/job.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class LlmJobPayload(BaseModel):
3737
schema: Optional[str] = None
3838
cache: bool = False
3939
provider: Optional[str] = None
40+
temperature: Optional[float] = None
41+
base_url: Optional[str] = None
4042

4143

4244
class CrawlJobPayload(BaseModel):
@@ -63,6 +65,8 @@ async def llm_job_enqueue(
6365
cache=payload.cache,
6466
config=_config,
6567
provider=payload.provider,
68+
temperature=payload.temperature,
69+
api_base_url=payload.base_url,
6670
)
6771

6872

@@ -72,7 +76,7 @@ async def llm_job_status(
7276
task_id: str,
7377
_td: Dict = Depends(lambda: _token_dep())
7478
):
75-
return await handle_task_status(_redis, task_id)
79+
return await handle_task_status(_redis, task_id, base_url=str(request.base_url))
7680

7781

7882
# ---------- CRAWL job -------------------------------------------------------

deploy/docker/schemas.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ class MarkdownRequest(BaseModel):
1616
q: Optional[str] = Field(None, description="Query string used by BM25/LLM filters")
1717
c: Optional[str] = Field("0", description="Cache‑bust / revision counter")
1818
provider: Optional[str] = Field(None, description="LLM provider override (e.g., 'anthropic/claude-3-opus')")
19+
temperature: Optional[float] = Field(None, description="LLM temperature override (0.0-2.0)")
20+
base_url: Optional[str] = Field(None, description="LLM API base URL override")
1921

2022

2123
class RawCode(BaseModel):

deploy/docker/server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,8 @@ async def get_markdown(
241241
raise HTTPException(
242242
400, "Invalid URL format. Must start with http://, https://, or for raw HTML (raw:, raw://)")
243243
markdown = await handle_markdown_request(
244-
body.url, body.f, body.q, body.c, config, body.provider
244+
body.url, body.f, body.q, body.c, config, body.provider,
245+
body.temperature, body.base_url
245246
)
246247
return JSONResponse({
247248
"url": body.url,

deploy/docker/utils.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,69 @@ def validate_llm_provider(config: Dict, provider: Optional[str] = None) -> tuple
108108
return True, ""
109109

110110

111+
def get_llm_temperature(config: Dict, provider: Optional[str] = None) -> Optional[float]:
112+
"""Get temperature setting based on the LLM provider.
113+
114+
Priority order:
115+
1. Provider-specific environment variable (e.g., OPENAI_TEMPERATURE)
116+
2. Global LLM_TEMPERATURE environment variable
117+
3. None (to use litellm/provider defaults)
118+
119+
Args:
120+
config: The application configuration dictionary
121+
provider: Optional provider override (e.g., "openai/gpt-4")
122+
123+
Returns:
124+
The temperature setting if configured, otherwise None
125+
"""
126+
# Check provider-specific temperature first
127+
if provider:
128+
provider_name = provider.split('/')[0].upper()
129+
provider_temp = os.environ.get(f"{provider_name}_TEMPERATURE")
130+
if provider_temp:
131+
try:
132+
return float(provider_temp)
133+
except ValueError:
134+
logging.warning(f"Invalid temperature value for {provider_name}: {provider_temp}")
135+
136+
# Check global LLM_TEMPERATURE
137+
global_temp = os.environ.get("LLM_TEMPERATURE")
138+
if global_temp:
139+
try:
140+
return float(global_temp)
141+
except ValueError:
142+
logging.warning(f"Invalid global temperature value: {global_temp}")
143+
144+
# Return None to use litellm/provider defaults
145+
return None
146+
147+
148+
def get_llm_base_url(config: Dict, provider: Optional[str] = None) -> Optional[str]:
149+
"""Get base URL setting based on the LLM provider.
150+
151+
Priority order:
152+
1. Provider-specific environment variable (e.g., OPENAI_BASE_URL)
153+
2. Global LLM_BASE_URL environment variable
154+
3. None (to use default endpoints)
155+
156+
Args:
157+
config: The application configuration dictionary
158+
provider: Optional provider override (e.g., "openai/gpt-4")
159+
160+
Returns:
161+
The base URL if configured, otherwise None
162+
"""
163+
# Check provider-specific base URL first
164+
if provider:
165+
provider_name = provider.split('/')[0].upper()
166+
provider_url = os.environ.get(f"{provider_name}_BASE_URL")
167+
if provider_url:
168+
return provider_url
169+
170+
# Check global LLM_BASE_URL
171+
return os.environ.get("LLM_BASE_URL")
172+
173+
111174
def verify_email_domain(email: str) -> bool:
112175
try:
113176
domain = email.split('@')[1]

0 commit comments

Comments
 (0)