Skip to content

Commit 737be86

Browse files
committed
feat: add support for mistral
1 parent 8e6a7d0 commit 737be86

16 files changed

+648
-0
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

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,11 @@ $embeddings = new Embeddings();
6666
* [Meta's Llama](https://www.llama.com/) with [Azure](https://learn.microsoft.com/azure/machine-learning/how-to-deploy-models-llama), [Ollama](https://ollama.com/) and [Replicate](https://replicate.com/) as Platform
6767
* [Google's Gemini](https://gemini.google.com/) with [Google](https://ai.google.dev/) and [OpenRouter](https://www.openrouter.com/) as Platform
6868
* [DeepSeek's R1](https://www.deepseek.com/) with [OpenRouter](https://www.openrouter.com/) as Platform
69+
* [Mistral's Mistral](https://www.mistral.ai/) with [Mistral](https://www.mistral.ai/) as Platform
6970
* Embeddings Models
7071
* [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
7172
* [Voyage's Embeddings](https://docs.voyageai.com/docs/embeddings) with [Voyage](https://www.voyageai.com/) as Platform
73+
* [Mistral Embed](https://www.mistral.ai/) with [Mistral](https://www.mistral.ai/) as Platform
7274
* Other Models
7375
* [OpenAI's Dall·E](https://platform.openai.com/docs/guides/image-generation) with [OpenAI](https://platform.openai.com/docs/overview) as Platform
7476
* [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
@@ -135,6 +137,7 @@ $response = $chain->call($messages, [
135137
1. [Meta's Llama with Replicate](examples/replicate/chat-llama.php)
136138
1. [Google's Gemini with Google](examples/google/chat.php)
137139
1. [Google's Gemini with OpenRouter](examples/openrouter/chat-gemini.php)
140+
1. [Mistral's Mistral with Mistral](examples/mistral/chat-mistral.php)
138141

139142
### Tools
140143

@@ -543,6 +546,7 @@ needs to be used.
543546
544547
1. [Streaming Claude](examples/anthropic/stream.php)
545548
1. [Streaming GPT](examples/openai/stream.php)
549+
1. [Streaming Mistral](examples/mistral/stream.php)
546550
547551
### Image Processing
548552
@@ -619,6 +623,7 @@ dump($vectors[0]->getData()); // Array of float values
619623
620624
1. [OpenAI's Emebddings](examples/openai/embeddings.php)
621625
1. [Voyage's Embeddings](examples/voyage/embeddings.php)
626+
1. [Mistral's Embed](examples/mistral/embeddings.php)
622627

623628
### Parallel Platform Calls
624629

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/embeddings.php

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
use PhpLlm\LlmChain\Bridge\Mistral\Embeddings;
4+
use PhpLlm\LlmChain\Bridge\Mistral\PlatformFactory;
5+
use PhpLlm\LlmChain\Model\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+
$embeddings = new Embeddings();
18+
19+
$response = $platform->request($embeddings, <<<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

+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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Bridge\Mistral\Embeddings;
6+
7+
use PhpLlm\LlmChain\Bridge\Mistral\Embeddings;
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 Embeddings;
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/embeddings', [
34+
'auth_bearer' => $this->apiKey,
35+
'headers' => [
36+
'Content-Type' => 'application/json',
37+
],
38+
'json' => array_merge($options, [
39+
'model' => $model->getName(),
40+
'input' => $input,
41+
]),
42+
]);
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Bridge\Mistral\Embeddings;
6+
7+
use PhpLlm\LlmChain\Bridge\Mistral\Embeddings;
8+
use PhpLlm\LlmChain\Document\Vector;
9+
use PhpLlm\LlmChain\Exception\RuntimeException;
10+
use PhpLlm\LlmChain\Model\Model;
11+
use PhpLlm\LlmChain\Model\Response\VectorResponse;
12+
use PhpLlm\LlmChain\Platform\ResponseConverter as PlatformResponseConverter;
13+
use Symfony\Contracts\HttpClient\ResponseInterface;
14+
15+
final readonly class ResponseConverter implements PlatformResponseConverter
16+
{
17+
public function supports(Model $model, object|array|string $input): bool
18+
{
19+
return $model instanceof Embeddings;
20+
}
21+
22+
public function convert(ResponseInterface $response, array $options = []): VectorResponse
23+
{
24+
$data = $response->toArray(false);
25+
26+
if (200 !== $response->getStatusCode()) {
27+
throw new RuntimeException(sprintf('Unexpected response code %d: %s', $response->getStatusCode(), $response->getContent(false)));
28+
}
29+
30+
if (!isset($data['data'])) {
31+
throw new RuntimeException('Response does not contain data');
32+
}
33+
34+
return new VectorResponse(
35+
...\array_map(
36+
static fn (array $item): Vector => new Vector($item['embedding']),
37+
$data['data']
38+
),
39+
);
40+
}
41+
}

0 commit comments

Comments
 (0)