Skip to content

Commit a594bcc

Browse files
authored
xAI/Grok Support (#55)
1 parent 6ad1789 commit a594bcc

20 files changed

+815
-0
lines changed

config/prism.php

+4
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,9 @@
2525
'api_key' => env('GROQ_API_KEY', ''),
2626
'url' => env('GROQ_URL', 'https://api.groq.com/openai/v1'),
2727
],
28+
'xai' => [
29+
'api_key' => env('XAI_API_KEY', ''),
30+
'url' => env('XAI_URL', 'https://api.x.ai/v1'),
31+
],
2832
],
2933
];

docs/.vitepress/config.mts

+4
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ export default defineConfig({
131131
text: "OpenAI",
132132
link: "/providers/openai",
133133
},
134+
{
135+
text: "XAI",
136+
link: "/providers/xai",
137+
},
134138
],
135139
},
136140
{

docs/getting-started/introduction.md

+1
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,6 @@ We currently offer first-party support for these leading AI providers:
6565
- [Mistral](https://mistral.ai)
6666
- [Ollama](https://ollama.com)
6767
- [OpenAI](https://openai.com)
68+
- [xAI](https://x.ai/)
6869

6970
Each provider brings its own strengths to the table, and Prism makes it easy to use them all through a consistent, elegajnt interface.

docs/providers/xai.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# xAI
2+
## Configuration
3+
4+
```php
5+
'xai' => [
6+
'api_key' => env('XAI_API_KEY', ''),
7+
'url' => env('XAI_URL', 'https://api.x.ai/v1'),
8+
],
9+
```
10+
11+
## Limitations
12+
### Image Support
13+
14+
xAI does not support image inputs.

src/Enums/Provider.php

+1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ enum Provider: string
1111
case OpenAI = 'openai';
1212
case Mistral = 'mistral';
1313
case Groq = 'groq';
14+
case XAI = 'xai';
1415
}

src/PrismManager.php

+12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use EchoLabs\Prism\Providers\Mistral\Mistral;
1313
use EchoLabs\Prism\Providers\Ollama\Ollama;
1414
use EchoLabs\Prism\Providers\OpenAI\OpenAI;
15+
use EchoLabs\Prism\Providers\XAI\XAI;
1516
use Illuminate\Contracts\Foundation\Application;
1617
use InvalidArgumentException;
1718
use RuntimeException;
@@ -147,4 +148,15 @@ protected function createGroqProvider(array $config): Groq
147148
apiKey: $config['api_key'],
148149
);
149150
}
151+
152+
/**
153+
* @param array<string, string> $config
154+
*/
155+
protected function createXaiProvider(array $config): XAI
156+
{
157+
return new XAI(
158+
url: $config['url'],
159+
apiKey: $config['api_key'],
160+
);
161+
}
150162
}

src/Providers/XAI/Client.php

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace EchoLabs\Prism\Providers\XAI;
6+
7+
use Illuminate\Http\Client\PendingRequest;
8+
use Illuminate\Http\Client\Response;
9+
use Illuminate\Support\Facades\Http;
10+
11+
class Client
12+
{
13+
protected PendingRequest $client;
14+
15+
/**
16+
* @param array<string, mixed> $options
17+
*/
18+
public function __construct(
19+
public readonly string $url,
20+
public readonly string $apiKey,
21+
public readonly array $options = [],
22+
) {
23+
$this->client = Http::withHeaders(array_filter([
24+
'Authorization' => sprintf('Bearer %s', $this->apiKey),
25+
]))
26+
->withOptions($this->options)
27+
->baseUrl($this->url);
28+
}
29+
30+
/**
31+
* @param array<int, mixed> $messages
32+
* @param array<int, mixed>|null $tools
33+
* @param array<string, mixed>|string|null $toolChoice
34+
*/
35+
public function messages(
36+
string $model,
37+
array $messages,
38+
?int $maxTokens,
39+
int|float|null $temperature,
40+
int|float|null $topP,
41+
?array $tools,
42+
string|array|null $toolChoice,
43+
): Response {
44+
return $this->client->post(
45+
'chat/completions',
46+
array_merge([
47+
'model' => $model,
48+
'messages' => $messages,
49+
'max_tokens' => $maxTokens ?? 2048,
50+
], array_filter([
51+
'temperature' => $temperature,
52+
'top_p' => $topP,
53+
'tools' => $tools,
54+
'tool_choice' => $toolChoice,
55+
]))
56+
);
57+
}
58+
}

src/Providers/XAI/MessageMap.php

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace EchoLabs\Prism\Providers\XAI;
6+
7+
use EchoLabs\Prism\Contracts\Message;
8+
use EchoLabs\Prism\ValueObjects\Messages\AssistantMessage;
9+
use EchoLabs\Prism\ValueObjects\Messages\SystemMessage;
10+
use EchoLabs\Prism\ValueObjects\Messages\ToolResultMessage;
11+
use EchoLabs\Prism\ValueObjects\Messages\UserMessage;
12+
use EchoLabs\Prism\ValueObjects\ToolCall;
13+
use Exception;
14+
15+
class MessageMap
16+
{
17+
/** @var array<int, mixed> */
18+
protected $mappedMessages = [];
19+
20+
/**
21+
* @param array<int, Message> $messages
22+
*/
23+
public function __construct(
24+
protected array $messages,
25+
protected string $systemPrompt
26+
) {
27+
if ($systemPrompt !== '' && $systemPrompt !== '0') {
28+
$this->messages = array_merge(
29+
[new SystemMessage($systemPrompt)],
30+
$this->messages
31+
);
32+
}
33+
}
34+
35+
/**
36+
* @return array<int, mixed>
37+
*/
38+
public function __invoke(): array
39+
{
40+
array_map(
41+
fn (Message $message) => $this->mapMessage($message),
42+
$this->messages
43+
);
44+
45+
return $this->mappedMessages;
46+
}
47+
48+
public function mapMessage(Message $message): void
49+
{
50+
match ($message::class) {
51+
UserMessage::class => $this->mapUserMessage($message),
52+
AssistantMessage::class => $this->mapAssistantMessage($message),
53+
ToolResultMessage::class => $this->mapToolResultMessage($message),
54+
SystemMessage::class => $this->mapSystemMessage($message),
55+
default => throw new Exception('Could not map message type '.$message::class),
56+
};
57+
}
58+
59+
protected function mapSystemMessage(SystemMessage $message): void
60+
{
61+
$this->mappedMessages[] = [
62+
'role' => 'system',
63+
'content' => $message->content,
64+
];
65+
}
66+
67+
protected function mapToolResultMessage(ToolResultMessage $message): void
68+
{
69+
foreach ($message->toolResults as $toolResult) {
70+
$this->mappedMessages[] = [
71+
'role' => 'tool',
72+
'tool_call_id' => $toolResult->toolCallId,
73+
'content' => $toolResult->result,
74+
];
75+
}
76+
}
77+
78+
protected function mapUserMessage(UserMessage $message): void
79+
{
80+
$this->mappedMessages[] = [
81+
'role' => 'user',
82+
'content' => [
83+
['type' => 'text', 'text' => $message->text()],
84+
],
85+
];
86+
}
87+
88+
protected function mapAssistantMessage(AssistantMessage $message): void
89+
{
90+
$toolCalls = array_map(fn (ToolCall $toolCall): array => [
91+
'id' => $toolCall->id,
92+
'type' => 'function',
93+
'function' => [
94+
'name' => $toolCall->name,
95+
'arguments' => json_encode($toolCall->arguments()),
96+
],
97+
], $message->toolCalls);
98+
99+
$this->mappedMessages[] = array_filter([
100+
'role' => 'assistant',
101+
'content' => $message->content,
102+
'tool_calls' => $toolCalls,
103+
]);
104+
}
105+
}

src/Providers/XAI/Tool.php

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace EchoLabs\Prism\Providers\XAI;
6+
7+
use EchoLabs\Prism\Providers\ProviderTool;
8+
use EchoLabs\Prism\Tool as PrismTool;
9+
10+
class Tool extends ProviderTool
11+
{
12+
#[\Override]
13+
public static function toArray(PrismTool $tool): array
14+
{
15+
return [
16+
'type' => 'function',
17+
'function' => [
18+
'name' => $tool->name(),
19+
'description' => $tool->description(),
20+
'parameters' => [
21+
'type' => 'object',
22+
'properties' => collect($tool->parameters())
23+
->keyBy('name')
24+
->map(fn (array $field): array => [
25+
'description' => $field['description'],
26+
'type' => $field['type'],
27+
])
28+
->toArray(),
29+
'required' => $tool->requiredParameters(),
30+
],
31+
],
32+
];
33+
}
34+
}

0 commit comments

Comments
 (0)