Skip to content

Commit cbdf762

Browse files
authored
Merge pull request #9 from jdecool/feat-tool-support
Add tool support
2 parents 9966242 + 3627d34 commit cbdf762

File tree

10 files changed

+262
-6
lines changed

10 files changed

+262
-6
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
vendor/
22
.php-cs-fixer.cache
3+
.phpunit.result.cache
34
composer.lock

examples/chat-with-tools.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php declare(strict_types=1);
2+
3+
require __DIR__.'/../vendor/autoload.php';
4+
5+
use JDecool\OllamaClient\ClientBuilder;
6+
use JDecool\OllamaClient\Client\Message;
7+
use JDecool\OllamaClient\Client\Tool;
8+
use JDecool\OllamaClient\Client\ToolFunction;
9+
use JDecool\OllamaClient\Client\Request\ChatRequest;
10+
11+
$builder = new ClientBuilder();
12+
$client = $builder->create();
13+
14+
$request = new ChatRequest(
15+
model: 'llama3.1',
16+
messages: $messages = [
17+
new Message('user', 'What is the weather in San Francisco?'),
18+
],
19+
tools: [
20+
new Tool(
21+
type: Tool::TYPE_FUNCTION,
22+
function: new ToolFunction(
23+
name: 'get_current_weather',
24+
description: 'Get the current weather for a location',
25+
parameters: [
26+
'type' => 'object',
27+
'properties' => [
28+
'location' => [
29+
'type' => 'string',
30+
'description' => 'The city and state, e.g., San Francisco, CA',
31+
],
32+
'unit' => [
33+
'type' => 'string',
34+
'enum' => ['celsius', 'fahrenheit'],
35+
'description' => 'The unit of temperature',
36+
],
37+
],
38+
'required' => ['location'],
39+
],
40+
),
41+
),
42+
],
43+
);
44+
45+
$response = $client->chat($request);
46+
var_dump($response);
47+
48+
// Check if the model wants to use a tool
49+
if ($response->message->toolCalls !== null) {
50+
foreach ($response->message->toolCalls as $toolCall) {
51+
echo "Function: {$toolCall->function->name}\n";
52+
echo "Arguments: " . json_encode($toolCall->function->arguments, JSON_PRETTY_PRINT) . "\n";
53+
54+
// Simulate executing the function
55+
$functionResult = match($toolCall->function->name) {
56+
'get_current_weather' => json_encode([
57+
'temperature' => 72,
58+
'unit' => $toolCall->function->arguments['unit'] ?? 'fahrenheit',
59+
'condition' => 'sunny',
60+
]),
61+
default => json_encode(['error' => 'Unknown function']),
62+
};
63+
64+
// Add assistant message with tool calls
65+
$messages[] = $response->message;
66+
67+
// Add tool response message
68+
$messages[] = new Message(
69+
role: 'tool',
70+
content: (string) $functionResult,
71+
);
72+
}
73+
74+
// Send the conversation back with tool results
75+
$finalRequest = new ChatRequest(
76+
model: 'llama3.1',
77+
messages: $messages,
78+
);
79+
80+
var_dump($client->chat($finalRequest));
81+
}

phpstan.neon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ parameters:
44
- examples
55
- src
66
- tests
7-
8-
checkMissingIterableValueType: false
7+
ignoreErrors:
8+
- identifier: missingType.iterableValue

src/Client/Message.php

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,46 @@ class Message extends Request
66
{
77
public static function fromArray(array $data): self
88
{
9-
return new self($data['role'], $data['content'], /* $data['images'] */);
9+
$toolCalls = null;
10+
if (isset($data['tool_calls'])) {
11+
$toolCalls = array_map(
12+
fn (array $toolCall) => ToolCall::fromArray($toolCall),
13+
$data['tool_calls']
14+
);
15+
}
16+
17+
return new self(
18+
role: $data['role'],
19+
content: $data['content'] ?? '',
20+
toolCalls: $toolCalls,
21+
);
1022
}
1123

24+
/**
25+
* @param ToolCall[]|null $toolCalls
26+
*/
1227
public function __construct(
1328
public readonly string $role,
1429
public readonly string $content,
30+
public readonly ?array $toolCalls = null,
1531
// public readonly array $images = [],
1632
) {
1733
}
34+
35+
public function toArray(): array
36+
{
37+
$data = [
38+
'role' => $this->role,
39+
'content' => $this->content,
40+
];
41+
42+
if ($this->toolCalls !== null) {
43+
$data['tool_calls'] = array_map(
44+
fn (ToolCall $toolCall) => $toolCall->toArray(),
45+
$this->toolCalls
46+
);
47+
}
48+
49+
return $data;
50+
}
1851
}

src/Client/Request/ChatRequest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace JDecool\OllamaClient\Client\Request;
44

55
use JDecool\OllamaClient\Client\Request;
6+
use JDecool\OllamaClient\Client\Tool;
67

