Skip to content
Snippets Groups Projects
Commit d829bca3 authored by Mateusz Charytoniuk's avatar Mateusz Charytoniuk
Browse files

chore: extract string prompt upgrade

parent 130a1508
No related branches found
No related tags found
No related merge requests found
Showing
with 241 additions and 60 deletions
...@@ -20,6 +20,7 @@ grpc_php_plugin_bin = %DM_ROOT%/grpc_php_plugin ...@@ -20,6 +20,7 @@ grpc_php_plugin_bin = %DM_ROOT%/grpc_php_plugin
protoc_bin = /usr/bin/protoc protoc_bin = /usr/bin/protoc
[llamacpp] [llamacpp]
chat_template = mistral_instruct
host = 127.0.0.1 host = 127.0.0.1
port = 8081 port = 8081
......
...@@ -17,14 +17,11 @@ description: > ...@@ -17,14 +17,11 @@ description: >
<div class="homepage__content"> <div class="homepage__content">
<hgroup class="homepage__title"> <hgroup class="homepage__title">
<h1>Resonance</h1> <h1>Resonance</h1>
<h2>PHP Framework That Solves Real-Life Issues</h2> <h2>Build Web Applications with AI and ML Capabilities</h2>
<p> <p>
Designed from the ground up to facilitate interoperability and Designed from the ground up to facilitate interoperability and
messaging between services in your infrastructure and beyond. messaging between services in your infrastructure and beyond.
</p> </p>
<p>
Provides AI capabilities.
</p>
<p> <p>
Takes full advantage of asynchronous PHP. Built on top of Takes full advantage of asynchronous PHP. Built on top of
Swoole. Swoole.
......
<?php
declare(strict_types=1);
namespace Distantmagic\Resonance\BackusNaurFormGrammar;
use Distantmagic\Resonance\BackusNaurFormGrammar;
readonly class InlineGrammar extends BackusNaurFormGrammar
{
public function __construct(
/**
* @var non-empty-string
*/
private string $grammar
) {}
public function getGrammarContent(): string
{
return $this->grammar;
}
}
...@@ -37,7 +37,7 @@ readonly class SubjectActionGrammar extends BackusNaurFormGrammar ...@@ -37,7 +37,7 @@ readonly class SubjectActionGrammar extends BackusNaurFormGrammar
$stringGrammar = <<<'STRING_GRAMMAR' $stringGrammar = <<<'STRING_GRAMMAR'
"\"" ( "\"" (
[^"\\] | [^"\\] |
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) "\\" (["\\/bfnrt] | "u" [0-9a-zA-Z] [0-9a-zA-Z] [0-9a-zA-Z] [0-9a-zA-Z])
)* "\"" )* "\""
STRING_GRAMMAR; STRING_GRAMMAR;
......
...@@ -8,7 +8,6 @@ use CurlHandle; ...@@ -8,7 +8,6 @@ use CurlHandle;
use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\Attribute\Singleton;
use Generator; use Generator;
use JsonSerializable;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use RuntimeException; use RuntimeException;
use Swoole\Coroutine; use Swoole\Coroutine;
...@@ -20,6 +19,7 @@ readonly class LlamaCppClient implements LlamaCppClientInterface ...@@ -20,6 +19,7 @@ readonly class LlamaCppClient implements LlamaCppClientInterface
{ {
public function __construct( public function __construct(
private JsonSerializer $jsonSerializer, private JsonSerializer $jsonSerializer,
private LlmChatHistoryRenderer $llmChatHistoryRenderer,
private LlamaCppConfiguration $llamaCppConfiguration, private LlamaCppConfiguration $llamaCppConfiguration,
private LlamaCppLinkBuilder $llamaCppLinkBuilder, private LlamaCppLinkBuilder $llamaCppLinkBuilder,
private LoggerInterface $logger, private LoggerInterface $logger,
...@@ -27,7 +27,8 @@ readonly class LlamaCppClient implements LlamaCppClientInterface ...@@ -27,7 +27,8 @@ readonly class LlamaCppClient implements LlamaCppClientInterface
public function generateCompletion(LlamaCppCompletionRequest $request): LlamaCppCompletionIterator public function generateCompletion(LlamaCppCompletionRequest $request): LlamaCppCompletionIterator
{ {
$responseChunks = $this->streamResponse($request, '/completion'); $serializedRequest = $this->jsonSerializer->serialize($request->toJsonSerializable($this->llmChatHistoryRenderer));
$responseChunks = $this->streamResponse($serializedRequest, '/completion');
return new LlamaCppCompletionIterator( return new LlamaCppCompletionIterator(
$this->jsonSerializer, $this->jsonSerializer,
...@@ -75,7 +76,8 @@ readonly class LlamaCppClient implements LlamaCppClientInterface ...@@ -75,7 +76,8 @@ readonly class LlamaCppClient implements LlamaCppClientInterface
*/ */
public function generateInfill(LlamaCppInfillRequest $request): Generator public function generateInfill(LlamaCppInfillRequest $request): Generator
{ {
$responseChunks = $this->streamResponse($request, '/infill'); $serializedRequest = $this->jsonSerializer->serialize($request);
$responseChunks = $this->streamResponse($serializedRequest, '/infill');
foreach ($responseChunks as $responseChunk) { foreach ($responseChunks as $responseChunk) {
if ($responseChunk instanceof SwooleChannelIteratorError) { if ($responseChunk instanceof SwooleChannelIteratorError) {
...@@ -166,10 +168,9 @@ readonly class LlamaCppClient implements LlamaCppClientInterface ...@@ -166,10 +168,9 @@ readonly class LlamaCppClient implements LlamaCppClientInterface
/** /**
* @return SwooleChannelIterator<LlamaCppClientResponseChunk> * @return SwooleChannelIterator<LlamaCppClientResponseChunk>
*/ */
private function streamResponse(JsonSerializable $request, string $path): SwooleChannelIterator private function streamResponse(string $requestData, string $path): SwooleChannelIterator
{ {
$channel = new Channel(1); $channel = new Channel(1);
$requestData = json_encode($request);
SwooleCoroutineHelper::mustGo(function () use ($channel, $path, $requestData): void { SwooleCoroutineHelper::mustGo(function () use ($channel, $path, $requestData): void {
$curlHandle = $this->createCurlHandle(); $curlHandle = $this->createCurlHandle();
......
...@@ -7,6 +7,7 @@ namespace Distantmagic\Resonance; ...@@ -7,6 +7,7 @@ namespace Distantmagic\Resonance;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Swoole\Event;
/** /**
* @internal * @internal
...@@ -17,7 +18,33 @@ final class LlamaCppClientTest extends TestCase ...@@ -17,7 +18,33 @@ final class LlamaCppClientTest extends TestCase
{ {
use TestsDependencyInectionContainerTrait; use TestsDependencyInectionContainerTrait;
public function test_request_header_is_parsed(): void protected function tearDown(): void
{
Event::wait();
}
public function test_completion_is_generated(): void
{
$llamaCppClient = self::$container->make(LlamaCppClient::class);
SwooleCoroutineHelper::mustRun(static function () use ($llamaCppClient) {
$completion = $llamaCppClient->generateCompletion(new LlamaCppCompletionRequest(
llmChatHistory: new LlmChatHistory([
new LlmChatMessage('user', 'Who are you? Answer in exactly two words.'),
]),
));
$ret = '';
foreach ($completion as $token) {
$ret .= (string) $token;
}
self::assertNotEmpty($ret);
});
}
public function test_health_status_is_checked(): void
{ {
$llamaCppClient = self::$container->make(LlamaCppClient::class); $llamaCppClient = self::$container->make(LlamaCppClient::class);
......
...@@ -4,23 +4,19 @@ declare(strict_types=1); ...@@ -4,23 +4,19 @@ declare(strict_types=1);
namespace Distantmagic\Resonance; namespace Distantmagic\Resonance;
use JsonSerializable; readonly class LlamaCppCompletionRequest
readonly class LlamaCppCompletionRequest implements JsonSerializable
{ {
public function __construct( public function __construct(
public LlmPromptTemplate $promptTemplate, public LlmChatHistory $llmChatHistory,
public ?BackusNaurFormGrammar $backusNaurFormGrammar = null, public ?BackusNaurFormGrammar $backusNaurFormGrammar = null,
public ?LlmPrompt $llmSystemPrompt = null,
) {} ) {}
public function jsonSerialize(): array public function toJsonSerializable(LlmChatHistoryRenderer $llmChatHistoryRenderer): array
{ {
$parameters = [ $parameters = [
'cache_prompt' => true, 'cache_prompt' => true,
// 'n_predict' => 200, 'n_predict' => 128,
'prompt' => $this->promptTemplate->getPromptTemplateContent(), 'prompt' => $llmChatHistoryRenderer->renderLlmChatHistory($this->llmChatHistory),
'stop' => $this->promptTemplate->getStopWords(),
'stream' => true, 'stream' => true,
]; ];
...@@ -28,12 +24,6 @@ readonly class LlamaCppCompletionRequest implements JsonSerializable ...@@ -28,12 +24,6 @@ readonly class LlamaCppCompletionRequest implements JsonSerializable
$parameters['grammar'] = $this->backusNaurFormGrammar->getGrammarContent(); $parameters['grammar'] = $this->backusNaurFormGrammar->getGrammarContent();
} }
if ($this->llmSystemPrompt) {
$parameters['system_prompt'] = [
'prompt' => $this->llmSystemPrompt->getPromptContent(),
];
}
return $parameters; return $parameters;
} }
} }
...@@ -27,6 +27,7 @@ readonly class LlamaCppConfiguration ...@@ -27,6 +27,7 @@ readonly class LlamaCppConfiguration
#[SensitiveParameter] #[SensitiveParameter]
public string $host, public string $host,
#[SensitiveParameter] #[SensitiveParameter]
public LlmChatTemplateType $llmChatTemplate,
public int $port, public int $port,
#[SensitiveParameter] #[SensitiveParameter]
public string $scheme, public string $scheme,
......
...@@ -4,7 +4,7 @@ declare(strict_types=1); ...@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Distantmagic\Resonance; namespace Distantmagic\Resonance;
use Distantmagic\Resonance\LlmPromptTemplate\MistralInstructChat; use Distantmagic\Resonance\BackusNaurFormGrammar\InlineGrammar;
readonly class LlamaCppExtractString readonly class LlamaCppExtractString
{ {
...@@ -18,14 +18,19 @@ readonly class LlamaCppExtractString ...@@ -18,14 +18,19 @@ readonly class LlamaCppExtractString
): ?string { ): ?string {
$completion = $this->llamaCppClient->generateCompletion( $completion = $this->llamaCppClient->generateCompletion(
new LlamaCppCompletionRequest( new LlamaCppCompletionRequest(
promptTemplate: new MistralInstructChat(<<<PROMPT backusNaurFormGrammar: new InlineGrammar('root ::= [0-9a-zA-Z\" ]+'),
User is about to provide the $subject. llmChatHistory: new LlmChatHistory([
If user provides the $subject, repeat only that $subject, without any additional comment. new LlmChatMessage(
If user did not provide $subject or it is not certain, write the empty string: "" actor: 'system',
message: <<<PROMPT
User input: User is about to provide the $subject.
$input If user provides the $subject, repeat only that $subject, without any additional comment.
PROMPT), If user did not provide $subject or it is not certain, write the empty string: ""
Respond only with provided $subject.
PROMPT
),
new LlmChatMessage('user', $input),
]),
), ),
); );
......
<?php
declare(strict_types=1);
namespace Distantmagic\Resonance;
readonly class LlmChatHistory
{
/**
* @param array<LlmChatMessage> $messages
*/
public function __construct(
public array $messages,
) {}
}
<?php
declare(strict_types=1);
namespace Distantmagic\Resonance;
use Distantmagic\Resonance\Attribute\Singleton;
#[Singleton]
readonly class LlmChatHistoryRenderer
{
public function __construct(
private LlmChatMessageRendererInterface $llmChatMessageRenderer,
) {}
public function renderLlmChatHistory(
LlmChatHistory $llmChatHistory,
): string {
$ret = '';
foreach ($llmChatHistory->messages as $message) {
$ret .= $this->llmChatMessageRenderer->renderLlmChatMessage($message)."\n";
}
return $ret;
}
}
<?php
declare(strict_types=1);
namespace Distantmagic\Resonance;
readonly class LlmChatMessage
{
public function __construct(
public string $actor,
public string $message,
) {}
}
<?php
declare(strict_types=1);
namespace Distantmagic\Resonance;
abstract readonly class LlmChatMessageRenderer implements LlmChatMessageRendererInterface {}
<?php
declare(strict_types=1);
namespace Distantmagic\Resonance\LlmChatMessageRenderer;
use Distantmagic\Resonance\Attribute\Singleton;
use Distantmagic\Resonance\LlmChatMessage;
use Distantmagic\Resonance\LlmChatMessageRenderer;
#[Singleton]
readonly class ChatMLMessageRenderer extends LlmChatMessageRenderer
{
public function renderLlmChatMessage(LlmChatMessage $llmChatMessage): string
{
return <<<FORMATTED
<|im_start|>{$llmChatMessage->actor}
{$llmChatMessage->message}<|im_end|>
FORMATTED;
}
}
<?php
declare(strict_types=1);
namespace Distantmagic\Resonance\LlmChatMessageRenderer;
use Distantmagic\Resonance\LlmChatMessage;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
/**
* @internal
*/
#[CoversClass(ChatMLMessageRenderer::class)]
final class ChatMLMessageRendererTest extends TestCase
{
public function test_chatml_message_is_rendered(): void
{
$chatMessageRenderer = new ChatMLMessageRenderer();
self::assertSame(
<<<'EXPECTED_MESSAGE'
<|im_start|>system
How can I help?<|im_end|>
EXPECTED_MESSAGE,
$chatMessageRenderer->renderLlmChatMessage(new LlmChatMessage(
actor: 'system',
message: 'How can I help?',
))
);
}
}
<?php
declare(strict_types=1);
namespace Distantmagic\Resonance\LlmChatMessageRenderer;
use Distantmagic\Resonance\Attribute\Singleton;
use Distantmagic\Resonance\LlmChatMessage;
use Distantmagic\Resonance\LlmChatMessageRenderer;
#[Singleton]
readonly class MistralInstructMessageRenderer extends LlmChatMessageRenderer
{
public function renderLlmChatMessage(LlmChatMessage $llmChatMessage): string
{
return <<<FORMATTED
[INST]{$llmChatMessage->message}[/INST]
FORMATTED;
}
}
<?php
declare(strict_types=1);
namespace Distantmagic\Resonance;
interface LlmChatMessageRendererInterface
{
public function renderLlmChatMessage(LlmChatMessage $llmChatMessage): string;
}
<?php
declare(strict_types=1);
namespace Distantmagic\Resonance;
enum LlmChatTemplateType: string
{
use EnumValuesTrait;
case ChatML = 'chatml';
case MistralInstruct = 'mistral_instruct';
}
<?php
declare(strict_types=1);
namespace Distantmagic\Resonance\LlmPromptTemplate;
use Distantmagic\Resonance\LlmPromptTemplate;
readonly class HermesChat extends LlmPromptTemplate
{
public function __construct(private string $prompt) {}
public function getPromptTemplateContent(): string
{
return sprintf(
'<|im_start|%s<|im_end|>',
$this->prompt,
);
}
public function getStopWords(): array
{
return ['<|im_start|>', '<|im_end|>'];
}
}
...@@ -12,11 +12,13 @@ use Distantmagic\Resonance\Constraint\NumberConstraint; ...@@ -12,11 +12,13 @@ use Distantmagic\Resonance\Constraint\NumberConstraint;
use Distantmagic\Resonance\Constraint\ObjectConstraint; use Distantmagic\Resonance\Constraint\ObjectConstraint;
use Distantmagic\Resonance\Constraint\StringConstraint; use Distantmagic\Resonance\Constraint\StringConstraint;
use Distantmagic\Resonance\LlamaCppConfiguration; use Distantmagic\Resonance\LlamaCppConfiguration;
use Distantmagic\Resonance\LlmChatTemplateType;
use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider;
/** /**
* @template-extends ConfigurationProvider<LlamaCppConfiguration, array{ * @template-extends ConfigurationProvider<LlamaCppConfiguration, array{
* api_key: null|non-empty-string, * api_key: null|non-empty-string,
* chat_template: non-empty-string,
* completion_token_timeout: float, * completion_token_timeout: float,
* host: non-empty-string, * host: non-empty-string,
* port: int, * port: int,
...@@ -31,6 +33,7 @@ final readonly class LlamaCppConfigurationProvider extends ConfigurationProvider ...@@ -31,6 +33,7 @@ final readonly class LlamaCppConfigurationProvider extends ConfigurationProvider
return new ObjectConstraint( return new ObjectConstraint(
properties: [ properties: [
'api_key' => (new StringConstraint())->default(null), 'api_key' => (new StringConstraint())->default(null),
'chat_template' => new EnumConstraint(LlmChatTemplateType::values()),
'completion_token_timeout' => (new NumberConstraint())->default(1.0), 'completion_token_timeout' => (new NumberConstraint())->default(1.0),
'host' => new StringConstraint(), 'host' => new StringConstraint(),
'port' => new IntegerConstraint(), 'port' => new IntegerConstraint(),
...@@ -51,6 +54,7 @@ final readonly class LlamaCppConfigurationProvider extends ConfigurationProvider ...@@ -51,6 +54,7 @@ final readonly class LlamaCppConfigurationProvider extends ConfigurationProvider
completionTokenTimeout: $validatedData['completion_token_timeout'], completionTokenTimeout: $validatedData['completion_token_timeout'],
host: $validatedData['host'], host: $validatedData['host'],
port: $validatedData['port'], port: $validatedData['port'],
llmChatTemplate: LlmChatTemplateType::from($validatedData['chat_template']),
scheme: $validatedData['scheme'], scheme: $validatedData['scheme'],
); );
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment