Skip to content

Commit 3ce94e4

Browse files
committed
feat: add support for temperature, max_tokens, and timeout configuration for all AI providers
- Add environment variable support for PDODB_AI_{PROVIDER}_TEMPERATURE - Add environment variable support for PDODB_AI_{PROVIDER}_MAX_TOKENS - Add environment variable support for PDODB_AI_{PROVIDER}_TIMEOUT - Add config array support for temperature, max_tokens, and timeout per provider - Add initializeCommonDefaults() method in BaseAiProvider to load timeout from config - Update documentation with examples for all providers showing parameter configuration - All providers (OpenAI, Anthropic, Google, Microsoft, DeepSeek, Yandex, Ollama) now support these parameters
1 parent 6e95790 commit 3ce94e4

File tree

9 files changed

+111
-28
lines changed

9 files changed

+111
-28
lines changed

documentation/05-advanced-features/23-ai-analysis.md

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,40 @@ export PDODB_AI_OLLAMA_MODEL=llama3.2 # Optional: model name
7676

7777
# OpenAI
7878
export PDODB_AI_OPENAI_MODEL=gpt-4o-mini # Optional: gpt-4, gpt-3.5-turbo
79+
export PDODB_AI_OPENAI_TEMPERATURE=0.7 # Optional: 0.0-2.0, default: 0.7
80+
export PDODB_AI_OPENAI_MAX_TOKENS=2000 # Optional: default: 2000
81+
export PDODB_AI_OPENAI_TIMEOUT=30 # Optional: seconds, default: 30
7982

