Skip to content

Commit 9c7dcf4

Browse files
committed
feat: add support for mistral
1 parent bdddee6 commit 9c7dcf4

29 files changed

+593
-74
lines changed

.env

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ OPENAI_API_KEY=
66
# For using Claude on Anthropic
77
ANTHROPIC_API_KEY=
88

9+
# For using Mistral
10+
MISTRAL_API_KEY=
11+
912
# For using Voyage
1013
VOYAGE_API_KEY=
1114

examples/mistral/chat.php

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Bridge\Mistral\Mistral;
4+
use PhpLlm\LlmChain\Bridge\Mistral\PlatformFactory;
5+
use PhpLlm\LlmChain\Chain;
6+
use PhpLlm\LlmChain\Model\Message\Message;
7+
use PhpLlm\LlmChain\Model\Message\MessageBag;
8+
use Symfony\Component\Dotenv\Dotenv;
9+
10+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
11+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
12+
13+
if (empty($_ENV['MISTRAL_API_KEY'])) {
14+
echo 'Please set the REPLICATE_API_KEY environment variable.'.PHP_EOL;
15+
exit(1);
16+
}
17+
18+
$platform = PlatformFactory::create($_ENV['MISTRAL_API_KEY']);
19+
$llm = new Mistral();
20+
$chain = new Chain($platform, $llm);
21+
22+
$messages = new MessageBag(Message::ofUser('What is the best French cheese?'));
23+
$response = $chain->call($messages, [
24+
'temperature' => 0.7,
25+
]);
26+
27+
echo $response->getContent().PHP_EOL;

examples/mistral/image.php

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Bridge\Mistral\Mistral;
4+
use PhpLlm\LlmChain\Bridge\Mistral\PlatformFactory;
5+
use PhpLlm\LlmChain\Chain;
6+
use PhpLlm\LlmChain\Model\Message\Content\Image;
7+
use PhpLlm\LlmChain\Model\Message\Message;
8+
use PhpLlm\LlmChain\Model\Message\MessageBag;
9+
use Symfony\Component\Dotenv\Dotenv;
10+
11+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
12+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
13+
14+
if (empty($_ENV['OPENAI_API_KEY'])) {
15+
echo 'Please set the OPENAI_API_KEY environment variable.'.PHP_EOL;
16+
exit(1);
17+
}
18+
19+
$platform = PlatformFactory::create($_ENV['MISTRAL_API_KEY']);
20+
$llm = new Mistral(Mistral::MISTRAL_SMALL);
21+
$chain = new Chain($platform, $llm);
22+
23+
$messages = new MessageBag(
24+
Message::forSystem('You are an image analyzer bot that helps identify the content of images.'),
25+
Message::ofUser(
26+
'Describe the image as a comedian would do it.',
27+
Image::fromFile(dirname(__DIR__, 2).'/tests/Fixture/image.jpg'),
28+
),
29+
);
30+
$response = $chain->call($messages);
31+
32+
echo $response->getContent().PHP_EOL;

examples/mistral/stream.php

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Bridge\Mistral\Mistral;
4+
use PhpLlm\LlmChain\Bridge\Mistral\PlatformFactory;
5+
use PhpLlm\LlmChain\Chain;
6+
use PhpLlm\LlmChain\Model\Message\Message;
7+
use PhpLlm\LlmChain\Model\Message\MessageBag;
8+
use Symfony\Component\Dotenv\Dotenv;
9+
10+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
11+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
12+
13+
if (empty($_ENV['MISTRAL_API_KEY'])) {
14+
echo 'Please set the REPLICATE_API_KEY environment variable.'.PHP_EOL;
15+
exit(1);
16+
}
17+
18+
$platform = PlatformFactory::create($_ENV['MISTRAL_API_KEY']);
19+
$llm = new Mistral();
20+
$chain = new Chain($platform, $llm);
21+
22+
$messages = new MessageBag(Message::ofUser('What is the eighth prime number?'));
23+
$response = $chain->call($messages, [
24+
'stream' => true,
25+
]);
26+
27+
foreach ($response->getContent() as $word) {
28+
echo $word;
29+
}
30+
echo PHP_EOL;

