Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
vendor/
.php-cs-fixer.cache
.phpunit.result.cache
composer.lock
81 changes: 81 additions & 0 deletions examples/chat-with-tools.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php declare(strict_types=1);

require __DIR__.'/../vendor/autoload.php';

use JDecool\OllamaClient\ClientBuilder;
use JDecool\OllamaClient\Client\Message;
use JDecool\OllamaClient\Client\Tool;
use JDecool\OllamaClient\Client\ToolFunction;
use JDecool\OllamaClient\Client\Request\ChatRequest;

$builder = new ClientBuilder();
$client = $builder->create();

$request = new ChatRequest(
model: 'llama3.1',
messages: $messages = [
new Message('user', 'What is the weather in San Francisco?'),
],
tools: [
new Tool(
type: Tool::TYPE_FUNCTION,
function: new ToolFunction(
name: 'get_current_weather',
description: 'Get the current weather for a location',
parameters: [
'type' => 'object',
'properties' => [
'location' => [
'type' => 'string',
'description' => 'The city and state, e.g., San Francisco, CA',
],
'unit' => [
'type' => 'string',
'enum' => ['celsius', 'fahrenheit'],
'description' => 'The unit of temperature',
],
],
'required' => ['location'],
],
),
),
],
);

$response = $client->chat($request);
var_dump($response);

// Check if the model wants to use a tool
if ($response->message->toolCalls !== null) {
foreach ($response->message->toolCalls as $toolCall) {
echo "Function: {$toolCall->function->name}\n";
echo "Arguments: " . json_encode($toolCall->function->arguments, JSON_PRETTY_PRINT) . "\n";

// Simulate executing the function
$functionResult = match($toolCall->function->name) {
'get_current_weather' => json_encode([
'temperature' => 72,
'unit' => $toolCall->function->arguments['unit'] ?? 'fahrenheit',
'condition' => 'sunny',
]),
default => json_encode(['error' => 'Unknown function']),
};

// Add assistant message with tool calls
$messages[] = $response->message;

// Add tool response message
$messages[] = new Message(
role: 'tool',
content: (string) $functionResult,
);
}

// Send the conversation back with tool results
$finalRequest = new ChatRequest(
model: 'llama3.1',
messages: $messages,
);

var_dump($client->chat($finalRequest));
}
4 changes: 2 additions & 2 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ parameters:
- examples
- src
- tests

checkMissingIterableValueType: false
ignoreErrors:
- identifier: missingType.iterableValue
35 changes: 34 additions & 1 deletion src/Client/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,46 @@ class Message extends Request
{
public static function fromArray(array $data): self
{
return new self($data['role'], $data['content'], /* $data['images'] */);
$toolCalls = null;
if (isset($data['tool_calls'])) {
$toolCalls = array_map(
fn (array $toolCall) => ToolCall::fromArray($toolCall),
$data['tool_calls']
);
}

return new self(
role: $data['role'],
content: $data['content'] ?? '',
toolCalls: $toolCalls,
);
}

/**
* @param ToolCall[]|null $toolCalls
*/
public function __construct(
public readonly string $role,
public readonly string $content,
public readonly ?array $toolCalls = null,
// public readonly array $images = [],
) {
}

public function toArray(): array
{
$data = [
'role' => $this->role,
'content' => $this->content,
];

if ($this->toolCalls !== null) {
$data['tool_calls'] = array_map(
fn (ToolCall $toolCall) => $toolCall->toArray(),
$this->toolCalls
);
}

return $data;
}
}
27 changes: 27 additions & 0 deletions src/Client/Request/ChatRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace JDecool\OllamaClient\Client\Request;

use JDecool\OllamaClient\Client\Request;
use JDecool\OllamaClient\Client\Tool;

class ChatRequest extends Request
{
Expand All @@ -11,14 +12,40 @@ public static function fromArray(array $data): self
return new self(
model: $data['model'],
messages: $data['messages'] ?? [],
tools: isset($data['tools']) ? array_map(Tool::fromArray(...), $data['tools']) : null,
format: $data['format'] ?? null,
);
}

/**
* @param Tool[]|null $tools
*/
public function __construct(
public readonly string $model,
public readonly array $messages = [],
public readonly ?array $tools = null,
public readonly ?string $format = null,
) {
}

public function toArray(): array
{
$data = [
'model' => $this->model,
'messages' => $this->messages,
];

if ($this->tools !== null) {
$data['tools'] = array_map(
static fn (Tool $tool) => $tool->toArray(),
$this->tools
);
}

if ($this->format !== null) {
$data['format'] = $this->format;
}

return $data;
}
}
30 changes: 30 additions & 0 deletions src/Client/Tool.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php declare(strict_types=1);

namespace JDecool\OllamaClient\Client;

class Tool extends Request
{
public const TYPE_FUNCTION = 'function';

public static function fromArray(array $data): self
{
return new self(
type: $data['type'],
function: ToolFunction::fromArray($data['function']),
);
}

public function __construct(
public readonly string $type,
public readonly ToolFunction $function,
) {
}

public function toArray(): array
{
return [
'type' => $this->type,
'function' => $this->function->toArray(),
];
}
}
25 changes: 25 additions & 0 deletions src/Client/ToolCall.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php declare(strict_types=1);

namespace JDecool\OllamaClient\Client;

class ToolCall extends Request
{
public static function fromArray(array $data): self
{
return new self(
function: ToolCallFunction::fromArray($data['function']),
);
}

public function __construct(
public readonly ToolCallFunction $function,
) {
}

public function toArray(): array
{
return [
'function' => $this->function->toArray(),
];
}
}
28 changes: 28 additions & 0 deletions src/Client/ToolCallFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php declare(strict_types=1);

namespace JDecool\OllamaClient\Client;

class ToolCallFunction extends Request
{
public static function fromArray(array $data): self
{
return new self(
name: $data['name'],
arguments: $data['arguments'],
);
}

public function __construct(
public readonly string $name,
public readonly array $arguments,
) {
}

public function toArray(): array
{
return [
'name' => $this->name,
'arguments' => $this->arguments,
];
}
}
31 changes: 31 additions & 0 deletions src/Client/ToolFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php declare(strict_types=1);

namespace JDecool\OllamaClient\Client;

class ToolFunction extends Request
{
public static function fromArray(array $data): self
{
return new self(
name: $data['name'],
description: $data['description'],
parameters: $data['parameters'],
);
}

public function __construct(
public readonly string $name,
public readonly string $description,
public readonly array $parameters,
) {
}

public function toArray(): array
{
return [
'name' => $this->name,
'description' => $this->description,
'parameters' => $this->parameters,
];
}
}
6 changes: 3 additions & 3 deletions src/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function __construct(
) {
}

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

Expand All @@ -30,7 +30,7 @@ public function request(string $method, string $uri, string $body = null): strin
/**
* @return Generator<string>
*/
public function stream(string $method, string $uri, string $body = null): Generator
public function stream(string $method, string $uri, ?string $body = null): Generator
{
$response = $this->executeRequest($method, $uri, $body);

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

private function executeRequest(string $method, string $uri, string $body = null): ResponseInterface
private function executeRequest(string $method, string $uri, ?string $body = null): ResponseInterface
{
$this->logger->debug('HTTP Request: {method} {uri}', [
'method' => $method,
Expand Down
Loading