Skip to content

Commit

Permalink
Implemented modelfile to string conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
evowareio committed Apr 11, 2024
1 parent 4bd55bf commit aa6ddac
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 48 deletions.
11 changes: 11 additions & 0 deletions src/Exceptions/OllamaClientException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Evoware\OllamaPHP\Exceptions;

class OllamaClientException extends OllamaException
{
public function __construct(string $message = 'Malformed request data.', int $code = 400, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
11 changes: 11 additions & 0 deletions src/Exceptions/OllamaServerException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Evoware\OllamaPHP\Exceptions;

class OllamaServerException extends OllamaException
{
public function __construct(string $message = 'Request to Ollama API failed.', int $code = 400, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
1 change: 0 additions & 1 deletion src/Models/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class Model

public function __construct(array $data)
{
// validate all fields in minimalist way
foreach (['name', 'model', 'size', 'digest', 'modified_at', 'details'] as $key) {
if (!isset($data[$key]) || empty($data[$key])) {
throw new \Exception('Missing required field: ' . $key);
Expand Down
43 changes: 43 additions & 0 deletions src/Models/ModelFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,49 @@ public function toJson(int $options = 0): string
return json_encode($this->toArray(), $options);
}


public function __toString(): string
{
$lines = [];

if ($this->template) {
$lines[] = "TEMPLATE {$this->template}";
}

if ($this->parent) {
$lines[] = "FROM {$this->parent}";
}

foreach (self::VALID_PARAMETERS as $parameter => $value) {
if (isset($this->parameters[$parameter])) {
$lines[] = "PARAMETER {$parameter} {$this->parameters[$parameter]}";
}
}

if ($this->system) {
$lines[] = "SYSTEM {$this->system}";
}

if ($this->adapter) {
$lines[] = "ADAPTER {$this->adapter}";
}

if ($this->license) {
$lines[] = "LICENSE {$this->license}";
}

foreach ($this->chatHistory as $message) {
$lines[] = "MESSAGE {$message['role']} {$message['message']}";
}

foreach ($this->details as $key => $value) {
$lines[] = "DETAIL {$key} {$value}";
}

return implode("\n", $lines);
}


/** Getters */
public function getParent(): string
{
Expand Down
49 changes: 24 additions & 25 deletions src/OllamaClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@

namespace Evoware\OllamaPHP;

use Evoware\OllamaPHP\Models\ModelFile;
use Evoware\OllamaPHP\Repositories\ModelRepository;
use Evoware\OllamaPHP\Responses\ChatCompletionResponse;
use Evoware\OllamaPHP\Responses\CompletionResponse;
use Evoware\OllamaPHP\Responses\EmbeddingResponse;
use Evoware\OllamaPHP\Traits\MakesHttpRequests;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Client;
use Evoware\OllamaPHP\Traits\ValidatesFields;
use Evoware\OllamaPHP\Traits\MakesHttpRequests;
use Evoware\OllamaPHP\Responses\EmbeddingResponse;
use Evoware\OllamaPHP\Responses\CompletionResponse;
use Evoware\OllamaPHP\Responses\ChatCompletionResponse;
use Evoware\OllamaPHP\Repositories\ModelRepository;
use Evoware\OllamaPHP\Models\ModelFile;
use Evoware\OllamaPHP\Exceptions\OllamaClientException;

/**
* OllamaClient is the main class for interacting with the Ollama API.
*/
class OllamaClient
{
use MakesHttpRequests;
use ValidatesFields;

private ModelRepository $modelRepository;

Expand All @@ -35,7 +38,7 @@ public function __construct(?ClientInterface $httpClient = null, array $clientOp
*/
public function getModelRepository(): ModelRepository
{
if (! isset($this->modelRepository)) {
if (!isset($this->modelRepository)) {
$this->modelRepository = new ModelRepository($this->client);
}

Expand All @@ -48,14 +51,11 @@ public function getModelRepository(): ModelRepository
* @param string $prompt The prompt for generating the completion.
* @param string|null $modelName (Optional) The name of the model to use for completion.
* @param array $modelOptions (Optional) Additional modelOptions for generating the completion.
* @throws OllamaClientException
*/
public function generateCompletion(string $prompt, ?string $modelName = null, array $modelOptions = []): CompletionResponse
{
if (! empty($modelName)) {
$this->modelName = $modelName;
} elseif (isset($modelOptions['model'])) {
$this->modelName = $modelOptions['model'];
}
$this->validate(['model'], array_merge(['model' => $modelName ?? $this->modelName], $modelOptions));

$jsonData = array_merge([
'model' => $this->modelName,
Expand All @@ -70,10 +70,14 @@ public function generateCompletion(string $prompt, ?string $modelName = null, ar
* Generate a chat completion response.
*
* @param array $messages list of chat messages
* @param array $modelOptions modelOptions for the model
* @param string|null $modelName (Optional) The name of the model to use for completion.
* @param array|null $modelOptions modelOptions for the model
* @throws OllamaClientException
*/
public function generateChatCompletion(array $messages = [], ?string $modelName = null, array $modelOptions = []): ChatCompletionResponse
{
$this->validate(['model'], array_merge(['model' => $modelName ?? $this->modelName], $modelOptions));

$jsonData = array_merge([
'model' => $this->modelName,
'messages' => $messages,
Expand All @@ -88,18 +92,13 @@ public function generateChatCompletion(array $messages = [], ?string $modelName
*
* @param string $prompt The prompt for which to generate embeddings.
* @param string|null $modelName The name of the model to use.
* @param array $modelOptions Additional clientOptions for the model.
* @param array|null $modelOptions Additional clientOptions for the model.
* @return EmbeddingResponse The response containing the generated embeddings.
* @throws OllamaClientException
*/
public function generateEmbeddings(string|array $prompt, ?string $modelName = null, array $modelOptions = []): EmbeddingResponse
{
if (! empty($this->modelName)) {
// Override model if provided on runtime
$this->modelName = $modelName;
} elseif (isset($modelOptions['model'])) {
// Use model name specified in modelOptions
$this->modelName = $modelOptions['model'];
}
$this->validate(['model', 'prompt'], array_merge(['model' => $modelName, 'prompt' => $prompt], $modelOptions));

if (is_array($prompt)) {
$prompt = implode("\n", $prompt);
Expand All @@ -124,9 +123,9 @@ public function getHttpClient(): ClientInterface
* @param string $modelName The name of the model.
* @return $this
*/
public function setModel(string $modelName): self
public function setModel(?string $modelName): self
{
$this->modelName = $modelName;
$this->modelName = $modelName ?? $this->modelName;

return $this;
}
Expand All @@ -145,7 +144,7 @@ public function getModel(): string
*/
public function model(?string $modelName = null): ModelRepository
{
if (! empty($modelName)) {
if (!empty($modelName)) {
$this->modelName = $modelName;
}

Expand Down
11 changes: 8 additions & 3 deletions src/Repositories/ModelRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

namespace Evoware\OllamaPHP\Repositories;

use Evoware\OllamaPHP\DataObjects\Model;
use Evoware\OllamaPHP\Models\ModelFile;
use Evoware\OllamaPHP\Traits\MakesHttpRequests;
use GuzzleHttp\ClientInterface;
use Evoware\OllamaPHP\Traits\ValidatesFields;
use Evoware\OllamaPHP\Traits\MakesHttpRequests;
use Evoware\OllamaPHP\Models\ModelFile;
use Evoware\OllamaPHP\DataObjects\Model;

class ModelRepository
{
use MakesHttpRequests;
use ValidatesFields;

protected ClientInterface $client;

Expand All @@ -25,8 +27,10 @@ public function fromModelFile(string $modelName, ModelFile $modelfile)

public function create($name, ?ModelFile $modelFile = null)
{
$this->validate(['name'], ['name' => $name]);
$response = $this->post('models', [
'name' => $name,
'modelfile' => isset($modelfile) ? (string) $modelFile : null,
'stream' => false, // TODO introduce parameter once streaming is supported
]);

Expand All @@ -53,6 +57,7 @@ public function list(): array
*/
public function info(string $modelName): Model
{
$this->validate(['name'], ['name' => $modelName]);
$response = $this->get('show', [
'name' => $modelName,
'stream' => false, // TODO introduce parameter once streaming is supported
Expand Down
23 changes: 15 additions & 8 deletions src/traits/MakesHttpRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

namespace Evoware\OllamaPHP\Traits;

use Evoware\OllamaPHP\Exceptions\OllamaException;
use Evoware\OllamaPHP\Responses\ChatCompletionResponse;
use Evoware\OllamaPHP\Responses\CompletionResponse;
use Evoware\OllamaPHP\Responses\EmbeddingResponse;
use Evoware\OllamaPHP\Responses\OllamaResponse;
use Evoware\OllamaPHP\Responses\OllamaResponseInterface;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\ClientInterface;
use Evoware\OllamaPHP\Responses\OllamaResponseInterface;
use Evoware\OllamaPHP\Responses\OllamaResponse;
use Evoware\OllamaPHP\Responses\EmbeddingResponse;
use Evoware\OllamaPHP\Responses\CompletionResponse;
use Evoware\OllamaPHP\Responses\ChatCompletionResponse;
use Evoware\OllamaPHP\Exceptions\OllamaServerException;
use Evoware\OllamaPHP\Exceptions\OllamaException;
use Evoware\OllamaPHP\Exceptions\OllamaClientException;

trait MakesHttpRequests
{
Expand All @@ -26,9 +30,12 @@ protected function request(string $method, string $endpoint, array $data = []):
$response = $this->client->request($method, $endpoint, [
'json' => $data,
]);
} catch (ClientException $e) {
throw new OllamaClientException($e->getMessage(), $e->getCode(), $e);
} catch (ServerException $e) {
throw new OllamaServerException($e->getMessage(), $e->getCode(), $e);
} catch (\Exception $e) {
error_log($e->getMessage());
throw new OllamaException('Request to Ollama API failed', $e->getCode(), $e);
throw new OllamaException($e->getMessage(), $e->getCode(), $e);
}

switch ($endpoint) {
Expand Down
17 changes: 17 additions & 0 deletions src/traits/ValidatesFields.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Evoware\OllamaPHP\Traits;

use Evoware\OllamaPHP\Exceptions\OllamaClientException;

trait ValidatesFields
{
protected function validate(array $fields, array $data): void
{
foreach ($fields as $field) {
if (!array_key_exists($field, $data) || empty($data[$field])) {
throw new OllamaClientException("Missing required parameter: $field");
}
}
}
}
51 changes: 41 additions & 10 deletions tests/IntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

namespace Evoware\OllamaPHP\Tests;

use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\ClientException;
use Evoware\OllamaPHP\Responses\CompletionResponse;
use Evoware\OllamaPHP\OllamaClient;
use Evoware\OllamaPHP\Exceptions\OllamaServerException;
use Evoware\OllamaPHP\Exceptions\OllamaClientException;

class IntegrationTest extends TestCase
{
Expand All @@ -20,41 +24,68 @@ public function testGenerateCompletionSuccess()
])],
]);
$this->ollamaClient = new OllamaClient($httpClient);

$model = 'mistral:7b';
$prompt = 'Hello, world!';

$response = $this->ollamaClient->setModel($model)->generateCompletion($prompt);

$this->assertInstanceOf(CompletionResponse::class, $response);

$this->assertEquals(200, $response->getHttpResponse()->getStatusCode());

$this->assertNotEmpty($response->getResponse());
$this->assertEquals('Generated completion goes here.', $response->getResponse());
$this->assertTrue($response->isDone());
}

public function testGenerateCompletionFailure()
public function testServerErrorHandling()
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Request to Ollama API failed');
$this->expectException(ServerException::class);
$this->expectException(OllamaServerException::class);
$this->expectExceptionMessage('Internal Server Error');

$httpClient = $this->mockHttpClient([
[500, ['Content-Type' => 'application/json'], json_encode(['error' => 'Internal Server Error'])],
]);
$this->ollamaClient = new OllamaClient($httpClient);

$model = 'mistral:7b';
$prompt = 'Hello, world!';

$response = $this->ollamaClient->setModel($model)->generateCompletion($prompt);

$this->assertInstanceOf(CompletionResponse::class, $response);

$this->assertEquals(500, $response->getStatusCode());
$this->assertEmpty($response->getResponse());
$this->assertFalse($response->isSuccessful());
}

public function testClientErrorHandling()
{
$this->expectException(ClientException::class);
$this->expectException(OllamaClientException::class);
$this->expectExceptionMessage('Missing required parameter: model');

$httpClient = $this->mockHttpClient([
[420, ['Content-Type' => 'application/json'], json_encode(['error' => 'The model parameter cannot be empty.'])],
]);
$this->ollamaClient = new OllamaClient($httpClient);
$prompt = 'Hello, world!';
$response = $this->ollamaClient->generateCompletion($prompt);

$this->assertInstanceOf(CompletionResponse::class, $response);
$this->assertEquals(420, $response->getStatusCode());
$this->assertEmpty($response->getResponse());
$this->assertFalse($response->isSuccessful());
}

public function testRequiredParameterValidation()
{
$this->expectException(OllamaClientException::class);
$httpClient = $this->mockHttpClient([
[
400,
['Content-Type' => 'application/json'],
json_encode(['error' => 'Missing prompt parameter.']),
],
]);
$ollamaClient = new OllamaClient($httpClient);

$ollamaClient->generateEmbeddings('', modelName: 'nomic-embed-text', modelOptions: ['stream' => false]);
}
}
Loading

0 comments on commit aa6ddac

Please sign in to comment.