Skip to content

Commit a7c4b62

Browse files
committed
feat(platform): Ollama structured ouput support
1 parent 11ef98f commit a7c4b62

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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\Agent\StructuredOutput\AgentProcessor;
14+
use Symfony\AI\Fixtures\StructuredOutput\MathReasoning;
15+
use Symfony\AI\Platform\Bridge\Ollama\Ollama;
16+
use Symfony\AI\Platform\Bridge\Ollama\PlatformFactory;
17+
use Symfony\AI\Platform\Message\Message;
18+
use Symfony\AI\Platform\Message\MessageBag;
19+
20+
require_once dirname(__DIR__).'/bootstrap.php';
21+
22+
$platform = PlatformFactory::create(env('OLLAMA_HOST_URL'), http_client());
23+
$model = new Ollama();
24+
25+
$processor = new AgentProcessor();
26+
$agent = new Agent($platform, $model, [$processor], [$processor], logger());
27+
$messages = new MessageBag(
28+
Message::forSystem('You are a helpful math tutor. Guide the user through the solution step by step.'),
29+
Message::ofUser('how can I solve 8x + 7 = -23'),
30+
);
31+
$result = $agent->call($messages, ['output_structure' => MathReasoning::class]);
32+
33+
dump($result->getContent());

src/platform/src/Bridge/Ollama/Ollama.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class Ollama extends Model
4545
'/./' => [
4646
Capability::INPUT_MESSAGES,
4747
Capability::OUTPUT_TEXT,
48+
Capability::OUTPUT_STRUCTURED,
4849
],
4950
'/^llama\D*3(\D*\d+)/' => [
5051
Capability::TOOL_CALLING,

src/platform/src/Bridge/Ollama/OllamaClient.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ private function doCompletionRequest(array|string $payload, array $options = [])
6363
// Revert Ollama's default streaming behavior
6464
$options['stream'] ??= false;
6565

66+
if (array_key_exists('response_format', $options) && array_key_exists('json_schema', $options['response_format'])) {
67+
$options['format'] = $options['response_format']['json_schema']['schema'];
68+
unset($options['response_format']);
69+
}
70+
6671
return new RawHttpResult($this->httpClient->request('POST', \sprintf('%s/api/chat', $this->hostUrl), [
6772
'headers' => ['Content-Type' => 'application/json'],
6873
'json' => array_merge($options, $payload),
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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 Bridge\Ollama;
13+
14+
use PHPUnit\Framework\Attributes\CoversClass;
15+
use PHPUnit\Framework\Attributes\UsesClass;
16+
use PHPUnit\Framework\TestCase;
17+
use Symfony\AI\Platform\Bridge\Ollama\Ollama;
18+
use Symfony\AI\Platform\Bridge\Ollama\OllamaClient;
19+
use Symfony\AI\Platform\Model;
20+
use Symfony\Component\HttpClient\MockHttpClient;
21+
use Symfony\Component\HttpClient\Response\JsonMockResponse;
22+
23+
#[CoversClass(OllamaClient::class)]
24+
#[UsesClass(Ollama::class)]
25+
#[UsesClass(Model::class)]
26+
final class OllamaClientTest extends TestCase
27+
{
28+
public function testSupportsModel()
29+
{
30+
$client = new OllamaClient(new MockHttpClient(), 'http://localhost:1234');
31+
32+
$this->assertTrue($client->supports(new Ollama()));
33+
$this->assertFalse($client->supports(new Model('any-model')));
34+
}
35+
36+
public function testOutputStructureIsSupported()
37+
{
38+
$httpClient = new MockHttpClient([
39+
new JsonMockResponse([
40+
'capabilities' => ['completion', 'tools'],
41+
]),
42+
new JsonMockResponse([
43+
'model' => 'foo',
44+
'response' => [
45+
'age' => 22,
46+
'available' => true,
47+
],
48+
'done' => true,
49+
]),
50+
], 'http://127.0.0.1:1234');
51+
52+
$client = new OllamaClient($httpClient, 'http://127.0.0.1:1234');
53+
$response = $client->request(new Ollama(), [
54+
'messages' => [
55+
[
56+
'role' => 'user',
57+
'content' => 'Ollama is 22 years old and is busy saving the world. Respond using JSON',
58+
],
59+
],
60+
'model' => 'llama3.2',
61+
], [
62+
'response_format' => [
63+
'type' => 'json_schema',
64+
'json_schema' => [
65+
'name' => 'clock',
66+
'strict' => true,
67+
'schema' => [
68+
'type' => 'object',
69+
'properties' => [
70+
'age' => ['type' => 'integer'],
71+
'available' => ['type' => 'boolean'],
72+
],
73+
'required' => ['age', 'available'],
74+
'additionalProperties' => false,
75+
],
76+
],
77+
]
78+
]);
79+
80+
$this->assertSame(2, $httpClient->getRequestsCount());
81+
$this->assertSame([
82+
'model' => 'foo',
83+
'response' => [
84+
'age' => 22,
85+
'available' => true,
86+
],
87+
'done' => true,
88+
], $response->getData());
89+
}
90+
}

0 commit comments

Comments
 (0)