Skip to content

Commit d71ea56

Browse files
committed
feat(models): add xAI (Grok) support
- Introduce `XaiDriver` for xAI Grok API, mirroring OpenAiDriver - Register new `xai` vendor in `ModelRegistry` - Extend `resources/models.yaml` with xAI endpoint, auth config, usage mapping (including reasoning tokens) and grok-* models - Update `README.md` and CLI `Help` to list `xai:grok-*` models and require `XAI_API_KEY` - Restructure README section order slightly to accommodate new vendor
1 parent 0910e70 commit d71ea56

File tree

5 files changed

+196
-0
lines changed

5 files changed

+196
-0
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ APIs supported:
2727
- `yandex:gpt-lite` - YandexGPT Lite
2828
- `yandex:gpt-pro` - YandexGPT Pro
2929
- `yandex:gpt-32k` - YandexGPT Pro 32K
30+
- X.ai:
31+
- `xai:grok-4` - Grok 4
32+
- `xai:grok-3` - Grok 3
33+
- `xai:grok-3-fast` - Grok 3 Fast
34+
- `xai:grok-3-mini` - Grok 3 Mini
35+
- `xai:grok-3-mini-fast` - Grok 3 Mini Fast
3036

3137
## Features
3238

@@ -61,6 +67,7 @@ chmod +x vendor/bin/bblslug
6167
export GOOGLE_API_KEY=...
6268
export OPENAI_API_KEY=...
6369
export YANDEX_API_KEY=... && export YANDEX_FOLDER_ID=...
70+
export XAI_API_KEY=...
6471
```
6572

6673
**NB!** Some vendors require additional parameters, e.g. `YANDEX_FOLDER_ID`.

resources/models.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,66 @@ yandex:
291291
defaults:
292292
model: llama/latest
293293
notes: 'Full-scale Meta Llama 70 B, up to 8 k tokens sync or async.'
294+
295+
296+
# -------------------------------------------------------------------
297+
# xAI (Grok)
298+
# -------------------------------------------------------------------
299+
xai:
300+
endpoint: 'https://api.x.ai/v1/chat/completions'
301+
format: text
302+
defaults:
303+
temperature: 0.0
304+
source_lang: auto
305+
target_lang: EN
306+
requirements:
307+
auth:
308+
type: header
309+
key_name: Authorization
310+
prefix: Bearer
311+
env: XAI_API_KEY
312+
help_url: 'https://console.x.ai/team/default/api-keys'
313+
headers:
314+
- 'Content-Type: application/json'
315+
limits:
316+
max_tokens: 262144
317+
token_estimator: gpt
318+
estimated_max_chars: 1048576
319+
usage:
320+
tokens:
321+
total: total_tokens
322+
breakdown:
323+
prompt: prompt_tokens
324+
completion: completion_tokens
325+
reasoning: completion_tokens_details.reasoning_tokens
326+
327+
models:
328+
grok-4:
329+
name: 'Grok 4'
330+
defaults:
331+
model: grok-4
332+
notes: 'Scientist-grade reasoning, coding mode, and real-time internet understanding'
333+
334+
grok-3:
335+
name: 'Grok 3'
336+
defaults:
337+
model: grok-3
338+
notes: 'Optimized for logical reasoning, math problem-solving, and real-time data with DeepSearch'
339+
340+
grok-3-fast:
341+
name: 'Grok 3 Fast'
342+
defaults:
343+
model: grok-3-fast
344+
notes: 'Optimized for fastest Grok 3 inference'
345+
346+
grok-3-mini:
347+
name: 'Grok 3 Mini'
348+
defaults:
349+
model: grok-3-mini
350+
notes: 'Compact variant balancing Grok 3 performance and efficiency'
351+
352+
grok-3-mini-fast:
353+
name: 'Grok 3 Mini Fast'
354+
defaults:
355+
model: grok-3-mini-fast
356+
notes: 'Mini variant optimized for fastest inference with compact footprint'

src/Bblslug/Console/Help.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ public static function printHelp(?int $exitCode = 1): void
6464
echo "\t export DEEPL_PRO_API_KEY=\"...\" # for DeepL Pro\n";
6565
echo "\t export GOOGLE_API_KEY=\"...\" # for Google (Gemini)\n";
6666
echo "\t export OPENAI_API_KEY=\"...\" # for OpenAI (GPT)\n";
67+
echo "\t export YANDEX_API_KEY=\"...\" && export YANDEX_FOLDER_ID=\"...\" # for Yandex (FM)\n";
68+
echo "\t export XAI_API_KEY=\"...\" # for X.AI (Grok)\n";
6769
echo "\t (See each model's required variable with --list-models)\n";
6870
echo "\tSome models may not require API keys at all.\n";
6971
echo "\tModel-specific variables can be passed via --variables or read from env\n";
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?php
2+
3+
namespace Bblslug\Models\Drivers;
4+
5+
use Bblslug\Models\ModelDriverInterface;
6+
use Bblslug\Models\Prompts;
7+
8+
/**
9+
* xAI Grok driver using explicit markers and OpenAI‐style API.
10+
*/
11+
class XaiDriver implements ModelDriverInterface
12+
{
13+
private const START = '‹‹TRANSLATION››';
14+
private const END = '‹‹END››';
15+
16+
/**
17+
* Build HTTP request params for Grok.
18+
*
19+
* @param array<string,mixed> $config Model config from registry
20+
* @param string $text Input text
21+
* @param array<string,mixed> $options Options: [
22+
* 'dryRun' => bool,
23+
* 'format' => 'text'|'html',
24+
* 'verbose' => bool,
25+
* 'temperature' => float,
26+
* 'context' => string|null,
27+
* ]
28+
*
29+
* @return array{
30+
* url: string,
31+
* headers: string[],
32+
* body: string
33+
* }
34+
*/
35+
public function buildRequest(array $config, string $text, array $options): array
36+
{
37+
$defaults = $config['defaults'] ?? [];
38+
$model = $defaults['model'] ?? throw new \RuntimeException('Missing xAI model name');
39+
$temperature = (float) ($options['temperature'] ?? $defaults['temperature'] ?? 0.0);
40+
$context = trim((string) ($options['context'] ?? $defaults['context'] ?? ''));
41+
$format = $options['format'] ?? 'text';
42+
43+
$systemPrompt = Prompts::render(
44+
'translator',
45+
$format,
46+
[
47+
'source' => $defaults['source_lang'] ?? 'auto',
48+
'target' => $defaults['target_lang'] ?? 'EN',
49+
'start' => self::START,
50+
'end' => self::END,
51+
'context' => $context !== '' ? "Context: {$context}" : '',
52+
]
53+
);
54+
55+
$messages = [
56+
['role' => 'system', 'content' => $systemPrompt],
57+
['role' => 'user', 'content' => self::START . "\n{$text}\n" . self::END],
58+
];
59+
60+
$payload = [
61+
'model' => $model,
62+
'messages' => $messages,
63+
'temperature' => $temperature,
64+
'stream' => false,
65+
];
66+
67+
return [
68+
'url' => $config['endpoint'],
69+
'headers' => $config['requirements']['headers'] ?? [],
70+
'body' => json_encode($payload, JSON_UNESCAPED_UNICODE),
71+
];
72+
}
73+
74+
/**
75+
* Parse the translated text from Grok's response.
76+
*
77+
* @param array<string,mixed> $config Model config (not used)
78+
* @param string $responseBody Raw HTTP body
79+
*
80+
* @return array{
81+
* text: string,
82+
* usage: array<string,mixed>|null
83+
* }
84+
*
85+
* @throws \RuntimeException If API returned an error or markers not found
86+
*/
87+
public function parseResponse(array $config, string $responseBody): array
88+
{
89+
$data = json_decode($responseBody, true);
90+
91+
// xAI error payload (model not found, auth, etc.)
92+
if (isset($data['error'])) {
93+
$code = $data['code'] ?? 'unknown_error';
94+
throw new \RuntimeException("Grok API error [{$code}]: {$data['error']}");
95+
}
96+
97+
// Normal completion path
98+
if (
99+
empty($data['choices'][0]['message']['content'])
100+
|| !is_string($data['choices'][0]['message']['content'])
101+
) {
102+
throw new \RuntimeException("Grok translation failed: {$responseBody}");
103+
}
104+
105+
$content = $data['choices'][0]['message']['content'];
106+
107+
if (
108+
!preg_match(
109+
'/' . preg_quote(self::START, '/') . '(.*?)' . preg_quote(self::END, '/') . '/s',
110+
$content,
111+
$matches
112+
)
113+
) {
114+
throw new \RuntimeException("Markers not found in Grok response");
115+
}
116+
117+
$text = trim($matches[1]);
118+
$usage = $data['usage'] ?? null;
119+
120+
return ['text' => $text, 'usage' => $usage];
121+
}
122+
}

src/Bblslug/Models/ModelRegistry.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Bblslug\Models\Drivers\GoogleDriver;
88
use Bblslug\Models\Drivers\OpenAiDriver;
99
use Bblslug\Models\Drivers\YandexDriver;
10+
use Bblslug\Models\Drivers\XaiDriver;
1011
use Bblslug\Models\ModelDriverInterface;
1112
use Symfony\Component\Yaml\Yaml;
1213

@@ -196,6 +197,7 @@ public function getDriver(string $key): ModelDriverInterface
196197
'google' => new GoogleDriver(),
197198
'openai' => new OpenAiDriver(),
198199
'yandex' => new YandexDriver(),
200+
'xai' => new XaiDriver(),
199201
default => throw new \InvalidArgumentException("Unknown vendor '{$vendor}' in registry."),
200202
};
201203
}

0 commit comments

Comments
 (0)