Skip to content

Commit

Permalink
chore: tests for simple value classes (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
chr-hertel authored Sep 21, 2024
1 parent e5a02e4 commit d67c5e7
Show file tree
Hide file tree
Showing 14 changed files with 513 additions and 41 deletions.
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.cache
coverage
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ qa-lowest:
vendor/bin/phpstan
vendor/bin/phpunit
git restore composer.lock

coverage:
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage
5 changes: 5 additions & 0 deletions phpstan.dist.neon
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ parameters:
- examples/
- src/
- tests/
ignoreErrors:
-
message: '#no value type specified in iterable type array#'
path: tests/*

46 changes: 45 additions & 1 deletion src/Message/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use PhpLlm\LlmChain\Response\ToolCall;

final class Message
final readonly class Message implements \JsonSerializable
{
/**
* @param ?ToolCall[] $toolCalls
Expand Down Expand Up @@ -46,8 +46,52 @@ public function isSystem(): bool
return Role::System === $this->role;
}

public function isAssistant(): bool
{
return Role::Assistant === $this->role;
}

public function isUser(): bool
{
return Role::User === $this->role;
}

public function isToolCall(): bool
{
return Role::ToolCall === $this->role;
}

public function hasToolCalls(): bool
{
return null !== $this->toolCalls && 0 !== count($this->toolCalls);
}

/**
* @return array{
* role: 'system'|'assistant'|'user'|'tool',
* content: ?string,
* tool_calls?: ToolCall[],
* tool_call_id?: string
* }
*/
public function jsonSerialize(): array
{
$array = [
'role' => $this->role->value,
];

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

if ($this->hasToolCalls() && $this->isToolCall()) {
$array['tool_call_id'] = $this->toolCalls[0]->id;
}

if ($this->hasToolCalls() && $this->isAssistant()) {
$array['tool_calls'] = $this->toolCalls;
}

return $array;
}
}
40 changes: 2 additions & 38 deletions src/Message/MessageBag.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@

/**
* @template-extends \ArrayObject<int, Message>
*
* @phpstan-type MessageBagData array<int, array{
* role: 'system'|'assistant'|'user'|'function',
* content: ?string,
* function_call?: array{name: string, arguments: string},
* name?: string
* }>
*/
final class MessageBag extends \ArrayObject implements \JsonSerializable
{
Expand Down Expand Up @@ -59,39 +52,10 @@ public function prepend(Message $message): self
}

/**
* @return MessageBagData
*/
public function toArray(): array
{
return array_map(
function (Message $message) {
$array = [
'role' => $message->role->value,
];

if (null !== $message->content) {
$array['content'] = $message->content;
}

if (null !== $message->hasToolCalls() && Role::ToolCall === $message->role) {
$array['tool_call_id'] = $message->toolCalls[0]->id;
}

if (null !== $message->hasToolCalls() && Role::Assistant === $message->role) {
$array['tool_calls'] = $message->toolCalls;
}

return $array;
},
$this->getArrayCopy(),
);
}

/**
* @return MessageBagData
* @return Message[]
*/
public function jsonSerialize(): array
{
return $this->toArray();
return $this->getArrayCopy();
}
}
2 changes: 1 addition & 1 deletion src/Response/ToolCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
public function __construct(
public string $id,
public string $name,
public array $arguments,
public array $arguments = [],
) {
}

Expand Down
108 changes: 108 additions & 0 deletions tests/Message/MessageBagTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

declare(strict_types=1);

namespace Message;

use PhpLlm\LlmChain\Message\Message;
use PhpLlm\LlmChain\Message\MessageBag;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Small;
use PHPUnit\Framework\Attributes\UsesClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(MessageBag::class)]
#[UsesClass(Message::class)]
#[Small]
final class MessageBagTest extends TestCase
{
public function testGetSystemMessage(): void
{
$messageBag = new MessageBag(
Message::forSystem('My amazing system prompt.'),
Message::ofAssistant('It is time to sleep.'),
Message::ofUser('Hello, world!'),
);

$systemMessage = $messageBag->getSystemMessage();

self::assertSame('My amazing system prompt.', $systemMessage->content);
}

public function testGetSystemMessageWithoutSystemMessage(): void
{
$messageBag = new MessageBag(
Message::ofAssistant('It is time to sleep.'),
Message::ofUser('Hello, world!'),
);

self::assertNull($messageBag->getSystemMessage());
}

public function testWith(): void
{
$messageBag = new MessageBag(
Message::forSystem('My amazing system prompt.'),
Message::ofAssistant('It is time to sleep.'),
Message::ofUser('Hello, world!'),
);

$newMessage = Message::ofAssistant('It is time to wake up.');
$newMessageBag = $messageBag->with($newMessage);

self::assertCount(3, $messageBag);
self::assertCount(4, $newMessageBag);
self::assertSame('It is time to wake up.', $newMessageBag[3]->content);
}

public function testWithoutSystemMessage(): void
{
$messageBag = new MessageBag(
Message::forSystem('My amazing system prompt.'),
Message::ofAssistant('It is time to sleep.'),
Message::ofUser('Hello, world!'),
);

$newMessageBag = $messageBag->withoutSystemMessage();

self::assertCount(3, $messageBag);
self::assertCount(2, $newMessageBag);
self::assertSame('It is time to sleep.', $newMessageBag[0]->content);
}

public function testPrepend(): void
{
$messageBag = new MessageBag(
Message::ofAssistant('It is time to sleep.'),
Message::ofUser('Hello, world!'),
);

$newMessage = Message::forSystem('My amazing system prompt.');
$newMessageBag = $messageBag->prepend($newMessage);

self::assertCount(2, $messageBag);
self::assertCount(3, $newMessageBag);
self::assertSame('My amazing system prompt.', $newMessageBag[0]->content);
}

public function testJsonSerialize(): void
{
$messageBag = new MessageBag(
Message::forSystem('My amazing system prompt.'),
Message::ofAssistant('It is time to sleep.'),
Message::ofUser('Hello, world!'),
);

$json = json_encode($messageBag);

self::assertJson($json);
self::assertJsonStringEqualsJsonString(
json_encode([
['role' => 'system', 'content' => 'My amazing system prompt.'],
['role' => 'assistant', 'content' => 'It is time to sleep.'],
['role' => 'user', 'content' => 'Hello, world!'],
]),
$json
);
}
}
136 changes: 136 additions & 0 deletions tests/Message/MessageTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php