examples/mistral/toolcall-stream.php

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Bridge\Mistral\Mistral;
4+
use PhpLlm\LlmChain\Bridge\Mistral\PlatformFactory;
5+
use PhpLlm\LlmChain\Chain;
6+
use PhpLlm\LlmChain\Chain\Toolbox\ChainProcessor;
7+
use PhpLlm\LlmChain\Chain\Toolbox\Tool\YouTubeTranscriber;
8+
use PhpLlm\LlmChain\Chain\Toolbox\Toolbox;
9+
use PhpLlm\LlmChain\Model\Message\Message;
10+
use PhpLlm\LlmChain\Model\Message\MessageBag;
11+
use Symfony\Component\Dotenv\Dotenv;
12+
use Symfony\Component\HttpClient\HttpClient;
13+
14+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
15+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
16+
17+
if (empty($_ENV['MISTRAL_API_KEY'])) {
18+
echo 'Please set the REPLICATE_API_KEY environment variable.'.PHP_EOL;
19+
exit(1);
20+
}
21+
22+
$platform = PlatformFactory::create($_ENV['MISTRAL_API_KEY']);
23+
$llm = new Mistral();
24+
25+
$transcriber = new YouTubeTranscriber(HttpClient::create());
26+
$toolbox = Toolbox::create($transcriber);
27+
$processor = new ChainProcessor($toolbox);
28+
$chain = new Chain($platform, $llm, [$processor], [$processor]);
29+
30+
$messages = new MessageBag(Message::ofUser('Please summarize this video for me: https://www.youtube.com/watch?v=6uXW-ulpj0s'));
31+
$response = $chain->call($messages, [
32+
'stream' => true,
33+
]);
34+
35+
foreach ($response->getContent() as $word) {
36+
echo $word;
37+
}
38+
echo PHP_EOL;

examples/mistral/toolcall.php

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Bridge\Mistral\Mistral;
4+
use PhpLlm\LlmChain\Bridge\Mistral\PlatformFactory;
5+
use PhpLlm\LlmChain\Chain;
6+
use PhpLlm\LlmChain\Chain\Toolbox\ChainProcessor;
7+
use PhpLlm\LlmChain\Chain\Toolbox\Tool\Clock;
8+
use PhpLlm\LlmChain\Chain\Toolbox\Toolbox;
9+
use PhpLlm\LlmChain\Model\Message\Message;
10+
use PhpLlm\LlmChain\Model\Message\MessageBag;
11+
use Symfony\Component\Dotenv\Dotenv;
12+
13+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
14+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
15+
16+
if (empty($_ENV['MISTRAL_API_KEY'])) {
17+
echo 'Please set the REPLICATE_API_KEY environment variable.'.PHP_EOL;
18+
exit(1);
19+
}
20+
21+
$platform = PlatformFactory::create($_ENV['MISTRAL_API_KEY']);
22+
$llm = new Mistral();
23+
24+
$toolbox = Toolbox::create(new Clock());
25+
$processor = new ChainProcessor($toolbox);
26+
$chain = new Chain($platform, $llm, [$processor], [$processor]);
27+
28+
$messages = new MessageBag(Message::ofUser('What time is it?'));
29+
$response = $chain->call($messages);
30+
31+
echo $response->getContent().PHP_EOL;

src/Bridge/Mistral/Embeddings.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Bridge\Mistral;
6+
7+
use PhpLlm\LlmChain\Model\EmbeddingsModel;
8+
9+
final readonly class Embeddings implements EmbeddingsModel
10+
{
11+
public const MISTRAL_EMBED = 'mistral-embed';
12+
13+
public function __construct(
14+
private string $name = self::MISTRAL_EMBED,
15+
private array $options = [],
16+
) {
17+
}
18+
19+
public function getName(): string
20+
{
21+
return $this->name;
22+
}
23+
24+
public function getOptions(): array
25+
{
26+
return $this->options;
27+
}
28+
29+
public function supportsMultipleInputs(): bool
30+
{
31+
return true;
32+
}
33+
}