78
class ChatRequest extends Request
89
{
@@ -11,14 +12,40 @@ public static function fromArray(array $data): self
1112
return new self(
1213
model: $data['model'],
1314
messages: $data['messages'] ?? [],
15+
tools: isset($data['tools']) ? array_map(Tool::fromArray(...), $data['tools']) : null,
1416
format: $data['format'] ?? null,
1517
);
1618
}
1719

20+
/**
21+
* @param Tool[]|null $tools
22+
*/
1823
public function __construct(
1924
public readonly string $model,
2025
public readonly array $messages = [],
26+
public readonly ?array $tools = null,
2127
public readonly ?string $format = null,
2228
) {
2329
}
30+
31+
public function toArray(): array
32+
{
33+
$data = [
34+
'model' => $this->model,
35+
'messages' => $this->messages,
36+
];
37+
38+
if ($this->tools !== null) {
39+
$data['tools'] = array_map(
40+
static fn (Tool $tool) => $tool->toArray(),
41+
$this->tools
42+
);
43+
}
44+
45+
if ($this->format !== null) {
46+
$data['format'] = $this->format;
47+
}
48+
49+
return $data;
50+
}
2451
}

src/Client/Tool.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace JDecool\OllamaClient\Client;
4+
5+
class Tool extends Request
6+
{
7+
public const TYPE_FUNCTION = 'function';
8+
9+
public static function fromArray(array $data): self
10+
{
11+
return new self(
12+
type: $data['type'],
13+
function: ToolFunction::fromArray($data['function']),
14+
);
15+
}
16+
17+
public function __construct(
18+
public readonly string $type,
19+
public readonly ToolFunction $function,
20+
) {
21+
}
22+
23+
public function toArray(): array
24+
{
25+
return [
26+
'type' => $this->type,
27+
'function' => $this->function->toArray(),
28+
];
29+
}
30+
}

src/Client/ToolCall.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace JDecool\OllamaClient\Client;
4+
5+
class ToolCall extends Request
6+
{
7+
public static function fromArray(array $data): self
8+
{
9+
return new self(
10+
function: ToolCallFunction::fromArray($data['function']),
11+
);
12+
}
13+
14+
public function __construct(
15+
public readonly ToolCallFunction $function,
16+
) {
17+
}
18+
19+
public function toArray(): array
20+
{
21+
return [
22+
'function' => $this->function->toArray(),
23+
];
24+
}
25+
}

src/Client/ToolCallFunction.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace JDecool\OllamaClient\Client;
4+
5+
class ToolCallFunction extends Request
6+
{
7+
public static function fromArray(array $data): self
8+
{
9+
return new self(
10+
name: $data['name'],
11+
arguments: $data['arguments'],
12+
);
13+
}
14+
15+
public function __construct(
16+
public readonly string $name,
17+
public readonly array $arguments,
18+
) {
19+
}
20+
21+
public function toArray(): array
22+
{
23+
return [
24+
'name' => $this->name,
25+
'arguments' => $this->arguments,
26+
];
27+
}
28+
}

src/Client/ToolFunction.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace JDecool\OllamaClient\Client;
4+
5+
class ToolFunction extends Request
6+
{
7+
public static function fromArray(array $data): self
8+
{
9+
return new self(
10+
name: $data['name'],
11+
description: $data['description'],
12+
parameters: $data['parameters'],
13+
);
14+
}
15+
16+
public function __construct(
17+
public readonly string $name,
18+
public readonly string $description,
19+
public readonly array $parameters,
20+
) {
21+
}
22+
23+
public function toArray(): array
24+
{
25+
return [
26+
'name' => $this->name,
27+
'description' => $this->description,
28+
'parameters' => $this->parameters,
29+
];
30+
}
31+
}

src/Http.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public function __construct(
1515
) {
1616
}
1717

18-
public function request(string $method, string $uri, string $body = null): string
18+
public function request(string $method, string $uri, ?string $body = null): string
1919
{
2020
$response = $this->executeRequest($method, $uri, $body);
2121

@@ -30,7 +30,7 @@ public function request(string $method, string $uri, string $body = null): strin
3030
/**
3131
* @return Generator<string>
3232
*/
33-
public function stream(string $method, string $uri, string $body = null): Generator
33+
public function stream(string $method, string $uri, ?string $body = null): Generator
3434
{
3535
$response = $this->executeRequest($method, $uri, $body);
3636

@@ -49,7 +49,7 @@ public function stream(string $method, string $uri, string $body = null): Genera
4949
}
5050
}
5151

52-
private function executeRequest(string $method, string $uri, string $body = null): ResponseInterface
52+
private function executeRequest(string $method, string $uri, ?string $body = null): ResponseInterface
5353
{
5454
$this->logger->debug('HTTP Request: {method} {uri}', [
5555
'method' => $method,

0 commit comments

Comments
 (0)