declare(strict_types=1);

namespace Message;

use PhpLlm\LlmChain\Message\Message;
use PhpLlm\LlmChain\Response\ToolCall;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Small;
use PHPUnit\Framework\Attributes\UsesClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(Message::class)]
#[UsesClass(ToolCall::class)]
#[Small]
final class MessageTest extends TestCase
{
public function testCreateSystemMessage(): void
{
$message = Message::forSystem('My amazing system prompt.');

self::assertSame('My amazing system prompt.', $message->content);
self::assertTrue($message->isSystem());
self::assertFalse($message->isAssistant());
self::assertFalse($message->isUser());
self::assertFalse($message->isToolCall());
self::assertFalse($message->hasToolCalls());
}

public function testCreateAssistantMessage(): void
{
$message = Message::ofAssistant('It is time to sleep.');

self::assertSame('It is time to sleep.', $message->content);
self::assertFalse($message->isSystem());
self::assertTrue($message->isAssistant());
self::assertFalse($message->isUser());
self::assertFalse($message->isToolCall());
self::assertFalse($message->hasToolCalls());
}

public function testCreateAssistantMessageWithToolCalls(): void
{
$toolCalls = [
new ToolCall('call_123456', 'my_tool', ['foo' => 'bar']),
new ToolCall('call_456789', 'my_faster_tool'),
];
$message = Message::ofAssistant(toolCalls: $toolCalls);

self::assertCount(2, $message->toolCalls);
self::assertFalse($message->isSystem());
self::assertTrue($message->isAssistant());
self::assertFalse($message->isUser());
self::assertFalse($message->isToolCall());
self::assertTrue($message->hasToolCalls());
}

public function testCreateUserMessage(): void
{
$message = Message::ofUser('Hi, my name is John.');

self::assertSame('Hi, my name is John.', $message->content);
self::assertFalse($message->isSystem());
self::assertFalse($message->isAssistant());
self::assertTrue($message->isUser());
self::assertFalse($message->isToolCall());
self::assertFalse($message->hasToolCalls());
}

public function testCreateToolCallMessage(): void
{
$toolCall = new ToolCall('call_123456', 'my_tool', ['foo' => 'bar']);
$message = Message::ofToolCall($toolCall, 'Foo bar.');

self::assertSame('Foo bar.', $message->content);
self::assertCount(1, $message->toolCalls);
self::assertFalse($message->isSystem());
self::assertFalse($message->isAssistant());
self::assertFalse($message->isUser());
self::assertTrue($message->isToolCall());
self::assertTrue($message->hasToolCalls());
}

#[DataProvider('provideJsonScenarios')]
public function testJsonSerialize(Message $message, array $expected): void
{
self::assertSame($expected, $message->jsonSerialize());
}

public static function provideJsonScenarios(): array
{
$toolCall1 = new ToolCall('call_123456', 'my_tool', ['foo' => 'bar']);
$toolCall2 = new ToolCall('call_456789', 'my_faster_tool');

return [
'system' => [
Message::forSystem('My amazing system prompt.'),
[
'role' => 'system',
'content' => 'My amazing system prompt.',
],
],
'assistant' => [
Message::ofAssistant('It is time to sleep.'),
[
'role' => 'assistant',
'content' => 'It is time to sleep.',
],
],
'assistant_with_tool_calls' => [
Message::ofAssistant(toolCalls: [$toolCall1, $toolCall2]),
[
'role' => 'assistant',
'tool_calls' => [$toolCall1, $toolCall2],
],
],
'user' => [
Message::ofUser('Hi, my name is John.'),
[
'role' => 'user',
'content' => 'Hi, my name is John.',
],
],
'tool_call' => [
Message::ofToolCall($toolCall1, 'Foo bar.'),
[
'role' => 'tool',
'content' => 'Foo bar.',
'tool_call_id' => 'call_123456',
],
],
];
}
}
Loading

0 comments on commit d67c5e7

Please sign in to comment.