Skip to content

Commit c27618d

Browse files
authored
feat: add support for mistral (#291)
* [x] text generation * [x] streaming * [x] tool calling * [x] image input * [x] structured output * [x] embeddings * [x] examples * [x] documentation Implementation is basically functional, but has following flaws to be resolved: 1. tool schema differs in a breaking way from GPT - extension point is a MUST 2. duplication of stream, message conversion and tool calling 3. error handling - once again
1 parent 773a700 commit c27618d

17 files changed

+675
-1
lines changed

.env

Lines changed: 3 additions & 0 deletions
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

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,11 @@ $embeddings = new Embeddings();
6868
* [Google's Gemini](https://gemini.google.com/) with [Google](https://ai.google.dev/) and [OpenRouter](https://www.openrouter.com/) as Platform
6969
* [DeepSeek's R1](https://www.deepseek.com/) with [OpenRouter](https://www.openrouter.com/) as Platform
7070
* [Amazon's Nova](https://nova.amazon.com) with [AWS](https://aws.amazon.com/bedrock/) as Platform
71+
* [Mistral's Mistral](https://www.mistral.ai/) with [Mistral](https://www.mistral.ai/) as Platform
7172
* Embeddings Models
7273
* [OpenAI's Text Embeddings](https://platform.openai.com/docs/guides/embeddings/embedding-models) with [OpenAI](https://platform.openai.com/docs/overview) and [Azure](https://learn.microsoft.com/azure/ai-services/openai/concepts/models) as Platform
7374
* [Voyage's Embeddings](https://docs.voyageai.com/docs/embeddings) with [Voyage](https://www.voyageai.com/) as Platform
75+
* [Mistral Embed](https://www.mistral.ai/) with [Mistral](https://www.mistral.ai/) as Platform
7476
* Other Models
7577
* [OpenAI's Dall·E](https://platform.openai.com/docs/guides/image-generation) with [OpenAI](https://platform.openai.com/docs/overview) as Platform
7678
* [OpenAI's Whisper](https://platform.openai.com/docs/guides/speech-to-text) with [OpenAI](https://platform.openai.com/docs/overview) and [Azure](https://learn.microsoft.com/azure/ai-services/openai/concepts/models) as Platform
@@ -137,6 +139,7 @@ $response = $chain->call($messages, [
137139
1. [Meta's Llama with Replicate](examples/replicate/chat-llama.php)
138140
1. [Google's Gemini with Google](examples/google/chat.php)
139141
1. [Google's Gemini with OpenRouter](examples/openrouter/chat-gemini.php)
142+
1. [Mistral's Mistral with Mistral](examples/mistral/chat-mistral.php)
140143

141144
### Tools
142145

@@ -409,7 +412,7 @@ use PhpLlm\LlmChain\Platform\Message\MessageBag;
409412

410413
// Initialize Platform & Models
411414

412-
$similaritySearch = new SimilaritySearch($embeddings, $store);
415+
$similaritySearch = new SimilaritySearch($model, $store);
413416
$toolbox = Toolbox::create($similaritySearch);
414417
$processor = new Chain($toolbox);
415418
$chain = new Chain($platform, $model, [$processor], [$processor]);
@@ -547,6 +550,7 @@ needs to be used.
547550
548551
1. [Streaming Claude](examples/anthropic/stream.php)
549552
1. [Streaming GPT](examples/openai/stream.php)
553+
1. [Streaming Mistral](examples/mistral/stream.php)
550554
551555
### Image Processing
552556
@@ -623,6 +627,7 @@ dump($vectors[0]->getData()); // Array of float values
623627
624628
1. [OpenAI's Emebddings](examples/openai/embeddings.php)
625629
1. [Voyage's Embeddings](examples/voyage/embeddings.php)
630+
1. [Mistral's Embed](examples/mistral/embeddings.php)
626631

627632
### Parallel Platform Calls
628633

examples/mistral/chat.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Chain\Chain;
4+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\Mistral;
5+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\PlatformFactory;
6+
use PhpLlm\LlmChain\Platform\Message\Message;
7+
use PhpLlm\LlmChain\Platform\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+
$model = new Mistral();
20+
$chain = new Chain($platform, $model);
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/embeddings.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\Embeddings;
4+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\PlatformFactory;
5+
use PhpLlm\LlmChain\Platform\Response\VectorResponse;
6+
use Symfony\Component\Dotenv\Dotenv;
7+
8+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
9+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
10+
11+
if (empty($_ENV['MISTRAL_API_KEY'])) {
12+
echo 'Please set the MISTRAL_API_KEY environment variable.'.\PHP_EOL;
13+
exit(1);
14+
}
15+
16+
$platform = PlatformFactory::create($_ENV['MISTRAL_API_KEY']);
17+
$model = new Embeddings();
18+
19+
$response = $platform->request($model, <<<TEXT
20+
In the middle of the 20th century, food scientists began to understand the importance of vitamins and minerals in
21+
human health. They discovered that certain nutrients were essential for growth, development, and overall well-being.
22+
This led to the fortification of foods with vitamins and minerals, such as adding vitamin D to milk and iodine to
23+
salt. The goal was to prevent deficiencies and promote better health in the population.
24+
TEXT);
25+
26+
assert($response instanceof VectorResponse);
27+
28+
echo 'Dimensions: '.$response->getContent()[0]->getDimensions().\PHP_EOL;

examples/mistral/image.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Chain\Chain;
4+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\Mistral;
5+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\PlatformFactory;
6+
use PhpLlm\LlmChain\Platform\Message\Content\Image;
7+
use PhpLlm\LlmChain\Platform\Message\Message;
8+
use PhpLlm\LlmChain\Platform\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+
$model = new Mistral(Mistral::MISTRAL_SMALL);
21+
$chain = new Chain($platform, $model);
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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Chain\Chain;
4+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\Mistral;
5+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\PlatformFactory;
6+
use PhpLlm\LlmChain\Platform\Message\Message;
7+
use PhpLlm\LlmChain\Platform\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+
$model = new Mistral();
20+
$chain = new Chain($platform, $model);
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;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Chain\Chain;
4+
use PhpLlm\LlmChain\Chain\StructuredOutput\ChainProcessor;
5+
use PhpLlm\LlmChain\Chain\StructuredOutput\ResponseFormatFactory;
6+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\Mistral;
7+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\PlatformFactory;
8+
use PhpLlm\LlmChain\Platform\Message\Message;
9+
use PhpLlm\LlmChain\Platform\Message\MessageBag;
10+
use PhpLlm\LlmChain\Tests\Fixture\StructuredOutput\MathReasoning;
11+
use Symfony\Component\Dotenv\Dotenv;
12+
use Symfony\Component\Serializer\Encoder\JsonEncoder;
13+
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
14+
use Symfony\Component\Serializer\Serializer;
15+
16+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
17+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
18+
19+
if (empty($_ENV['MISTRAL_API_KEY'])) {
20+
echo 'Please set the MISTRAL_API_KEY environment variable.'.\PHP_EOL;
21+
exit(1);
22+
}
23+
24+
$platform = PlatformFactory::create($_ENV['MISTRAL_API_KEY']);
25+
$model = new Mistral(Mistral::MISTRAL_SMALL);
26+
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
27+
28+
$processor = new ChainProcessor(new ResponseFormatFactory(), $serializer);
29+
$chain = new Chain($platform, $model, [$processor], [$processor]);
30+
$messages = new MessageBag(
31+
Message::forSystem('You are a helpful math tutor. Guide the user through the solution step by step.'),
32+
Message::ofUser('how can I solve 8x + 7 = -23'),
33+
);
34+
$response = $chain->call($messages, ['output_structure' => MathReasoning::class]);
35+
36+
dump($response->getContent());

examples/mistral/toolcall-stream.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Chain\Chain;
4+
use PhpLlm\LlmChain\Chain\Toolbox\ChainProcessor;
5+
use PhpLlm\LlmChain\Chain\Toolbox\Tool\YouTubeTranscriber;
6+
use PhpLlm\LlmChain\Chain\Toolbox\Toolbox;
7+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\Mistral;
8+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\PlatformFactory;
9+
use PhpLlm\LlmChain\Platform\Message\Message;
10+
use PhpLlm\LlmChain\Platform\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+
$model = new Mistral();
24+
25+
$transcriber = new YouTubeTranscriber(HttpClient::create());
26+
$toolbox = Toolbox::create($transcriber);
27+
$processor = new ChainProcessor($toolbox);
28+
$chain = new Chain($platform, $model, [$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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Chain\Chain;
4+
use PhpLlm\LlmChain\Chain\Toolbox\ChainProcessor;
5+
use PhpLlm\LlmChain\Chain\Toolbox\Tool\Clock;
6+
use PhpLlm\LlmChain\Chain\Toolbox\Toolbox;
7+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\Mistral;
8+
use PhpLlm\LlmChain\Platform\Bridge\Mistral\PlatformFactory;
9+
use PhpLlm\LlmChain\Platform\Message\Message;
10+
use PhpLlm\LlmChain\Platform\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+
$model = new Mistral();
23+
24+
$toolbox = Toolbox::create(new Clock());
25+
$processor = new ChainProcessor($toolbox);
26+
$chain = new Chain($platform, $model, [$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;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace PhpLlm\LlmChain\Platform\Bridge\Mistral\Contract;
4+
5+
use PhpLlm\LlmChain\Platform\Contract\Normalizer\ToolNormalizer as BaseToolNormalizer;
6+
7+
class ToolNormalizer extends BaseToolNormalizer
8+
{
9+
public function normalize(mixed $data, ?string $format = null, array $context = []): array
10+
{
11+
$array = parent::normalize($data, $format, $context);
12+
13+
$array['function']['parameters'] ??= ['type' => 'object'];
14+
15+
return $array;
16+
}
17+
}

0 commit comments

Comments
 (0)