8083
# Anthropic
8184
export PDODB_AI_ANTHROPIC_MODEL=claude-3-5-sonnet-20241022 # Optional: claude-3-opus
85+
export PDODB_AI_ANTHROPIC_TEMPERATURE=0.7 # Optional: 0.0-2.0, default: 0.7
86+
export PDODB_AI_ANTHROPIC_MAX_TOKENS=2000 # Optional: default: 2000
87+
export PDODB_AI_ANTHROPIC_TIMEOUT=30 # Optional: seconds, default: 30
88+
89+
# Google
90+
export PDODB_AI_GOOGLE_TEMPERATURE=0.7 # Optional: 0.0-2.0, default: 0.7
91+
export PDODB_AI_GOOGLE_MAX_TOKENS=8192 # Optional: default: 8192
92+
export PDODB_AI_GOOGLE_TIMEOUT=30 # Optional: seconds, default: 30
93+
94+
# Microsoft
95+
export PDODB_AI_MICROSOFT_TEMPERATURE=0.7 # Optional: 0.0-2.0, default: 0.7
96+
export PDODB_AI_MICROSOFT_MAX_TOKENS=2000 # Optional: default: 2000
97+
export PDODB_AI_MICROSOFT_TIMEOUT=30 # Optional: seconds, default: 30
98+
99+
# DeepSeek
100+
export PDODB_AI_DEEPSEEK_TEMPERATURE=0.7 # Optional: 0.0-2.0, default: 0.7
101+
export PDODB_AI_DEEPSEEK_MAX_TOKENS=2000 # Optional: default: 2000
102+
export PDODB_AI_DEEPSEEK_TIMEOUT=30 # Optional: seconds, default: 30
103+
104+
# Yandex
105+
export PDODB_AI_YANDEX_TEMPERATURE=0.7 # Optional: 0.0-2.0, default: 0.7
106+
export PDODB_AI_YANDEX_MAX_TOKENS=4000 # Optional: default: 4000
107+
export PDODB_AI_YANDEX_TIMEOUT=30 # Optional: seconds, default: 30
108+
109+
# Ollama
110+
export PDODB_AI_OLLAMA_TEMPERATURE=0.7 # Optional: 0.0-2.0, default: 0.7
111+
export PDODB_AI_OLLAMA_MAX_TOKENS=2000 # Optional: default: 2000
112+
export PDODB_AI_OLLAMA_TIMEOUT=30 # Optional: seconds, default: 30
82113
```
83114

84115
### Configuration Array
@@ -106,33 +137,45 @@ $db = new PdoDb('mysql', [
106137
'model' => 'gpt-4o-mini',
107138
'temperature' => 0.7,
108139
'max_tokens' => 2000,
140+
'timeout' => 30,
109141
],
110142
'anthropic' => [
111143
'model' => 'claude-3-5-sonnet-20241022',
112144
'temperature' => 0.7,
145+
'max_tokens' => 2000,
146+
'timeout' => 30,
113147
],
114148
'google' => [
115149
'model' => 'gemini-2.5-flash', # or gemini-2.5-pro, gemini-2.0-flash-001, gemini-flash-latest, gemini-pro-latest
116150
'temperature' => 0.7,
117-
'max_tokens' => 2000,
151+
'max_tokens' => 8192,
152+
'timeout' => 30,
118153
],
119154
'microsoft' => [
120155
'endpoint' => 'https://your-resource.openai.azure.com',
121156
'deployment' => 'gpt-4',
157+
'temperature' => 0.7,
158+
'max_tokens' => 2000,
159+
'timeout' => 30,
122160
],
123161
'deepseek' => [
124162
'model' => 'deepseek-chat', # or deepseek-reasoner (thinking mode)
125163
'temperature' => 0.7,
126164
'max_tokens' => 2000,
165+
'timeout' => 30,
127166
],
128167
'yandex' => [
129168
'folder_id' => 'b1ge9k4rdlck8g72slht', # Required: Yandex Cloud folder ID
130169
'model' => 'gpt-oss-120b/latest', # Optional: model name
131170
'temperature' => 0.7,
132-
'max_tokens' => 2000,
171+
'max_tokens' => 4000,
172+
'timeout' => 30,
133173
],
134174
'ollama' => [
135175
'model' => 'llama3.2', # or any local model name
176+
'temperature' => 0.7,
177+
'max_tokens' => 2000,
178+
'timeout' => 30,
136179
],
137180
],
138181
],

src/ai/AiConfig.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,33 @@ protected function loadProviderModelsFromEnvironment(): void
150150
{
151151
$providers = ['openai', 'anthropic', 'google', 'microsoft', 'ollama', 'deepseek', 'yandex'];
152152
foreach ($providers as $provider) {
153+
// Load model
153154
$envVar = 'PDODB_AI_' . strtoupper($provider) . '_MODEL';
154155
$model = getenv($envVar);
155156
if ($model !== false && $model !== '') {
156157
$this->setProviderSetting($provider, 'model', $model);
157158
}
159+
160+
// Load temperature
161+
$envVar = 'PDODB_AI_' . strtoupper($provider) . '_TEMPERATURE';
162+
$temperature = getenv($envVar);
163+
if ($temperature !== false && $temperature !== '') {
164+
$this->setProviderSetting($provider, 'temperature', (float)$temperature);
165+
}
166+
167+
// Load max_tokens
168+
$envVar = 'PDODB_AI_' . strtoupper($provider) . '_MAX_TOKENS';
169+
$maxTokens = getenv($envVar);
170+
if ($maxTokens !== false && $maxTokens !== '') {
171+
$this->setProviderSetting($provider, 'max_tokens', (int)$maxTokens);
172+
}
173+
174+
// Load timeout
175+
$envVar = 'PDODB_AI_' . strtoupper($provider) . '_TIMEOUT';
176+
$timeout = getenv($envVar);
177+
if ($timeout !== false && $timeout !== '') {
178+
$this->setProviderSetting($provider, 'timeout', (int)$timeout);
179+
}
158180
}
159181
}
160182

src/ai/BaseAiProvider.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,24 @@ public function __construct(?AiConfig $config = null)
2121
{
2222
$this->config = $config ?? new AiConfig();
2323
$this->initializeDefaults();
24+
$this->initializeCommonDefaults();
2425
}
2526

2627
/**
2728
* Initialize provider-specific defaults.
2829
*/
2930
abstract protected function initializeDefaults(): void;
3031

32+
/**
33+
* Initialize common defaults (timeout) that apply to all providers.
34+
*/
35+
protected function initializeCommonDefaults(): void
36+
{
37+
$providerName = $this->getProviderName();
38+
$timeout = $this->config->getProviderSetting($providerName, 'timeout', 30);
39+
$this->timeout = (int)$timeout;
40+
}
41+
3142
/**
3243
* Make HTTP request to AI API.
3344
*

src/ai/providers/DeepSeekProvider.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,3 @@ protected function ensureAvailable(): void
213213
}
214214
}
215215
}
216-

src/ai/providers/GoogleProvider.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ protected function callApi(string $prompt, string $systemInstruction): string
116116
if (isset($response['error'])) {
117117
$errorCode = $response['error']['code'] ?? 'unknown';
118118
$errorMessage = $response['error']['message'] ?? 'Unknown error';
119+
119120
throw new QueryException(
120121
"Google API error ({$errorCode}): {$errorMessage}",
121122
0
@@ -126,6 +127,7 @@ protected function callApi(string $prompt, string $systemInstruction): string
126127
if (!isset($response[self::RESPONSE_KEY_CANDIDATES])) {
127128
$responseJson = json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
128129
$responseStr = is_string($responseJson) ? substr($responseJson, 0, 500) : 'Unable to encode response';
130+
129131
throw new QueryException(
130132
'Invalid response format from Google API: missing "candidates" key. Response: ' . $responseStr,
131133
0
@@ -135,6 +137,7 @@ protected function callApi(string $prompt, string $systemInstruction): string
135137
if (empty($response[self::RESPONSE_KEY_CANDIDATES])) {
136138
$responseJson = json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
137139
$responseStr = is_string($responseJson) ? substr($responseJson, 0, 500) : 'Unable to encode response';
140+
138141
throw new QueryException(
139142
'Invalid response format from Google API: empty "candidates" array. Response: ' . $responseStr,
140143
0
@@ -153,7 +156,7 @@ protected function callApi(string $prompt, string $systemInstruction): string
153156
'OTHER' => 'Response was stopped for an unknown reason.',
154157
];
155158
$message = $reasonMessages[$finishReason] ?? "Response was stopped (reason: {$finishReason}).";
156-
159+
157160
// Try to get partial content if available
158161
$content = $candidate[self::RESPONSE_KEY_CONTENT] ?? null;
159162
if ($content !== null && isset($content[self::REQUEST_KEY_PARTS]) && !empty($content[self::REQUEST_KEY_PARTS])) {
@@ -163,7 +166,7 @@ protected function callApi(string $prompt, string $systemInstruction): string
163166
return (string)$part[self::REQUEST_KEY_TEXT] . "\n\n[Note: Response was truncated. {$message}]";
164167
}
165168
}
166-
169+
167170
throw new QueryException(
168171
"Google API response issue: {$message}",
169172
0
@@ -173,6 +176,7 @@ protected function callApi(string $prompt, string $systemInstruction): string
173176
if (!isset($candidate[self::RESPONSE_KEY_CONTENT])) {
174177
$responseJson = json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
175178
$responseStr = is_string($responseJson) ? substr($responseJson, 0, 500) : 'Unable to encode response';
179+
176180
throw new QueryException(
177181
'Invalid response format from Google API: missing "content" key in candidate. Response: ' . $responseStr,
178182
0
@@ -184,6 +188,7 @@ protected function callApi(string $prompt, string $systemInstruction): string
184188
if (!isset($content[self::REQUEST_KEY_PARTS]) || empty($content[self::REQUEST_KEY_PARTS])) {
185189
$responseJson = json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
186190
$responseStr = is_string($responseJson) ? substr($responseJson, 0, 500) : 'Unable to encode response';
191+
187192
throw new QueryException(
188193
'Invalid response format from Google API: missing or empty "parts" array. Response: ' . $responseStr,
189194
0
@@ -195,6 +200,7 @@ protected function callApi(string $prompt, string $systemInstruction): string
195200
if (!isset($part[self::REQUEST_KEY_TEXT])) {
196201
$responseJson = json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
197202
$responseStr = is_string($responseJson) ? substr($responseJson, 0, 500) : 'Unable to encode response';
203+
198204
throw new QueryException(
199205
'Invalid response format from Google API: missing "text" key in part. Response: ' . $responseStr,
200206
0

src/ai/providers/YandexProvider.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ protected function callApi(string $systemPrompt, string $userPrompt): string
124124
if (isset($response['error']) && $response['error'] !== '') {
125125
$error = $response['error'];
126126
$errorMessage = is_string($error) ? $error : json_encode($error);
127+
127128
throw new QueryException(
128129
'Yandex API error: ' . $errorMessage,
129130
0
@@ -144,7 +145,7 @@ protected function callApi(string $systemPrompt, string $userPrompt): string
144145
$outputArray = $response[self::RESPONSE_KEY_OUTPUT];
145146
if (!empty($outputArray) && is_array($outputArray[0])) {
146147
$firstOutput = $outputArray[0];
147-
148+
148149
// Try content first (for models like aliceai-llm) - this is the correct field
149150
$textArray = null;
150151
if (isset($firstOutput[self::RESPONSE_KEY_CONTENT]) && is_array($firstOutput[self::RESPONSE_KEY_CONTENT])) {
@@ -154,7 +155,7 @@ protected function callApi(string $systemPrompt, string $userPrompt): string
154155
// Try summary (for some models like gpt-oss-120b) - but may contain prompt, not response
155156
$textArray = $firstOutput[self::RESPONSE_KEY_SUMMARY];
156157
}
157-
158+
158159
if ($textArray !== null) {
159160
// Collect all text from all items
160161
$textParts = [];
@@ -171,7 +172,7 @@ protected function callApi(string $systemPrompt, string $userPrompt): string
171172
// Filter out prompt-like content (contains instructions or user prompt)
172173
// If output looks like a prompt rather than a response, skip it
173174
$lowerOutput = strtolower($output);
174-
if (str_contains($lowerOutput, 'the user asks') ||
175+
if (str_contains($lowerOutput, 'the user asks') ||
175176
str_contains($lowerOutput, 'need to give recommendations') ||
176177
str_contains($lowerOutput, 'provide suggestions') ||
177178
(str_contains($lowerOutput, 'analyze') && str_contains($lowerOutput, 'query') && strlen($output) < 500)) {
@@ -195,6 +196,7 @@ protected function callApi(string $systemPrompt, string $userPrompt): string
195196
$responseJson = 'Unable to encode response';
196197
}
197198
$responseStr = substr($responseJson, 0, 1000);
199+
198200
throw new QueryException(
199201
'Yandex API response was truncated due to max_output_tokens, but output is missing or invalid. ' .
200202
'Please increase max_output_tokens. Response: ' . $responseStr,
@@ -208,6 +210,7 @@ protected function callApi(string $systemPrompt, string $userPrompt): string
208210
$responseJson = 'Unable to encode response';
209211
}
210212
$responseStr = substr($responseJson, 0, 1000);
213+
211214
throw new QueryException(
212215
'Invalid response format from Yandex API: missing or invalid "output" key. Expected structure: output[0].summary[0].text or output[0].content[0].text. Response: ' . $responseStr,
213216
0
@@ -304,4 +307,3 @@ protected function ensureAvailable(): void
304307
}
305308
}
306309
}
307-

src/cli/InitWizard.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ protected function askAiConfiguration(): void
699699
}
700700

701701
$providers = ['openai', 'anthropic', 'google', 'microsoft', 'ollama', 'deepseek', 'yandex'];
702-
echo " Available providers: " . implode(', ', $providers) . "\n";
702+
echo ' Available providers: ' . implode(', ', $providers) . "\n";
703703
$provider = static::readInput(' Default AI provider', 'openai');
704704
$provider = mb_strtolower(trim($provider), 'UTF-8');
705705
if (!in_array($provider, $providers, true)) {
@@ -712,7 +712,7 @@ protected function askAiConfiguration(): void
712712
foreach ($providers as $p) {
713713
if ($p === 'ollama') {
714714
// Ollama doesn't need API key, but ask for URL
715-
$url = static::readInput(" Ollama URL", 'http://localhost:11434');
715+
$url = static::readInput(' Ollama URL', 'http://localhost:11434');
716716
if ($url !== '') {
717717
$this->config['ai']['ollama_url'] = $url;
718718
}
@@ -754,7 +754,7 @@ protected function askAiConfiguration(): void
754754
}
755755

756756
echo "\n {$p} settings:\n";
757-
$model = static::readInput(" Model (optional)", '');
757+
$model = static::readInput(' Model (optional)', '');
758758
if ($model !== '') {
759759
if (!isset($this->config['ai']['providers'][$p])) {
760760
$this->config['ai']['providers'][$p] = [];

src/cli/MarkdownFormatter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function format(string $markdown): string
5353
// This protects content from markdown formatting (like asterisks and underscores)
5454
$inlineCode = [];
5555
$inlineCodeIndex = 0;
56-
56+
5757
// First, handle double backticks (``code``) - these can contain single backticks
5858
// Pattern: `` followed by content (which can include `) followed by ``
5959
// Match the shortest possible sequence to avoid greedy matching
@@ -69,7 +69,7 @@ public function format(string $markdown): string
6969
$text = '';
7070
}
7171
$text = (string)$text;
72-
72+
7373
// Then handle single backticks (`code`) - these cannot contain backticks
7474
// Use non-greedy matching to handle cases correctly
7575
$text = preg_replace_callback('/`([^`\n]+?)`/', function (array $matches) use (&$inlineCode, &$inlineCodeIndex): string {

0 commit comments

Comments
 (0)