Skip to content

Introduce UID support for Messages to enable efficient frontend tracking #344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
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
28 changes: 28 additions & 0 deletions src/Platform/Message/AssistantMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace PhpLlm\LlmChain\Platform\Message;

use PhpLlm\LlmChain\Platform\Response\ToolCall;
use Symfony\Component\Uid\Uuid;

/**
* @author Denis Zunke <[email protected]>
Expand All @@ -25,6 +26,33 @@ public function getRole(): Role
return Role::Assistant;
}

public function getId(): Uuid
{
// Generate deterministic UUID based on content, role, and tool calls
$toolCallsData = '';
if ($this->toolCalls !== null) {
$toolCallsData = serialize(array_map(
static fn (ToolCall $toolCall) => [
'id' => $toolCall->id,
'name' => $toolCall->name,
'arguments' => $toolCall->arguments,
],
$this->toolCalls
));
}

$data = sprintf('assistant:%s:%s', $this->content ?? '', $toolCallsData);

return Uuid::v5(self::getNamespace(), $data);
}

private static function getNamespace(): Uuid
{
// Use a fixed namespace UUID for the LLM Chain message system
// This ensures deterministic IDs across application runs
return Uuid::fromString('6ba7b810-9dad-11d1-80b4-00c04fd430c8');
}

public function hasToolCalls(): bool
{
return null !== $this->toolCalls && 0 !== \count($this->toolCalls);
Expand Down
72 changes: 72 additions & 0 deletions src/Platform/Message/MessageBag.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace PhpLlm\LlmChain\Platform\Message;

use Symfony\Component\Uid\Uuid;

/**
* @final
*
Expand Down Expand Up @@ -102,6 +104,76 @@ public function containsImage(): bool
return false;
}

/**
* Get all messages that come after a message with the specified ID.
* If the ID is not found, returns all messages.
*
* @return list<MessageInterface>
*/
public function messagesAfterId(Uuid $id): array
{
$found = false;
$messagesAfter = [];

foreach ($this->messages as $message) {
if ($found) {
$messagesAfter[] = $message;
} elseif ($message->getId()->equals($id)) {
$found = true;
}
}

// If ID not found, return all messages
return $found ? $messagesAfter : $this->messages;
}

/**
* Get messages newer than (excluding) the specified ID.
*
* @return self
*/
public function messagesNewerThan(Uuid $id): self
{
$messagesAfter = $this->messagesAfterId($id);

return new self(...$messagesAfter);
}

/**
* Find a message by its ID.
*/
public function findById(Uuid $id): ?MessageInterface
{
foreach ($this->messages as $message) {
if ($message->getId()->equals($id)) {
return $message;
}
}

return null;
}

/**
* Check if a message with the specified ID exists in the bag.
*/
public function hasMessageWithId(Uuid $id): bool
{
return $this->findById($id) !== null;
}

/**
* Get all IDs in the message bag in order.
*
* @return list<Uuid>
*/
public function getIds(): array
{
return array_map(
static fn (MessageInterface $message) => $message->getId(),
$this->messages
);
}

public function count(): int
{
return \count($this->messages);
Expand Down
32 changes: 32 additions & 0 deletions src/Platform/Message/MessageBagInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace PhpLlm\LlmChain\Platform\Message;

use Symfony\Component\Uid\Uuid;

/**
* @author Oskar Stark <[email protected]>
*/
Expand All @@ -29,4 +31,34 @@ public function prepend(MessageInterface $message): self;
public function containsAudio(): bool;

public function containsImage(): bool;

/**
* Get all messages that come after a message with the specified ID.
* If the ID is not found, returns all messages.
*
* @return list<MessageInterface>
*/
public function messagesAfterId(Uuid $id): array;

/**
* Get messages newer than (excluding) the specified ID.
*/
public function messagesNewerThan(Uuid $id): self;

/**
* Find a message by its ID.
*/
public function findById(Uuid $id): ?MessageInterface;

/**
* Check if a message with the specified ID exists in the bag.
*/
public function hasMessageWithId(Uuid $id): bool;

/**
* Get all IDs in the message bag in order.
*
* @return list<Uuid>
*/
public function getIds(): array;
}
4 changes: 4 additions & 0 deletions src/Platform/Message/MessageInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@

namespace PhpLlm\LlmChain\Platform\Message;

use Symfony\Component\Uid\Uuid;

/**
* @author Denis Zunke <[email protected]>
*/
interface MessageInterface
{
public function getRole(): Role;

public function getId(): Uuid;
}
17 changes: 17 additions & 0 deletions src/Platform/Message/SystemMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace PhpLlm\LlmChain\Platform\Message;

use Symfony\Component\Uid\Uuid;

/**
* @author Denis Zunke <[email protected]>
*/
Expand All @@ -17,4 +19,19 @@ public function getRole(): Role
{
return Role::System;
}

public function getId(): Uuid
{
// Generate deterministic UUID based on content and role
$data = sprintf('system:%s', $this->content);

return Uuid::v5(self::getNamespace(), $data);
}

private static function getNamespace(): Uuid
{
// Use a fixed namespace UUID for the LLM Chain message system
// This ensures deterministic IDs across application runs
return Uuid::fromString('6ba7b810-9dad-11d1-80b4-00c04fd430c8');
}
}
17 changes: 17 additions & 0 deletions src/Platform/Message/ToolCallMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace PhpLlm\LlmChain\Platform\Message;

use PhpLlm\LlmChain\Platform\Response\ToolCall;
use Symfony\Component\Uid\Uuid;

/**
* @author Denis Zunke <[email protected]>
Expand All @@ -21,4 +22,20 @@ public function getRole(): Role
{
return Role::ToolCall;
}

public function getId(): Uuid
{
// Generate deterministic UUID based on tool call and content
$toolCallData = sprintf('%s:%s:%s', $this->toolCall->id, $this->toolCall->name, serialize($this->toolCall->arguments));
$data = sprintf('toolcall:%s:%s', $toolCallData, $this->content);

return Uuid::v5(self::getNamespace(), $data);
}

private static function getNamespace(): Uuid
{
// Use a fixed namespace UUID for the LLM Chain message system
// This ensures deterministic IDs across application runs
return Uuid::fromString('6ba7b810-9dad-11d1-80b4-00c04fd430c8');
}
}
17 changes: 17 additions & 0 deletions src/Platform/Message/UserMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PhpLlm\LlmChain\Platform\Message\Content\ContentInterface;
use PhpLlm\LlmChain\Platform\Message\Content\Image;
use PhpLlm\LlmChain\Platform\Message\Content\ImageUrl;
use Symfony\Component\Uid\Uuid;

/**
* @author Denis Zunke <[email protected]>
Expand All @@ -30,6 +31,22 @@ public function getRole(): Role
return Role::User;
}

public function getId(): Uuid
{
// Generate deterministic UUID based on content and role
$contentData = serialize($this->content);
$data = sprintf('user:%s', $contentData);

return Uuid::v5(self::getNamespace(), $data);
}

private static function getNamespace(): Uuid
{
// Use a fixed namespace UUID for the LLM Chain message system
// This ensures deterministic IDs across application runs
return Uuid::fromString('6ba7b810-9dad-11d1-80b4-00c04fd430c8');
}

public function hasAudioContent(): bool
{
foreach ($this->content as $content) {
Expand Down
Loading
Loading