src/Bridge/Mistral/Mistral.php

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Bridge\Mistral;
6+
7+
use PhpLlm\LlmChain\Model\LanguageModel;
8+
9+
final readonly class Mistral implements LanguageModel
10+
{
11+
public const CODESTRAL = 'codestral-latest';
12+
public const CODESTRAL_MAMBA = 'open-codestral-mamba';
13+
public const MISTRAL_LARGE = 'mistral-large-latest';
14+
public const MISTRAL_SMALL = 'mistral-small-latest';
15+
public const MISTRAL_NEMO = 'open-mistral-nemo';
16+
public const MISTRAL_SABA = 'mistral-saba-latest';
17+
public const MINISTRAL_3B = 'mistral-3b-latest';
18+
public const MINISTRAL_8B = 'mistral-8b-latest';
19+
public const PIXSTRAL_LARGE = 'pixstral-large-latest';
20+
public const PIXSTRAL = 'pixstral-12b-latest';
21+
22+
public function __construct(
23+
private string $name = self::MISTRAL_LARGE,
24+
private array $options = [],
25+
) {
26+
}
27+
28+
public function getName(): string
29+
{
30+
return $this->name;
31+
}
32+
33+
public function getOptions(): array
34+
{
35+
return $this->options;
36+
}
37+
38+
public function supportsAudioInput(): bool
39+
{
40+
return false;
41+
}
42+
43+
public function supportsImageInput(): bool
44+
{
45+
return in_array($this->name, [self::PIXSTRAL, self::PIXSTRAL_LARGE, self::MISTRAL_SMALL], true);
46+
}
47+
48+
public function supportsStreaming(): bool
49+
{
50+
return true;
51+
}
52+
53+
public function supportsStructuredOutput(): bool
54+
{
55+
return true;
56+
}
57+
58+
public function supportsToolCalling(): bool
59+
{
60+
return in_array($this->name, [
61+
self::CODESTRAL,
62+
self::MISTRAL_LARGE,
63+
self::MISTRAL_SMALL,
64+
self::MISTRAL_NEMO,
65+
self::MINISTRAL_3B,
66+
self::MINISTRAL_8B,
67+
self::PIXSTRAL,
68+
self::PIXSTRAL_LARGE,
69+
], true);
70+
}
71+
}

src/Bridge/Mistral/ModelClient.php

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Bridge\Mistral;
6+
7+
use PhpLlm\LlmChain\Model\Message\MessageBagInterface;
8+
use PhpLlm\LlmChain\Model\Model;
9+
use PhpLlm\LlmChain\Platform\ModelClient as PlatformResponseFactory;
10+
use Symfony\Component\HttpClient\EventSourceHttpClient;
11+
use Symfony\Contracts\HttpClient\HttpClientInterface;
12+
use Symfony\Contracts\HttpClient\ResponseInterface;
13+
14+
final readonly class ModelClient implements PlatformResponseFactory
15+
{
16+
private EventSourceHttpClient $httpClient;
17+
18+
public function __construct(
19+
HttpClientInterface $httpClient,
20+
#[\SensitiveParameter]
21+
private string $apiKey,
22+
) {
23+
$this->httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
24+
}
25+
26+
public function supports(Model $model, object|array|string $input): bool
27+
{
28+
return $model instanceof Mistral && $input instanceof MessageBagInterface;
29+
}
30+
31+
public function request(Model $model, object|array|string $input, array $options = []): ResponseInterface
32+
{
33+
return $this->httpClient->request('POST', 'https://api.mistral.ai/v1/chat/completions', [
34+
'auth_bearer' => $this->apiKey,
35+
'headers' => [
36+
'Content-Type' => 'application/json',
37+
'Accept' => 'application/json',
38+
],
39+
'json' => array_merge($options, [
40+
'model' => $model->getName(),
41+
'messages' => $input,
42+
]),
43+
]);
44+
}
45+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Bridge\Mistral;
6+
7+
use PhpLlm\LlmChain\Platform;
8+
use Symfony\Component\HttpClient\EventSourceHttpClient;
9+
use Symfony\Contracts\HttpClient\HttpClientInterface;
10+
11+
final class PlatformFactory
12+
{
13+
public static function create(
14+
#[\SensitiveParameter]
15+
string $apiKey,
16+
?HttpClientInterface $httpClient = null,
17+
): Platform {
18+
$httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
19+
20+
return new Platform([new ModelClient($httpClient, $apiKey)], [new ResponseConverter()]);
21+
}
22+
}

0 commit comments

Comments
 (0)