Skip to content

Commit 7ec20d8

Browse files
lubianachr-hertel
authored andcommitted
[Platform] Add lmstudio
1 parent 8ffcb19 commit 7ec20d8

File tree

19 files changed

+734
-1
lines changed

19 files changed

+734
-1
lines changed

examples/.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,6 @@ MARIADB_URI=pdo-mysql://[email protected]:3309/my_database
7878
# Meilisearch
7979
MEILISEARCH_HOST=http://127.0.0.1:7700
8080
MEILISEARCH_API_KEY=changeMe
81+
82+
# For using LMStudio
83+
LMSTUDIO_HOST_URL=http://127.0.0.1:1234

examples/lmstudio/chat.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Platform\Bridge\LMStudio\Completions;
14+
use Symfony\AI\Platform\Bridge\LMStudio\PlatformFactory;
15+
use Symfony\AI\Platform\Message\Message;
16+
use Symfony\AI\Platform\Message\MessageBag;
17+
use Symfony\Component\Dotenv\Dotenv;
18+
19+
require_once dirname(__DIR__).'/vendor/autoload.php';
20+
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
21+
22+
if (!isset($_SERVER['LMSTUDIO_HOST_URL'])) {
23+
echo 'Please set the LMSTUDIO_HOST_URL environment variable.'.\PHP_EOL;
24+
exit(1);
25+
}
26+
27+
$platform = PlatformFactory::create($_SERVER['LMSTUDIO_HOST_URL']);
28+
$model = new Completions('gemma-3-4b-it-qat');
29+
30+
$agent = new Agent($platform, $model);
31+
$messages = new MessageBag(
32+
Message::forSystem('You are a pirate and you write funny.'),
33+
Message::ofUser('What is the Symfony framework?'),
34+
);
35+
$response = $agent->call($messages, [
36+
'max_tokens' => 500, // specific options just for this call
37+
]);
38+
39+
echo $response->getContent().\PHP_EOL;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Platform\Bridge\LMStudio\Completions;
14+
use Symfony\AI\Platform\Bridge\LMStudio\PlatformFactory;
15+
use Symfony\AI\Platform\Capability;
16+
use Symfony\AI\Platform\Message\Content\Image;
17+
use Symfony\AI\Platform\Message\Message;
18+
use Symfony\AI\Platform\Message\MessageBag;
19+
use Symfony\Component\Dotenv\Dotenv;
20+
21+
require_once dirname(__DIR__).'/vendor/autoload.php';
22+
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
23+
24+
if (!isset($_SERVER['LMSTUDIO_HOST_URL'])) {
25+
echo 'Please set the LMSTUDIO_HOST_URL environment variable.'.\PHP_EOL;
26+
exit(1);
27+
}
28+
29+
$platform = PlatformFactory::create($_SERVER['LMSTUDIO_HOST_URL']);
30+
$model = new Completions(
31+
name: 'gemma-3-4b-it-qat',
32+
capabilities: [...Completions::DEFAULT_CAPABILITIES, Capability::INPUT_IMAGE]
33+
);
34+
35+
$agent = new Agent($platform, $model);
36+
$messages = new MessageBag(
37+
Message::forSystem('You are an image analyzer bot that helps identify the content of images.'),
38+
Message::ofUser(
39+
'Describe the image as a comedian would do it.',
40+
Image::fromFile(dirname(__DIR__, 2).'/fixtures/image.jpg'),
41+
),
42+
);
43+
$response = $agent->call($messages);
44+
45+
echo $response->getContent().\PHP_EOL;

src/ai-bundle/config/options.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@
5757
->scalarNode('api_key')->isRequired()->end()
5858
->end()
5959
->end()
60+
->arrayNode('lmstudio')
61+
->children()
62+
->scalarNode('host_url')->defaultValue('http://127.0.0.1:1234')->end()
63+
->end()
64+
->end()
6065
->end()
6166
->end()
6267
->arrayNode('agent')

src/ai-bundle/src/AIBundle.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory as AnthropicPlatformFactory;
3333
use Symfony\AI\Platform\Bridge\Azure\OpenAI\PlatformFactory as AzureOpenAIPlatformFactory;
3434
use Symfony\AI\Platform\Bridge\Google\PlatformFactory as GooglePlatformFactory;
35+
use Symfony\AI\Platform\Bridge\LMStudio\PlatformFactory as LMStudioPlatformFactory;
3536
use Symfony\AI\Platform\Bridge\Mistral\PlatformFactory as MistralPlatformFactory;
3637
use Symfony\AI\Platform\Bridge\OpenAI\PlatformFactory as OpenAIPlatformFactory;
3738
use Symfony\AI\Platform\Bridge\OpenRouter\PlatformFactory as OpenRouterPlatformFactory;
@@ -256,6 +257,21 @@ private function processPlatformConfig(string $type, array $platform, ContainerB
256257
return;
257258
}
258259

260+
if ('lmstudio' === $type) {
261+
$platformId = 'symfony_ai.platform.lmstudio';
262+
$definition = (new Definition(Platform::class))
263+
->setFactory(LMStudioPlatformFactory::class.'::create')
264+
->setAutowired(true)
265+
->setLazy(true)
266+
->addTag('proxy', ['interface' => PlatformInterface::class])
267+
->setArguments(['$hostUrl' => $platform['host_url']])
268+
->addTag('symfony_ai.platform');
269+
270+
$container->setDefinition($platformId, $definition);
271+
272+
return;
273+
}
274+
259275
throw new InvalidArgumentException(\sprintf('Platform "%s" is not supported for configuration via bundle at this point.', $type));
260276
}
261277

src/ai-bundle/tests/DependencyInjection/AIBundleTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ private function getFullConfig(): array
7272
'openrouter' => [
7373
'api_key' => 'openrouter_key_full',
7474
],
75+
'lmstudio' => [
76+
'host_url' => 'http://127.0.0.1:1234',
77+
],
7578
],
7679
'agent' => [
7780
'my_chat_agent' => [

src/platform/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ CHANGELOG
2020
- Voyage AI (specialized embeddings)
2121
- HuggingFace (extensive model support with multiple tasks)
2222
- TransformersPHP (local PHP-based transformer models)
23+
- LM Studio (local model hosting)
2324
* Add comprehensive message system with role-based messaging:
2425
- `UserMessage` for user inputs with multi-modal content
2526
- `SystemMessage` for system instructions
@@ -54,4 +55,4 @@ CHANGELOG
5455
* Add temperature and parameter controls
5556
* Add exception handling with specific error types
5657
* Add support for embeddings generation across multiple providers
57-
* Add response promises for async operations
58+
* Add response promises for async operations

src/platform/doc/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ usually defined by the specific models and their documentation.
8686
* **Other Models**
8787
* `OpenAI's Dall·E`_ with `OpenAI`_ as Platform
8888
* `OpenAI's Whisper`_ with `OpenAI`_ and `Azure`_ as Platform
89+
* `LM Studio Catalog`_ and `HuggingFace`_ Models with `LM Studio`_ as Platform.
8990
* All models provided by `HuggingFace`_ can be listed with a command in the examples folder,
9091
and also filtered, e.g. ``php examples/huggingface/_model-listing.php --provider=hf-inference --task=object-detection``
9192

@@ -332,3 +333,5 @@ which can be useful to speed up the processing::
332333
.. _`Embeddings with Mistral`: https://github.com/symfony/ai/blob/main/examples/mistral/embeddings.php
333334
.. _`Parallel GPT Calls`: https://github.com/symfony/ai/blob/main/examples/misc/parallel-chat-gpt.php
334335
.. _`Parallel Embeddings Calls`: https://github.com/symfony/ai/blob/main/examples/misc/parallel-embeddings.php
336+
.. _`LM Studio`: https://lmstudio.ai/
337+
.. _`LM Studio Catalog`: https://lmstudio.ai/models
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\Platform\Bridge\LMStudio;
13+
14+
use Symfony\AI\Platform\Capability;
15+
use Symfony\AI\Platform\Model;
16+
17+
/**
18+
* @author André Lubian <[email protected]>
19+
*/
20+
class Completions extends Model
21+
{
22+
public const DEFAULT_CAPABILITIES = [
23+
Capability::INPUT_MESSAGES,
24+
Capability::OUTPUT_TEXT,
25+
Capability::OUTPUT_STREAMING,
26+
];
27+
28+
public function __construct(
29+
string $name,
30+
array $options = ['temperature' => 0.7],
31+
array $capabilities = self::DEFAULT_CAPABILITIES,
32+
) {
33+
parent::__construct($name, $capabilities, $options);
34+
}
35+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\Platform\Bridge\LMStudio\Completions;
13+
14+
use Symfony\AI\Platform\Bridge\LMStudio\Completions;
15+
use Symfony\AI\Platform\Model;
16+
use Symfony\AI\Platform\ModelClientInterface as PlatformResponseFactory;
17+
use Symfony\Component\HttpClient\EventSourceHttpClient;
18+
use Symfony\Contracts\HttpClient\HttpClientInterface;
19+
use Symfony\Contracts\HttpClient\ResponseInterface;
20+
21+
/**
22+
* @author André Lubian <[email protected]>
23+
*/
24+
final readonly class ModelClient implements PlatformResponseFactory
25+
{
26+
private EventSourceHttpClient $httpClient;
27+
28+
public function __construct(
29+
HttpClientInterface $httpClient,
30+
private string $hostUrl,
31+
) {
32+
$this->httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
33+
}
34+
35+
public function supports(Model $model): bool
36+
{
37+
return $model instanceof Completions;
38+
}
39+
40+
public function request(Model $model, array|string $payload, array $options = []): ResponseInterface
41+
{
42+
return $this->httpClient->request('POST', \sprintf('%s/v1/chat/completions', $this->hostUrl), [
43+
'json' => array_merge($options, $payload),
44+
]);
45+
}
46+
}

0 commit comments

Comments
 (0)