diff --git a/Makefile b/Makefile index c7d6cf2145afce5aa33688512dfff601f8c18f1f..645d4b4f03a6185493916ce64bcb2f4014148eff 100644 --- a/Makefile +++ b/Makefile @@ -107,6 +107,7 @@ esbuild: $(CSS_SOURCES) node_modules --loader:.ttf=file \ --loader:.webp=file \ --metafile=esbuild-meta-docs.json \ + --minify \ --outdir=$(ESBUILD_TARGET_DIRECTORY) \ --sourcemap \ --splitting \ diff --git a/composer.json b/composer.json index b4887bcebb5a42ee182306b01f839514c90719e1..afd5f890a5b9481b566d534f498992cd8db16c4f 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,12 @@ "type": "library", "license": "MIT", "autoload": { + "files": [ + "src/helpers/coroutineMustGetContext.php", + "src/helpers/coroutineMustGo.php", + "src/helpers/coroutineMustRun.php", + "src/helpers/generatorGetReturn.php" + ], "psr-4": { "Distantmagic\\Docs\\": "app/", "Distantmagic\\Resonance\\": "src/" diff --git a/composer.lock b/composer.lock index 3f8829223a7187f58f947e9b477e52cc790fa031..081003b3c38e7b17230b1e3e6d44a83dfbfa6d85 100644 --- a/composer.lock +++ b/composer.lock @@ -344,16 +344,16 @@ }, { "name": "doctrine/collections", - "version": "2.2.1", + "version": "2.2.2", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "420480fc085bc65f3c956af13abe8e7546f94813" + "reference": "d8af7f248c74f195f7347424600fd9e17b57af59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/420480fc085bc65f3c956af13abe8e7546f94813", - "reference": "420480fc085bc65f3c956af13abe8e7546f94813", + "url": "https://api.github.com/repos/doctrine/collections/zipball/d8af7f248c74f195f7347424600fd9e17b57af59", + "reference": "d8af7f248c74f195f7347424600fd9e17b57af59", "shasum": "" }, "require": { @@ -410,7 +410,7 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/2.2.1" + "source": "https://github.com/doctrine/collections/tree/2.2.2" }, "funding": [ { @@ -426,7 +426,7 @@ "type": "tidelift" } ], - "time": "2024-03-05T22:28:45+00:00" + "time": "2024-04-18T06:56:21+00:00" }, { "name": "doctrine/dbal", @@ -1016,16 +1016,16 @@ }, { "name": "doctrine/orm", - "version": "3.1.1", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "9c560713925ac5859342e6ff370c4c997acf2fd4" + "reference": "f79d166a4e844beb9389f23bdb44abdbf58cec38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/9c560713925ac5859342e6ff370c4c997acf2fd4", - "reference": "9c560713925ac5859342e6ff370c4c997acf2fd4", + "url": "https://api.github.com/repos/doctrine/orm/zipball/f79d166a4e844beb9389f23bdb44abdbf58cec38", + "reference": "f79d166a4e844beb9389f23bdb44abdbf58cec38", "shasum": "" }, "require": { @@ -1098,9 +1098,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.1.1" + "source": "https://github.com/doctrine/orm/tree/3.1.2" }, - "time": "2024-03-21T11:37:52+00:00" + "time": "2024-04-15T14:20:40+00:00" }, { "name": "doctrine/persistence", @@ -2057,16 +2057,16 @@ }, { "name": "hyperf/collection", - "version": "v3.1.15", + "version": "v3.1.19", "source": { "type": "git", "url": "https://github.com/hyperf/collection.git", - "reference": "d0ac957987a704c8b2a16de4333b81f1f56a724a" + "reference": "fe9adae0fe88ec3330505c0cea758feee886d327" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/collection/zipball/d0ac957987a704c8b2a16de4333b81f1f56a724a", - "reference": "d0ac957987a704c8b2a16de4333b81f1f56a724a", + "url": "https://api.github.com/repos/hyperf/collection/zipball/fe9adae0fe88ec3330505c0cea758feee886d327", + "reference": "fe9adae0fe88ec3330505c0cea758feee886d327", "shasum": "" }, "require": { @@ -2116,7 +2116,7 @@ "type": "open_collective" } ], - "time": "2024-03-23T11:28:51+00:00" + "time": "2024-04-13T07:45:03+00:00" }, { "name": "hyperf/conditionable", @@ -2361,20 +2361,20 @@ }, { "name": "hyperf/engine", - "version": "v2.10.6", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/hyperf/engine.git", - "reference": "5617f01e40980723512f510cbacfd9a4671aaa5b" + "reference": "26e0b65fc2a63a00266e7124e221c6f3fb2c8e95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/engine/zipball/5617f01e40980723512f510cbacfd9a4671aaa5b", - "reference": "5617f01e40980723512f510cbacfd9a4671aaa5b", + "url": "https://api.github.com/repos/hyperf/engine/zipball/26e0b65fc2a63a00266e7124e221c6f3fb2c8e95", + "reference": "26e0b65fc2a63a00266e7124e221c6f3fb2c8e95", "shasum": "" }, "require": { - "hyperf/engine-contract": "~1.9.0", + "hyperf/engine-contract": "~1.10.0", "php": ">=8.0" }, "conflict": { @@ -2398,7 +2398,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10-dev" + "dev-master": "2.11-dev" }, "hyperf": { "config": "Hyperf\\Engine\\ConfigProvider" @@ -2425,7 +2425,7 @@ ], "support": { "issues": "https://github.com/hyperf/engine/issues", - "source": "https://github.com/hyperf/engine/tree/v2.10.6" + "source": "https://github.com/hyperf/engine/tree/v2.11.0" }, "funding": [ { @@ -2437,20 +2437,20 @@ "type": "open_collective" } ], - "time": "2024-04-09T11:34:48+00:00" + "time": "2024-04-17T13:36:28+00:00" }, { "name": "hyperf/engine-contract", - "version": "v1.9.1", + "version": "v1.10.1", "source": { "type": "git", "url": "https://github.com/hyperf/engine-contract.git", - "reference": "fec2e45f35404b2e5b4c3eaf1b0dce67d60771eb" + "reference": "2714a8ba6d6b916e5bd373ff680df9569a4c9eef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/engine-contract/zipball/fec2e45f35404b2e5b4c3eaf1b0dce67d60771eb", - "reference": "fec2e45f35404b2e5b4c3eaf1b0dce67d60771eb", + "url": "https://api.github.com/repos/hyperf/engine-contract/zipball/2714a8ba6d6b916e5bd373ff680df9569a4c9eef", + "reference": "2714a8ba6d6b916e5bd373ff680df9569a4c9eef", "shasum": "" }, "require": { @@ -2492,7 +2492,7 @@ ], "support": { "issues": "https://github.com/hyperf/engine-contract/issues", - "source": "https://github.com/hyperf/engine-contract/tree/v1.9.1" + "source": "https://github.com/hyperf/engine-contract/tree/v1.10.1" }, "funding": [ { @@ -2504,7 +2504,7 @@ "type": "open_collective" } ], - "time": "2023-12-15T07:37:14+00:00" + "time": "2024-04-17T13:34:51+00:00" }, { "name": "hyperf/event", @@ -2826,16 +2826,16 @@ }, { "name": "hyperf/stringable", - "version": "v3.1.15", + "version": "v3.1.17", "source": { "type": "git", "url": "https://github.com/hyperf/stringable.git", - "reference": "6cbd6f220d833c3f2c28c8263dccffee48021dcb" + "reference": "4997554ca2450c485446704adb9ed7c18ea93c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/stringable/zipball/6cbd6f220d833c3f2c28c8263dccffee48021dcb", - "reference": "6cbd6f220d833c3f2c28c8263dccffee48021dcb", + "url": "https://api.github.com/repos/hyperf/stringable/zipball/4997554ca2450c485446704adb9ed7c18ea93c70", + "reference": "4997554ca2450c485446704adb9ed7c18ea93c70", "shasum": "" }, "require": { @@ -2893,7 +2893,7 @@ "type": "open_collective" } ], - "time": "2024-03-23T11:28:51+00:00" + "time": "2024-04-03T03:21:14+00:00" }, { "name": "hyperf/support", @@ -3151,16 +3151,16 @@ }, { "name": "lcobucci/jwt", - "version": "5.2.0", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "0ba88aed12c04bd2ed9924f500673f32b67a6211" + "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/0ba88aed12c04bd2ed9924f500673f32b67a6211", - "reference": "0ba88aed12c04bd2ed9924f500673f32b67a6211", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/08071d8d2c7f4b00222cc4b1fb6aa46990a80f83", + "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83", "shasum": "" }, "require": { @@ -3208,7 +3208,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.2.0" + "source": "https://github.com/lcobucci/jwt/tree/5.3.0" }, "funding": [ { @@ -3220,7 +3220,7 @@ "type": "patreon" } ], - "time": "2023-11-20T21:17:42+00:00" + "time": "2024-04-11T23:07:54+00:00" }, { "name": "league/commonmark", @@ -7806,22 +7806,22 @@ }, { "name": "twig/cache-extra", - "version": "v3.8.0", + "version": "v3.9.0", "source": { "type": "git", "url": "https://github.com/twigphp/cache-extra.git", - "reference": "1e78b0e9268dd9742c480a2279eb246652a9c452" + "reference": "317ec85c3831ef08003364acfa291c160f393e5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/cache-extra/zipball/1e78b0e9268dd9742c480a2279eb246652a9c452", - "reference": "1e78b0e9268dd9742c480a2279eb246652a9c452", + "url": "https://api.github.com/repos/twigphp/cache-extra/zipball/317ec85c3831ef08003364acfa291c160f393e5d", + "reference": "317ec85c3831ef08003364acfa291c160f393e5d", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/cache": "^5.0|^6.0|^7.0", - "twig/twig": "^3.0" + "symfony/cache": "^5.4|^6.4|^7.0", + "twig/twig": "^3.9" }, "require-dev": { "symfony/phpunit-bridge": "^6.4|^7.0" @@ -7855,7 +7855,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/cache-extra/tree/v3.8.0" + "source": "https://github.com/twigphp/cache-extra/tree/v3.9.0" }, "funding": [ { @@ -7867,34 +7867,41 @@ "type": "tidelift" } ], - "time": "2023-11-21T14:02:01+00:00" + "time": "2024-02-10T08:52:03+00:00" }, { "name": "twig/twig", - "version": "v3.8.0", + "version": "v3.9.3", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" + "reference": "a842d75fed59cdbcbd3a3ad7fb9eb768fc350d58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", - "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a842d75fed59cdbcbd3a3ad7fb9eb768fc350d58", + "reference": "a842d75fed59cdbcbd3a3ad7fb9eb768fc350d58", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3", "symfony/polyfill-php80": "^1.22" }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, "type": "library", "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { "Twig\\": "src/" } @@ -7927,7 +7934,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.8.0" + "source": "https://github.com/twigphp/Twig/tree/v3.9.3" }, "funding": [ { @@ -7939,7 +7946,7 @@ "type": "tidelift" } ], - "time": "2023-11-21T18:54:41+00:00" + "time": "2024-04-18T11:59:33+00:00" }, { "name": "webmozart/assert", @@ -8769,16 +8776,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.1.1", + "version": "11.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "816cb6070af901c0548aa5f18f2132fd8a6e4ade" + "reference": "51e342a0bc987e0ea8418105d0711f08ae116de3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/816cb6070af901c0548aa5f18f2132fd8a6e4ade", - "reference": "816cb6070af901c0548aa5f18f2132fd8a6e4ade", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/51e342a0bc987e0ea8418105d0711f08ae116de3", + "reference": "51e342a0bc987e0ea8418105d0711f08ae116de3", "shasum": "" }, "require": { @@ -8849,7 +8856,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.1.1" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.1.2" }, "funding": [ { @@ -8865,7 +8872,7 @@ "type": "tidelift" } ], - "time": "2024-04-06T06:20:21+00:00" + "time": "2024-04-14T07:13:56+00:00" }, { "name": "sebastian/cli-parser", diff --git a/docs/pages/docs/features/observability/observable-task-table/index.md b/docs/pages/docs/features/observability/observable-task-table/index.md index 8ea9b77a24c4c1b7518cea72acba810952ee769e..79ffb3429181e4a38b5a5a6e1be33abd6dfcee0f 100644 --- a/docs/pages/docs/features/observability/observable-task-table/index.md +++ b/docs/pages/docs/features/observability/observable-task-table/index.md @@ -40,37 +40,6 @@ $this->observableTaskTable->observe(new ObservableTask( )); ``` -## Observing Task with Timeout - -The `ObservableTaskTimeoutIterator` class is used to define a timeout for the -task. If the task is inactive for the specified time, the task will be -cancelled. - -You can use it by composing it with an observable task: - -```php -$observableTaskTimeout = new ObservableTaskTimeoutIterator( - iterableTask: function () use ( - $webSocketAuthResolution, - $webSocketConnection, - $rpcRequest, - ): Generator { - yield new ObservableTaskStatusUpdate(ObservableTaskStatus::Running, null); - - Coroutine::sleep(3); - - yield new ObservableTaskStatusUpdate(ObservableTaskStatus::Finished, null); - }, - inactivityTimeout: 5.0, -); - -$this->observableTaskTable->observe(new ObservableTask( - iterableTask: $observableTaskTimeout, - name: 'test', - category: 'test_tasks', -)); -``` - # Rendering Observable tasks table is iterable and uses shared memory. It is possible to diff --git a/docs/pages/index.md b/docs/pages/index.md index c6c73a6603c7ef7993d57742f438ce155de50bdb..560f6dc5a67ce76d7d3df735b5dae1042e092fcd 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -17,11 +17,13 @@ description: > <div class="homepage__content"> <hgroup class="homepage__title"> <h1>PHP Framework for Next-Gen Web Apps</h1> - <h2>Resonance: Connect, Converse, Create</h2> + <h2> + Connect, Converse, Create + </h2> <p> - PHP Resonance Framework is designed from the ground up to - facilitate interoperability and messaging between services in - your infrastructure and beyond. It provides AI capabilities, + Resonance is designed from the ground up to facilitate + interoperability and messaging between services in your + infrastructure and beyond. It provides AI capabilities, has a built in web server and integrates with llama.cpp. </p> <p> @@ -133,22 +135,6 @@ description: > href="/docs/features/validation/constraints/" >Learn More</a> </li> - <li class="homepage-gallery__item"> - <h4> - <a href="/docs/features/ai/prompt-subject-responders/"> - Prompt Subject Responders - <span class="homepage-gallery__version">v0.20.0</span> - </a> - </h4> - <iframe - src="https://www.youtube.com/embed/pCyEBueNw24" - title="YouTube video player" - frameborder="0" - allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" - referrerpolicy="strict-origin-when-cross-origin" - allowfullscreen - ></iframe> - </li> <li class="homepage-gallery__item"> <h4> <a href="/docs/features/swoole-server-tasks/"> diff --git a/resources/css/docs-page-homepage.css b/resources/css/docs-page-homepage.css index dc815085cabda65265d2eccfc78c6753b0ad8d4c..483f41802348a0d6e8a6e9018a408146f560a964 100644 --- a/resources/css/docs-page-homepage.css +++ b/resources/css/docs-page-homepage.css @@ -200,6 +200,11 @@ h2.homepage__example__title { } } +.homepage__title h3 { + font-size: 2em; + font-weight: bold; +} + .homepage__title p { line-height: 1.5; max-width: 80ch; diff --git a/src/Command/StaticPagesBuild.php b/src/Command/StaticPagesBuild.php index 41a2fc0fea5a5a183ada3725169f8f75c094db32..747e20e9a618f5c3511180979eb4e5e2e380b2bb 100644 --- a/src/Command/StaticPagesBuild.php +++ b/src/Command/StaticPagesBuild.php @@ -9,10 +9,11 @@ use Distantmagic\Resonance\Attribute\WantsFeature; use Distantmagic\Resonance\Command; use Distantmagic\Resonance\Feature; use Distantmagic\Resonance\StaticPageProcessor; -use Distantmagic\Resonance\SwooleCoroutineHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use function Distantmagic\Resonance\helpers\coroutineMustRun; + #[ConsoleCommand( name: 'static-pages:build', description: 'Generate static pages' @@ -28,7 +29,7 @@ final class StaticPagesBuild extends Command protected function execute(InputInterface $input, OutputInterface $output): int { - SwooleCoroutineHelper::mustRun(function () { + coroutineMustRun(function () { $this->staticPageProcessor->process(); }); diff --git a/src/CoroutineCommand.php b/src/CoroutineCommand.php index 193b34fa4de861c090b8f44ced4c87749737abc1..ddb4fb0e9fb25e646a724d3a4bc365b0cbe6f0eb 100644 --- a/src/CoroutineCommand.php +++ b/src/CoroutineCommand.php @@ -8,6 +8,8 @@ use Symfony\Component\Console\Command\Command as SymfonyCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use function Distantmagic\Resonance\helpers\coroutineMustRun; + abstract class CoroutineCommand extends SymfonyCommand { abstract protected function executeInCoroutine(InputInterface $input, OutputInterface $output): int; @@ -29,7 +31,7 @@ abstract class CoroutineCommand extends SymfonyCommand 'log_level' => $this->swooleConfiguration->logLevel, ]); - return SwooleCoroutineHelper::mustRun(function () use ($input, $output): int { + return coroutineMustRun(function () use ($input, $output): int { return $this->executeInCoroutine($input, $output); }); } diff --git a/src/CronJobRunner.php b/src/CronJobRunner.php index ccca13403dccb9a6c46cee0f6cc1d20c618c37f2..c9e97e4f4576f946e837b25a81af060be524912d 100644 --- a/src/CronJobRunner.php +++ b/src/CronJobRunner.php @@ -7,6 +7,8 @@ namespace Distantmagic\Resonance; use Distantmagic\Resonance\Attribute\Singleton; use Psr\Log\LoggerInterface; +use function Distantmagic\Resonance\helpers\coroutineMustGo; + #[Singleton] readonly class CronJobRunner { @@ -14,7 +16,7 @@ readonly class CronJobRunner public function runCronJob(CronRegisteredJob $cronRegisteredJob): void { - SwooleCoroutineHelper::mustGo(function () use ($cronRegisteredJob): void { + coroutineMustGo(function () use ($cronRegisteredJob): void { $this->logger->info(sprintf('cron_job_start(%s)', $cronRegisteredJob->name)); $cronRegisteredJob->cronJob->onCronTick(); }); diff --git a/src/DependencyInjectionContainer.php b/src/DependencyInjectionContainer.php index 0f04e6e6d7896cf5e669d60d9f160b23b5877974..2bc80ef7b4ec56b1d324f9600ec5871f1d0680ff 100644 --- a/src/DependencyInjectionContainer.php +++ b/src/DependencyInjectionContainer.php @@ -18,6 +18,8 @@ use ReflectionFunction; use ReflectionFunctionAbstract; use Swoole\Event; +use function Distantmagic\Resonance\helpers\coroutineMustRun; + readonly class DependencyInjectionContainer { public PHPProjectFiles $phpProjectFiles; @@ -76,7 +78,7 @@ readonly class DependencyInjectionContainer /** * @var null|array<string,mixed> */ - $parameters = SwooleCoroutineHelper::mustRun(function () use ($function): array { + $parameters = coroutineMustRun(function () use ($function): array { $reflectionFunction = new ReflectionFunction($function); return $this->makeParameters($reflectionFunction, new DependencyStack()); @@ -122,7 +124,7 @@ readonly class DependencyInjectionContainer */ public function make(string $className): object { - $ret = SwooleCoroutineHelper::mustRun(function () use ($className): object { + $ret = coroutineMustRun(function () use ($className): object { $stack = new DependencyStack(); if ($this->dependencyProviders->hasKey($className)) { diff --git a/src/DialogueMessageProducer/LlamaCppPromptMessageProducer.php b/src/DialogueMessageProducer/LlamaCppPromptMessageProducer.php index cc3c68db15727d43cbd8f285db4ba8af35032541..e659022da0f9c38a548a338f11f5c0caca1263a6 100644 --- a/src/DialogueMessageProducer/LlamaCppPromptMessageProducer.php +++ b/src/DialogueMessageProducer/LlamaCppPromptMessageProducer.php @@ -27,22 +27,10 @@ readonly class LlamaCppPromptMessageProducer extends DialogueMessageProducer ); foreach ($completion as $token) { - if ($token->isFailed) { - $completion->stop(); - - yield new DialogueMessageChunk( - content: '', - isFailed: true, - isLastToken: true, - ); - - return; - } - yield new DialogueMessageChunk( content: (string) $token, - isFailed: false, - isLastToken: $token->isLastToken, + isFailed: $token->isFailed(), + isLastToken: $token->isLastToken(), ); } } diff --git a/src/DialogueNode.php b/src/DialogueNode.php index 83ac2456eaa4c5eaea09edcee38c0f560fd573ee..260575f20e0c1be072e51e82d820d8c5bc4c905c 100644 --- a/src/DialogueNode.php +++ b/src/DialogueNode.php @@ -6,6 +6,9 @@ namespace Distantmagic\Resonance; use Distantmagic\Resonance\DialogueMessageProducer\ConstMessageProducer; use Ds\Set; +use Generator; + +use function Distantmagic\Resonance\helpers\generatorGetReturn; readonly class DialogueNode implements DialogueNodeInterface { @@ -69,9 +72,20 @@ readonly class DialogueNode implements DialogueNodeInterface } public function respondTo(DialogueInputInterface $dialogueInput): DialogueResponseResolutionInterface + { + $respondToGenerator = $this->respondToWithProgress($dialogueInput); + + return generatorGetReturn($respondToGenerator); + } + + public function respondToWithProgress(DialogueInputInterface $dialogueInput): Generator { foreach (new DialogueResponseSortedIterator($this->responses) as $response) { - $resolution = $response->resolveResponse($dialogueInput); + $resolutionGenerator = $response->resolveResponseWithProgress($dialogueInput); + + yield from $resolutionGenerator; + + $resolution = $resolutionGenerator->getReturn(); $resolutionStatus = $resolution->getStatus(); if ($resolutionStatus->isFailed() || $resolutionStatus->canRespond()) { diff --git a/src/DialogueNodeInterface.php b/src/DialogueNodeInterface.php index b0c5c3a5e963004482817fbb9de61e356d6afe0c..685b1e6a91d996ed9ea247383339dac7df4b2b56 100644 --- a/src/DialogueNodeInterface.php +++ b/src/DialogueNodeInterface.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance; use Ds\Set; +use Generator; interface DialogueNodeInterface { @@ -27,4 +28,9 @@ interface DialogueNodeInterface public function getSideEffects(): Set; public function respondTo(DialogueInputInterface $dialogueInput): DialogueResponseResolutionInterface; + + /** + * @return Generator<mixed,LlmCompletionProgressInterface,mixed,DialogueResponseResolutionInterface> + */ + public function respondToWithProgress(DialogueInputInterface $dialogueInput): Generator; } diff --git a/src/DialogueNodeTest.php b/src/DialogueNodeTest.php index 7ecad6711439e02377cf66ac7607adfa2910e1d3..c873947b911f916794c4dad3b668ae746b9a0474 100644 --- a/src/DialogueNodeTest.php +++ b/src/DialogueNodeTest.php @@ -13,6 +13,8 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; +use function Distantmagic\Resonance\helpers\coroutineMustRun; + /** * @internal */ @@ -53,14 +55,14 @@ final class DialogueNodeTest extends TestCase followUp: $marketingNode, )); - SwooleCoroutineHelper::mustRun(static function () use ($marketingNode, $rootNode) { + coroutineMustRun(static function () use ($marketingNode, $rootNode) { $response = $rootNode->respondTo(new UserInput('yep')); self::assertFalse($response->getStatus()->isFailed()); self::assertSame($marketingNode, $response->getFollowUp()); }); - SwooleCoroutineHelper::mustRun(static function () use ($rootNode) { + coroutineMustRun(static function () use ($rootNode) { $response = $rootNode->respondTo(new UserInput('I do not know who I am')); self::assertFalse($response->getStatus()->isFailed()); @@ -127,7 +129,7 @@ final class DialogueNodeTest extends TestCase followUp: $marketingNode, )); - SwooleCoroutineHelper::mustRun(static function () use ($rootNode) { + coroutineMustRun(static function () use ($rootNode) { $response = $rootNode->respondTo(new UserInput('i am a recruiter')); self::assertFalse($response->getStatus()->isFailed()); diff --git a/src/DialogueResponse.php b/src/DialogueResponse.php index 9940317fa4d3483e1722e95222db5bfdcbea1fbd..f86b47e8cdd6a8a460a51a7eaab95828d82a4f92 100644 --- a/src/DialogueResponse.php +++ b/src/DialogueResponse.php @@ -4,4 +4,14 @@ declare(strict_types=1); namespace Distantmagic\Resonance; -abstract readonly class DialogueResponse implements DialogueResponseInterface {} +use function Distantmagic\Resonance\helpers\generatorGetReturn; + +abstract readonly class DialogueResponse implements DialogueResponseInterface +{ + public function resolveResponse(DialogueInputInterface $dialogueInput): DialogueResponseResolutionInterface + { + $resolveGenerator = $this->resolveResponseWithProgress($dialogueInput); + + return generatorGetReturn($resolveGenerator); + } +} diff --git a/src/DialogueResponse/CatchAllResponse.php b/src/DialogueResponse/CatchAllResponse.php index 5e7cc9de20eae20d54a94e98e208e8f2c9f84fea..084738db8ff8dfd72f19ada622547eaeae4df8f9 100644 --- a/src/DialogueResponse/CatchAllResponse.php +++ b/src/DialogueResponse/CatchAllResponse.php @@ -9,6 +9,8 @@ use Distantmagic\Resonance\DialogueNodeInterface; use Distantmagic\Resonance\DialogueResponse; use Distantmagic\Resonance\DialogueResponseResolution; use Distantmagic\Resonance\DialogueResponseResolutionStatus; +use Distantmagic\Resonance\LlmCompletionProgress; +use Generator; readonly class CatchAllResponse extends DialogueResponse { @@ -19,8 +21,13 @@ readonly class CatchAllResponse extends DialogueResponse return 100; } - public function resolveResponse(DialogueInputInterface $dialogueInput): DialogueResponseResolution + public function resolveResponseWithProgress(DialogueInputInterface $dialogueInput): Generator { + yield new LlmCompletionProgress( + category: 'catch_all_response', + shouldNotify: false, + ); + return new DialogueResponseResolution( followUp: $this->followUp, status: DialogueResponseResolutionStatus::CanRespond, diff --git a/src/DialogueResponse/LiteralInputResponse.php b/src/DialogueResponse/LiteralInputResponse.php index 6d95df7d4da90d349ca9cf3b4f737b6f67ebba05..41a5b4e950d8522073e618b300d743de7f84b036 100644 --- a/src/DialogueResponse/LiteralInputResponse.php +++ b/src/DialogueResponse/LiteralInputResponse.php @@ -9,6 +9,8 @@ use Distantmagic\Resonance\DialogueNodeInterface; use Distantmagic\Resonance\DialogueResponse; use Distantmagic\Resonance\DialogueResponseResolution; use Distantmagic\Resonance\DialogueResponseResolutionStatus; +use Distantmagic\Resonance\LlmCompletionProgress; +use Generator; readonly class LiteralInputResponse extends DialogueResponse { @@ -22,8 +24,13 @@ readonly class LiteralInputResponse extends DialogueResponse return 2; } - public function resolveResponse(DialogueInputInterface $dialogueInput): DialogueResponseResolution + public function resolveResponseWithProgress(DialogueInputInterface $dialogueInput): Generator { + yield new LlmCompletionProgress( + category: 'literal_input_response', + shouldNotify: false, + ); + if ($dialogueInput->getContent() === $this->when) { return new DialogueResponseResolution( followUp: $this->followUp, diff --git a/src/DialogueResponse/LlamaCppExtractSubjectResponse.php b/src/DialogueResponse/LlamaCppExtractSubjectResponse.php index 47d9ea2e79ba5c01171ca85644cddafeee4ca3d3..e217c0e9d41cb47c1fc17b274ea3aa7cd74b0c5f 100644 --- a/src/DialogueResponse/LlamaCppExtractSubjectResponse.php +++ b/src/DialogueResponse/LlamaCppExtractSubjectResponse.php @@ -11,18 +11,23 @@ use Distantmagic\Resonance\DialogueResponseResolution; use Distantmagic\Resonance\DialogueResponseResolutionStatus; use Distantmagic\Resonance\LlamaCppExtractSubjectInterface; use Distantmagic\Resonance\LlamaCppExtractSubjectResult; +use Distantmagic\Resonance\LlmCompletionProgressInterface; use Distantmagic\Resonance\LlmPersona\HelpfulAssistant; use Distantmagic\Resonance\LlmPersonaInterface; +use Generator; +/** + * @psalm-type TCallbackReturn = DialogueResponseResolution|Generator<mixed,LlmCompletionProgressInterface,mixed,DialogueResponseResolution> + */ readonly class LlamaCppExtractSubjectResponse extends DialogueResponse { /** - * @var Closure(LlamaCppExtractSubjectResult):DialogueResponseResolution $whenProvided + * @var Closure(LlamaCppExtractSubjectResult):TCallbackReturn $whenProvided */ private Closure $whenProvided; /** - * @param callable(LlamaCppExtractSubjectResult):DialogueResponseResolution $whenProvided + * @param callable(LlamaCppExtractSubjectResult):TCallbackReturn $whenProvided */ public function __construct( private LlamaCppExtractSubjectInterface $llamaCppExtractSubject, @@ -38,14 +43,18 @@ readonly class LlamaCppExtractSubjectResponse extends DialogueResponse return 50; } - public function resolveResponse(DialogueInputInterface $dialogueInput): DialogueResponseResolution + public function resolveResponseWithProgress(DialogueInputInterface $dialogueInput): Generator { - $extracted = $this->llamaCppExtractSubject->extract( + $extractedGenerator = $this->llamaCppExtractSubject->extractWithProgress( input: $dialogueInput->getContent(), persona: $this->persona, topic: $this->topic, ); + yield from $extractedGenerator; + + $extracted = $extractedGenerator->getReturn(); + if ($extracted->isFailed) { return new DialogueResponseResolution( followUp: null, @@ -60,6 +69,14 @@ readonly class LlamaCppExtractSubjectResponse extends DialogueResponse ); } - return ($this->whenProvided)($extracted); + $ret = ($this->whenProvided)($extracted); + + if ($ret instanceof Generator) { + yield from $ret; + + return $ret->getReturn(); + } + + return $ret; } } diff --git a/src/DialogueResponse/LlamaCppExtractWhenResponse.php b/src/DialogueResponse/LlamaCppExtractWhenResponse.php index f3ad6d93d00d6b919e1d5ac91265eb14a088a469..67f98373f57c7d788218d2af84a429abe38286f0 100644 --- a/src/DialogueResponse/LlamaCppExtractWhenResponse.php +++ b/src/DialogueResponse/LlamaCppExtractWhenResponse.php @@ -11,18 +11,23 @@ use Distantmagic\Resonance\DialogueResponseResolution; use Distantmagic\Resonance\DialogueResponseResolutionStatus; use Distantmagic\Resonance\LlamaCppExtractWhenInterface; use Distantmagic\Resonance\LlamaCppExtractWhenResult; +use Distantmagic\Resonance\LlmCompletionProgressInterface; use Distantmagic\Resonance\LlmPersona\HelpfulAssistant; use Distantmagic\Resonance\LlmPersonaInterface; +use Generator; +/** + * @psalm-type TCallbackReturn = DialogueResponseResolution|Generator<mixed,LlmCompletionProgressInterface,mixed,DialogueResponseResolution> + */ readonly class LlamaCppExtractWhenResponse extends DialogueResponse { /** - * @var Closure(LlamaCppExtractWhenResult):DialogueResponseResolution $whenProvided + * @var Closure(LlamaCppExtractWhenResult):TCallbackReturn $whenProvided */ private Closure $whenProvided; /** - * @param callable(LlamaCppExtractWhenResult):DialogueResponseResolution $whenProvided + * @param callable(LlamaCppExtractWhenResult):TCallbackReturn $whenProvided */ public function __construct( private LlamaCppExtractWhenInterface $llamaCppExtractWhen, @@ -38,15 +43,18 @@ readonly class LlamaCppExtractWhenResponse extends DialogueResponse return 50; } - public function resolveResponse( - DialogueInputInterface $dialogueInput - ): DialogueResponseResolution { - $extracted = $this->llamaCppExtractWhen->extract( + public function resolveResponseWithProgress(DialogueInputInterface $dialogueInput): Generator + { + $extractedGenerator = $this->llamaCppExtractWhen->extractWithProgress( condition: $this->condition, input: $dialogueInput->getContent(), persona: $this->persona, ); + yield from $extractedGenerator; + + $extracted = $extractedGenerator->getReturn(); + if ($extracted->isFailed) { return new DialogueResponseResolution( followUp: null, @@ -61,6 +69,14 @@ readonly class LlamaCppExtractWhenResponse extends DialogueResponse ); } - return ($this->whenProvided)($extracted); + $ret = ($this->whenProvided)($extracted); + + if ($ret instanceof Generator) { + yield from $ret; + + return $ret->getReturn(); + } + + return $ret; } } diff --git a/src/DialogueResponse/LlamaCppExtractYesNoMaybeResponse.php b/src/DialogueResponse/LlamaCppExtractYesNoMaybeResponse.php index b631ee041b2f4060480cd3d235ff4b421f130603..a22f8ec0c797ca69c87a5d84e6ab1b9791d5a130 100644 --- a/src/DialogueResponse/LlamaCppExtractYesNoMaybeResponse.php +++ b/src/DialogueResponse/LlamaCppExtractYesNoMaybeResponse.php @@ -11,16 +11,22 @@ use Distantmagic\Resonance\DialogueResponseResolution; use Distantmagic\Resonance\DialogueResponseResolutionStatus; use Distantmagic\Resonance\LlamaCppExtractYesNoMaybe; use Distantmagic\Resonance\LlamaCppExtractYesNoMaybeResult; +use Distantmagic\Resonance\LlmCompletionProgress; +use Distantmagic\Resonance\LlmCompletionProgressInterface; +use Generator; +/** + * @psalm-type TCallbackReturn = DialogueResponseResolution|Generator<mixed,LlmCompletionProgressInterface,mixed,DialogueResponseResolution> + */ readonly class LlamaCppExtractYesNoMaybeResponse extends DialogueResponse { /** - * @var Closure(LlamaCppExtractYesNoMaybeResult):DialogueResponseResolution $whenProvided + * @var Closure(LlamaCppExtractYesNoMaybeResult):TCallbackReturn $whenProvided */ private Closure $whenProvided; /** - * @param callable(LlamaCppExtractYesNoMaybeResult):DialogueResponseResolution $whenProvided + * @param callable(LlamaCppExtractYesNoMaybeResult):TCallbackReturn $whenProvided */ public function __construct( private LlamaCppExtractYesNoMaybe $llamaCppExtractYesNoMaybe, @@ -34,10 +40,15 @@ readonly class LlamaCppExtractYesNoMaybeResponse extends DialogueResponse return 50; } - public function resolveResponse(DialogueInputInterface $dialogueInput): DialogueResponseResolution + public function resolveResponseWithProgress(DialogueInputInterface $dialogueInput): Generator { $extracted = $this->llamaCppExtractYesNoMaybe->extract(input: $dialogueInput->getContent()); + yield new LlmCompletionProgress( + category: 'extract_yes_no_maybe_response', + shouldNotify: false, + ); + if (is_null($extracted->result) || $extracted->isFailed) { return new DialogueResponseResolution( followUp: null, @@ -45,6 +56,14 @@ readonly class LlamaCppExtractYesNoMaybeResponse extends DialogueResponse ); } - return ($this->whenProvided)($extracted); + $ret = ($this->whenProvided)($extracted); + + if ($ret instanceof Generator) { + yield from $ret; + + return $ret->getReturn(); + } + + return $ret; } } diff --git a/src/DialogueResponseInterface.php b/src/DialogueResponseInterface.php index 8eba99612df548cefd5fa2da8b23bbc534697682..5e79a92e7009fbd1f1b4a8820270b657f2ae6722 100644 --- a/src/DialogueResponseInterface.php +++ b/src/DialogueResponseInterface.php @@ -4,9 +4,16 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use Generator; + interface DialogueResponseInterface { public function getCost(): int; public function resolveResponse(DialogueInputInterface $dialogueInput): DialogueResponseResolutionInterface; + + /** + * @return Generator<mixed,LlmCompletionProgressInterface,mixed,DialogueResponseResolutionInterface> + */ + public function resolveResponseWithProgress(DialogueInputInterface $dialogueInput): Generator; } diff --git a/src/DoctrineConsoleRunner.php b/src/DoctrineConsoleRunner.php index 30ebc273d00471de2034f3e78036d8cbfd018359..d50be6405101dbf6f53785f72e99f61ceb7fb647 100644 --- a/src/DoctrineConsoleRunner.php +++ b/src/DoctrineConsoleRunner.php @@ -8,6 +8,8 @@ use Doctrine\ORM\Tools\Console\ConsoleRunner; use Swoole\Runtime; use Symfony\Component\Console\Application; +use function Distantmagic\Resonance\helpers\coroutineMustRun; + final readonly class DoctrineConsoleRunner { public static function run(DependencyInjectionContainer $container): never @@ -22,7 +24,7 @@ final readonly class DoctrineConsoleRunner ConsoleRunner::addCommands($cli, $entityManagerProvider); - $errorCode = SwooleCoroutineHelper::mustRun(static function () use ($cli): int { + $errorCode = coroutineMustRun(static function () use ($cli): int { return $cli->run(); }); diff --git a/src/GeneratorHelper.php b/src/GeneratorHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..24e37650032e24dc31be7ee4560e471ffedf3a25 --- /dev/null +++ b/src/GeneratorHelper.php @@ -0,0 +1,7 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +final readonly class GeneratorHelper {} diff --git a/src/HttpResponderAggregate.php b/src/HttpResponderAggregate.php index 0ee1b574783e46818f6b8b9f8f28289e31b5169d..c69153eb2bff9b10f9bd269cfbd5fb7a5fe22a37 100644 --- a/src/HttpResponderAggregate.php +++ b/src/HttpResponderAggregate.php @@ -27,6 +27,8 @@ use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; use Throwable; +use function Distantmagic\Resonance\helpers\coroutineMustGetContext; + #[Singleton] readonly class HttpResponderAggregate implements RequestHandlerInterface { @@ -64,7 +66,7 @@ readonly class HttpResponderAggregate implements RequestHandlerInterface $responder = $this->selectResponder($request); try { - $context = SwooleCoroutineHelper::mustGetContext(); + $context = coroutineMustGetContext(); $context[SwooleContextRequestResponseReader::CONTEXT_KEY_REQUEST] = $request; $context[SwooleContextRequestResponseReader::CONTEXT_KEY_RESPONSE] = $response; diff --git a/src/LlamaCppClient.php b/src/LlamaCppClient.php index c12be7f6e3ecf6afe15ee9df55b8b48ea08859c6..6e9229d946e019708a9cbbcd32236c88580fbe66 100644 --- a/src/LlamaCppClient.php +++ b/src/LlamaCppClient.php @@ -13,6 +13,8 @@ use RuntimeException; use Swoole\Coroutine; use Swoole\Coroutine\Channel; +use function Distantmagic\Resonance\helpers\coroutineMustGo; + #[RequiresPhpExtension('curl')] #[Singleton(provides: LlamaCppClientInterface::class)] readonly class LlamaCppClient implements LlamaCppClientInterface @@ -172,7 +174,7 @@ readonly class LlamaCppClient implements LlamaCppClientInterface { $channel = new Channel(1); - SwooleCoroutineHelper::mustGo(function () use ($channel, $path, $requestData): void { + coroutineMustGo(function () use ($channel, $path, $requestData): void { $curlHandle = $this->createCurlHandle(); Coroutine::defer(static function () use ($channel) { diff --git a/src/LlamaCppClientTest.php b/src/LlamaCppClientTest.php index 5b5f697f08e25a346822b996bc9975418d124df5..c1abf98c682fa40aff1ccdc2e8b97f73b90ced4c 100644 --- a/src/LlamaCppClientTest.php +++ b/src/LlamaCppClientTest.php @@ -9,6 +9,8 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Swoole\Event; +use function Distantmagic\Resonance\helpers\coroutineMustRun; + /** * @internal */ @@ -27,7 +29,7 @@ final class LlamaCppClientTest extends TestCase { $llamaCppClient = self::$container->make(LlamaCppClient::class); - SwooleCoroutineHelper::mustRun(static function () use ($llamaCppClient) { + coroutineMustRun(static function () use ($llamaCppClient) { $completion = $llamaCppClient->generateCompletion(new LlamaCppCompletionRequest( llmChatHistory: new LlmChatHistory([ new LlmChatMessage('user', 'Who are you? Answer in exactly two words.'), diff --git a/src/LlamaCppCompletionIterator.php b/src/LlamaCppCompletionIterator.php index f4eb21a75721b09b902f67a716a8fb4e46f41fa8..7c40d38840d7bb67fb77e1c34041b5b162170342 100644 --- a/src/LlamaCppCompletionIterator.php +++ b/src/LlamaCppCompletionIterator.php @@ -34,42 +34,46 @@ readonly class LlamaCppCompletionIterator implements IteratorAggregate */ $previousChunk = ''; - foreach ($this->responseChunks as $responseChunk) { - if ($responseChunk instanceof SwooleChannelIteratorError || ObservableTaskStatus::Failed === $responseChunk->data->status) { - yield new LlamaCppCompletionToken( - content: '', - isFailed: true, - isLastToken: true, - ); + try { + foreach ($this->responseChunks as $responseChunk) { + if ($responseChunk instanceof SwooleChannelIteratorError || ObservableTaskStatus::Failed === $responseChunk->data->status) { + yield new LlamaCppCompletionToken( + content: '', + isFailed: true, + isLastToken: true, + ); - break; - } + break; + } - $previousChunk .= $responseChunk->data->chunk; + $previousChunk .= $responseChunk->data->chunk; - /** - * @var null|object{ - * content: string, - * stop: boolean, - * } - */ - $unserializedToken = $this->jsonSerializer->unserialize( - json: $previousChunk, - offset: self::COMPLETION_CHUNKED_DATA_PREFIX_LENGTH, - throw: false, - ); + /** + * @var null|object{ + * content: string, + * stop: boolean, + * } + */ + $unserializedToken = $this->jsonSerializer->unserialize( + json: $previousChunk, + offset: self::COMPLETION_CHUNKED_DATA_PREFIX_LENGTH, + throw: false, + ); - if (JSON_ERROR_NONE === json_last_error()) { - $previousChunk = ''; - } + if (JSON_ERROR_NONE === json_last_error()) { + $previousChunk = ''; + } - if ($unserializedToken) { - yield new LlamaCppCompletionToken( - content: $unserializedToken->content, - isFailed: false, - isLastToken: $unserializedToken->stop, - ); + if ($unserializedToken) { + yield new LlamaCppCompletionToken( + content: $unserializedToken->content, + isFailed: false, + isLastToken: $unserializedToken->stop, + ); + } } + } finally { + $this->stop(); } if (!empty($previousChunk)) { diff --git a/src/LlamaCppCompletionToken.php b/src/LlamaCppCompletionToken.php index d475879cfdd3823b151dc4823abb79b70fc54588..d6a2d2657cfe9a74f34c6e1eab6622820403b6a5 100644 --- a/src/LlamaCppCompletionToken.php +++ b/src/LlamaCppCompletionToken.php @@ -9,16 +9,31 @@ use Stringable; /** * @psalm-suppress PossiblyUnusedProperty used in applications */ -readonly class LlamaCppCompletionToken implements Stringable +readonly class LlamaCppCompletionToken implements LlmCompletionTokenInterface, Stringable { public function __construct( - public string $content, - public bool $isFailed, - public bool $isLastToken, + private string $content, + private bool $isFailed, + private bool $isLastToken, ) {} public function __toString(): string { return $this->content; } + + public function getContent(): string + { + return $this->content; + } + + public function isFailed(): bool + { + return $this->isFailed; + } + + public function isLastToken(): bool + { + return $this->isLastToken; + } } diff --git a/src/LlamaCppExtractSubject.php b/src/LlamaCppExtractSubject.php index d18f8281362048057867ae87c072f90751981caf..32c691601e8670937eebac5a13ca4bd89d72cb11 100644 --- a/src/LlamaCppExtractSubject.php +++ b/src/LlamaCppExtractSubject.php @@ -7,6 +7,9 @@ namespace Distantmagic\Resonance; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\BackusNaurFormGrammar\InlineGrammar; use Distantmagic\Resonance\LlmPersona\HelpfulAssistant; +use Generator; + +use function Distantmagic\Resonance\helpers\generatorGetReturn; #[Singleton(provides: LlamaCppExtractSubjectInterface::class)] readonly class LlamaCppExtractSubject implements LlamaCppExtractSubjectInterface @@ -20,6 +23,16 @@ readonly class LlamaCppExtractSubject implements LlamaCppExtractSubjectInterface string $topic, LlmPersonaInterface $persona = new HelpfulAssistant(), ): LlamaCppExtractSubjectResult { + $extractGenerator = $this->extractWithProgress($input, $topic, $persona); + + return generatorGetReturn($extractGenerator); + } + + public function extractWithProgress( + string $input, + string $topic, + LlmPersonaInterface $persona = new HelpfulAssistant(), + ): Generator { $completion = $this->llamaCppClient->generateCompletion( new LlamaCppCompletionRequest( backusNaurFormGrammar: new InlineGrammar('root ::= [0-9a-zA-Z\"\\\\\' ]+'), @@ -46,9 +59,12 @@ readonly class LlamaCppExtractSubject implements LlamaCppExtractSubjectInterface $ret = ''; foreach ($completion as $token) { - if ($token->isFailed) { - $completion->stop(); + yield new LlmCompletionProgress( + category: 'extract_subject', + shouldNotify: true, + ); + if ($token->isFailed()) { return new LlamaCppExtractSubjectResult( content: '', input: $input, @@ -93,5 +109,6 @@ readonly class LlamaCppExtractSubject implements LlamaCppExtractSubjectInterface isMatched: true, topic: $topic, ); + } } diff --git a/src/LlamaCppExtractSubjectInterface.php b/src/LlamaCppExtractSubjectInterface.php index 7a51bfdc9a0c5dc2d28197dafdadb9086d40c792..456635f4d44c276fc6b2a81e26fd0120f6e89403 100644 --- a/src/LlamaCppExtractSubjectInterface.php +++ b/src/LlamaCppExtractSubjectInterface.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance; use Distantmagic\Resonance\LlmPersona\HelpfulAssistant; +use Generator; interface LlamaCppExtractSubjectInterface { @@ -13,4 +14,13 @@ interface LlamaCppExtractSubjectInterface string $topic, LlmPersonaInterface $persona = new HelpfulAssistant(), ): LlamaCppExtractSubjectResult; + + /** + * @return Generator<mixed,LlmCompletionProgressInterface,mixed,LlamaCppExtractSubjectResult> + */ + public function extractWithProgress( + string $input, + string $topic, + LlmPersonaInterface $persona = new HelpfulAssistant(), + ): Generator; } diff --git a/src/LlamaCppExtractSubjectTest.php b/src/LlamaCppExtractSubjectTest.php index 89ae16b3c245e7cb293e8c46610a117b79078704..8470e5a80e8eb92ccbc2caceb131f04337f4221b 100644 --- a/src/LlamaCppExtractSubjectTest.php +++ b/src/LlamaCppExtractSubjectTest.php @@ -11,6 +11,8 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Swoole\Event; +use function Distantmagic\Resonance\helpers\coroutineMustRun; + /** * @internal */ @@ -75,7 +77,7 @@ final class LlamaCppExtractSubjectTest extends TestCase { $llamaCppExtract = self::$container->make(LlamaCppExtractSubject::class); - SwooleCoroutineHelper::mustRun(static function () use ($expected, $input, $llamaCppExtract, $topic) { + coroutineMustRun(static function () use ($expected, $input, $llamaCppExtract, $topic) { $extracted = $llamaCppExtract->extract( topic: $topic, input: $input, diff --git a/src/LlamaCppExtractWhen.php b/src/LlamaCppExtractWhen.php index 199343aa17346a829236835ec0faf800c4a0183f..96878586913565a10b52afa040f43d00796220a7 100644 --- a/src/LlamaCppExtractWhen.php +++ b/src/LlamaCppExtractWhen.php @@ -7,6 +7,9 @@ namespace Distantmagic\Resonance; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\BackusNaurFormGrammar\YesNoMaybeGrammar; use Distantmagic\Resonance\LlmPersona\HelpfulAssistant; +use Generator; + +use function Distantmagic\Resonance\helpers\generatorGetReturn; #[Singleton(provides: LlamaCppExtractWhenInterface::class)] readonly class LlamaCppExtractWhen implements LlamaCppExtractWhenInterface @@ -21,6 +24,16 @@ readonly class LlamaCppExtractWhen implements LlamaCppExtractWhenInterface string $condition, LlmPersonaInterface $persona = new HelpfulAssistant(), ): LlamaCppExtractWhenResult { + $extractGenerator = $this->extractWithProgress($input, $condition, $persona); + + return generatorGetReturn($extractGenerator); + } + + public function extractWithProgress( + string $input, + string $condition, + LlmPersonaInterface $persona = new HelpfulAssistant(), + ): Generator { $completion = $this->llamaCppClient->generateCompletion( new LlamaCppCompletionRequest( backusNaurFormGrammar: $this->yesNoMaybeGrammar, @@ -46,7 +59,12 @@ readonly class LlamaCppExtractWhen implements LlamaCppExtractWhenInterface $ret = ''; foreach ($completion as $token) { - if ($token->isFailed) { + yield new LlmCompletionProgress( + category: 'extract_when', + shouldNotify: true, + ); + + if ($token->isFailed()) { return new LlamaCppExtractWhenResult( condition: $condition, isFailed: true, diff --git a/src/LlamaCppExtractWhenInterface.php b/src/LlamaCppExtractWhenInterface.php index 99a1470647d230c5b17282174c834c2ea6432205..ae1cdb2992e0e6802f8f17a50325529c7c4687ea 100644 --- a/src/LlamaCppExtractWhenInterface.php +++ b/src/LlamaCppExtractWhenInterface.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use Generator; + interface LlamaCppExtractWhenInterface { public function extract( @@ -11,4 +13,13 @@ interface LlamaCppExtractWhenInterface string $condition, LlmPersonaInterface $persona, ): LlamaCppExtractWhenResult; + + /** + * @return Generator<mixed,LlmCompletionProgressInterface,mixed,LlamaCppExtractWhenResult> + */ + public function extractWithProgress( + string $input, + string $condition, + LlmPersonaInterface $persona, + ): Generator; } diff --git a/src/LlamaCppExtractWhenTest.php b/src/LlamaCppExtractWhenTest.php index 229a3773a1383af871a1ad58bb686e684c38c39f..c17cee192b6c3b4b5d8458a58842f27a9a4d1dbc 100644 --- a/src/LlamaCppExtractWhenTest.php +++ b/src/LlamaCppExtractWhenTest.php @@ -11,6 +11,8 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Swoole\Event; +use function Distantmagic\Resonance\helpers\coroutineMustRun; + /** * @internal */ @@ -102,7 +104,7 @@ final class LlamaCppExtractWhenTest extends TestCase ): void { $llamaCppExtract = self::$container->make(LlamaCppExtractWhen::class); - SwooleCoroutineHelper::mustRun(static function () use ($expected, $input, $condition, $llamaCppExtract) { + coroutineMustRun(static function () use ($expected, $input, $condition, $llamaCppExtract) { $extracted = $llamaCppExtract->extract( input: $input, condition: $condition, diff --git a/src/LlamaCppExtractYesNoMaybeTest.php b/src/LlamaCppExtractYesNoMaybeTest.php index 90ea4a5e61fbbc66d54f98b9260b73b81b636fd6..96f6b2f38f435bade2fd8b96ada993f87e17b508 100644 --- a/src/LlamaCppExtractYesNoMaybeTest.php +++ b/src/LlamaCppExtractYesNoMaybeTest.php @@ -11,6 +11,8 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Swoole\Event; +use function Distantmagic\Resonance\helpers\coroutineMustRun; + /** * @internal */ @@ -63,7 +65,7 @@ final class LlamaCppExtractYesNoMaybeTest extends TestCase { $llamaCppExtract = self::$container->make(LlamaCppExtractYesNoMaybe::class); - SwooleCoroutineHelper::mustRun(static function () use ($expected, $input, $llamaCppExtract) { + coroutineMustRun(static function () use ($expected, $input, $llamaCppExtract) { $extracted = $llamaCppExtract->extract(input: $input); self::assertSame($expected, $extracted->result); diff --git a/src/LlmCompletionProgress.php b/src/LlmCompletionProgress.php new file mode 100644 index 0000000000000000000000000000000000000000..7d0d4d0a08441aaa9f8e32e4432ecbed8db15853 --- /dev/null +++ b/src/LlmCompletionProgress.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +readonly class LlmCompletionProgress implements LlmCompletionProgressInterface +{ + public function __construct( + private string $category, + private bool $shouldNotify, + ) {} + + public function getCategory(): string + { + return $this->category; + } + + public function shouldNotify(): bool + { + return $this->shouldNotify; + } +} diff --git a/src/LlmCompletionProgressInterface.php b/src/LlmCompletionProgressInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..e8c535aa64e124f26e5e44f7cdc3e9fd01705e84 --- /dev/null +++ b/src/LlmCompletionProgressInterface.php @@ -0,0 +1,12 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +interface LlmCompletionProgressInterface +{ + public function getCategory(): string; + + public function shouldNotify(): bool; +} diff --git a/src/LlmCompletionTokenInterface.php b/src/LlmCompletionTokenInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..eb1436a925baa00cd76a7975e7454b32b073e1fc --- /dev/null +++ b/src/LlmCompletionTokenInterface.php @@ -0,0 +1,14 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +interface LlmCompletionTokenInterface +{ + public function getContent(): string; + + public function isFailed(): bool; + + public function isLastToken(): bool; +} diff --git a/src/ObservableTaskFactory.php b/src/ObservableTaskFactory.php deleted file mode 100644 index 3bf9a5d155a26bed5906043b0b5eddb7360e3a5e..0000000000000000000000000000000000000000 --- a/src/ObservableTaskFactory.php +++ /dev/null @@ -1,40 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Distantmagic\Resonance; - -use Generator; - -/** - * @psalm-import-type TIterableTaskCallback from ObservableTask - */ -final readonly class ObservableTaskFactory -{ - public static function withTimeout( - callable $iterableTask, - float $inactivityTimeout = 5.0, - string $name = '', - string $category = '', - ): ObservableTask { - return new ObservableTask( - iterableTask: new ObservableTaskTimeoutIterator( - iterableTask: static function () use ($iterableTask): Generator { - yield new ObservableTaskStatusUpdate(ObservableTaskStatus::Running, null); - - yield from $iterableTask(); - yield new ObservableTaskStatusUpdate(ObservableTaskStatus::Finished, null); - }, - inactivityTimeout: $inactivityTimeout, - ), - name: $name, - category: $category, - ); - } - - /** - * @psalm-suppress UnusedConstructor this class is just a wrapper around - * functions - */ - private function __construct() {} -} diff --git a/src/ObservableTaskTable.php b/src/ObservableTaskTable.php index 5319b5a3bd5fba885874def772bd3e97c920c653..8b18725de84f8ca35d2ff9084baf8fd9094a2c06 100644 --- a/src/ObservableTaskTable.php +++ b/src/ObservableTaskTable.php @@ -12,6 +12,8 @@ use RuntimeException; use Swoole\Coroutine; use Swoole\Table; +use function Distantmagic\Resonance\helpers\coroutineMustGo; + /** * @template-implements IteratorAggregate<non-empty-string,ObservableTaskTableRow> */ @@ -78,7 +80,7 @@ readonly class ObservableTaskTable implements IteratorAggregate { $slotId = $this->availableRowsPool->nextAvailableRow(); - SwooleCoroutineHelper::mustGo(function () use ($slotId, $observableTask) { + coroutineMustGo(function () use ($slotId, $observableTask) { Coroutine::defer(function () use ($slotId) { $this->availableRowsPool->freeAvailableRow($slotId); }); diff --git a/src/ObservableTaskTimeoutIterator.php b/src/ObservableTaskTimeoutIterator.php deleted file mode 100644 index c815c0e03491c812fd561ece61c1873de617262f..0000000000000000000000000000000000000000 --- a/src/ObservableTaskTimeoutIterator.php +++ /dev/null @@ -1,121 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Distantmagic\Resonance; - -use Closure; -use Generator; -use IteratorAggregate; -use Swoole\Coroutine; -use Swoole\Coroutine\Channel; - -/** - * @psalm-import-type TIterableTaskCallback from ObservableTask - * - * @template-implements IteratorAggregate<ObservableTaskStatusUpdate> - */ -readonly class ObservableTaskTimeoutIterator implements IteratorAggregate -{ - /** - * @var Closure():iterable<ObservableTaskStatusUpdate> - */ - private Closure $iterableTask; - - /** - * @param TIterableTaskCallback $iterableTask - */ - public function __construct( - callable $iterableTask, - private float $inactivityTimeout, - ) { - $this->iterableTask = Closure::fromCallable($iterableTask); - } - - /** - * @return Generator<ObservableTaskStatusUpdate> - */ - public function __invoke(): Generator - { - return $this->getIterator(); - } - - /** - * @psalm-suppress UnusedVariable $generatorCoroutineId is used, just asynchronously - * - * @return Generator<ObservableTaskStatusUpdate> - */ - public function getIterator(): Generator - { - /** - * @var null|int $generatorCoroutineId - */ - $generatorCoroutineId = null; - - $channel = new Channel(1); - - $swooleTimeout = new SwooleTimeout(static function () use ($channel, &$generatorCoroutineId) { - $channel->push(new ObservableTaskStatusUpdate( - ObservableTaskStatus::TimedOut, - null, - )); - - if (is_int($generatorCoroutineId)) { - Coroutine::cancel($generatorCoroutineId); - } - }); - - $generatorCoroutineId = SwooleCoroutineHelper::mustGo(function () use ($channel, $swooleTimeout) { - $swooleTimeoutScheduled = $swooleTimeout->setTimeout($this->inactivityTimeout); - - Coroutine::defer(static function () use ($channel) { - $channel->close(); - }); - - Coroutine::defer(static function () use (&$swooleTimeoutScheduled) { - /** - * @psalm-suppress UnnecessaryVarAnnotation it can be changed async - * - * @var null|SwooleTimeoutScheduled $swooleTimeoutScheduled - */ - $swooleTimeoutScheduled?->cancel(); - }); - - foreach (($this->iterableTask)() as $observableTaskStatusUpdate) { - if (Coroutine::isCanceled()) { - break; - } - - $swooleTimeoutScheduled = $swooleTimeoutScheduled?->reschedule($this->inactivityTimeout); - - if (!$swooleTimeoutScheduled) { - break; - } - - $channel->push($observableTaskStatusUpdate, $this->inactivityTimeout); - } - }); - - /** - * @var SwooleChannelIterator<ObservableTaskStatusUpdate> - */ - $swooleChannelIterator = new SwooleChannelIterator( - channel: $channel, - timeout: $this->inactivityTimeout, - ); - - foreach ($swooleChannelIterator as $observableTaskStatusUpdate) { - if ($observableTaskStatusUpdate instanceof SwooleChannelIteratorError) { - if ($observableTaskStatusUpdate->isTimeout) { - yield new ObservableTaskStatusUpdate(ObservableTaskStatus::TimedOut, null); - } else { - yield new ObservableTaskStatusUpdate(ObservableTaskStatus::Failed, null); - } - - break; - } - - yield $observableTaskStatusUpdate->data; - } - } -} diff --git a/src/PHPFileReflectionClassIterator.php b/src/PHPFileReflectionClassIterator.php index bbbad205efeabe60426a9d1b86f3955d85af94b9..b752aba62a42ca94d0c6f93e57f95498535d4304 100644 --- a/src/PHPFileReflectionClassIterator.php +++ b/src/PHPFileReflectionClassIterator.php @@ -66,10 +66,6 @@ readonly class PHPFileReflectionClassIterator implements IteratorAggregate return null; } - if (str_starts_with($file->getPath(), DM_RESONANCE_ROOT)) { - return $this->readKnownClassName($file, 'Distantmagic\\Resonance\\'); - } - $namespace = (new PHPFileNamespaceReader($file))->readNamespace(); if (is_null($namespace)) { @@ -94,24 +90,4 @@ readonly class PHPFileReflectionClassIterator implements IteratorAggregate return null; } - - /** - * This is an optimization to not tokenize all the files. - * - * @return class-string - */ - private function readKnownClassName(SplFileInfo $file, string $namespace): string - { - $relativeFilename = str_replace( - DIRECTORY_SEPARATOR, - '\\', - substr( - trim(substr($file->getPathname(), strlen(DM_RESONANCE_ROOT)), DIRECTORY_SEPARATOR), - 0, - -4, - ) - ); - - return $this->assertClassExists($namespace.$relativeFilename); - } } diff --git a/src/PHPFileReflectionFunctionIterator.php b/src/PHPFileReflectionFunctionIterator.php index 0dcc6f0706b855421595b960ca08059608d21356..fca7265930dbee57c5751f8a6b6649021ca38b54 100644 --- a/src/PHPFileReflectionFunctionIterator.php +++ b/src/PHPFileReflectionFunctionIterator.php @@ -4,10 +4,12 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use Closure; use Generator; use IteratorAggregate; use LogicException; use ReflectionFunction; +use RuntimeException; use SplFileInfo; /** @@ -50,7 +52,13 @@ readonly class PHPFileReflectionFunctionIterator implements IteratorAggregate */ private function assertFunctionExists(SplFileInfo $file, string $functionName): string { - require_once $file->getPathname(); + $requireClosure = Closure::fromCallable(function (SplFileInfo $file) { + require_once $file->getPathname(); + })->bindTo(null); + + if ($requireClosure) { + $requireClosure($file); + } if (function_exists($functionName)) { return $functionName; @@ -80,6 +88,8 @@ readonly class PHPFileReflectionFunctionIterator implements IteratorAggregate return null; } + $fileBasenameWithoutExtension = $file->getBasename('.php'); + $namespaceSequence = new PHPTokenSequenceMatcher([ 'T_WHITESPACE', 'T_FUNCTION', @@ -98,9 +108,16 @@ readonly class PHPFileReflectionFunctionIterator implements IteratorAggregate $namespaceSequence->pushToken($token); if ($namespaceSequence->isMatching()) { - $functionName = $namespace.'\\'.$namespaceSequence->matchingTokens->get(3)->text; + $baseFunctionName = $namespaceSequence->matchingTokens->get(3)->text; + $functionName = $namespace.'\\'.$baseFunctionName; - return $this->assertFunctionExists($file, $functionName); + if ($fileBasenameWithoutExtension !== $baseFunctionName && strtolower($baseFunctionName) === strtolower($fileBasenameWithoutExtension)) { + throw new RuntimeException('Function name must match the file name: '.$file->getPathname()); + } + + if ($fileBasenameWithoutExtension === $baseFunctionName) { + return $this->assertFunctionExists($file, $functionName); + } } } diff --git a/src/PromptSubjectRequest.php b/src/PromptSubjectRequest.php index 7815b11597343561cfd967162e899110961ab81c..1af1fe87315e572dae0457f05ee3cf29d2449f6c 100644 --- a/src/PromptSubjectRequest.php +++ b/src/PromptSubjectRequest.php @@ -11,5 +11,6 @@ readonly class PromptSubjectRequest { public function __construct( public ?AuthenticatedUser $authenticatedUser, + public string $prompt, ) {} } diff --git a/src/PromptSubjectResponderAggregate.php b/src/PromptSubjectResponderAggregate.php index 5193006f1e67ea67857f1d7ff72307e5d2846025..77beb6b5dc7b6b06a3a65d5469bec74bf25a9a49 100644 --- a/src/PromptSubjectResponderAggregate.php +++ b/src/PromptSubjectResponderAggregate.php @@ -8,6 +8,8 @@ use Distantmagic\Resonance\Attribute\Singleton; use Generator; use Psr\Log\LoggerInterface; +use function Distantmagic\Resonance\helpers\coroutineMustGo; + #[Singleton] readonly class PromptSubjectResponderAggregate { @@ -25,8 +27,10 @@ readonly class PromptSubjectResponderAggregate float $inactivityTimeout = DM_POOL_CONNECTION_TIMEOUT, ): Generator { $subjectActionTokenReader = new SubjectActionTokenReader(); + $prompt = ''; foreach ($completion as $token) { + $prompt .= (string) $token; $subjectActionTokenReader->write($token); if ($subjectActionTokenReader->isUnknown()) { @@ -35,7 +39,7 @@ readonly class PromptSubjectResponderAggregate break; } - if ($token->isFailed) { + if ($token->isFailed()) { yield new PromptSubjectResponseChunk( isFailed: true, isLastChunk: true, @@ -55,6 +59,7 @@ readonly class PromptSubjectResponderAggregate if ($subjectActionTokenReader->isUnknown() || !isset($action, $subject)) { yield from $this->respondWithSubjectAction( $authenticatedUser, + $prompt, 'unknown', 'unknown', $inactivityTimeout, @@ -62,6 +67,7 @@ readonly class PromptSubjectResponderAggregate } else { yield from $this->respondWithSubjectAction( $authenticatedUser, + $prompt, $subject, $action, $inactivityTimeout, @@ -77,6 +83,7 @@ readonly class PromptSubjectResponderAggregate */ private function respondWithSubjectAction( ?AuthenticatedUser $authenticatedUser, + string $prompt, string $subject, string $action, float $inactivityTimeout, @@ -98,10 +105,13 @@ readonly class PromptSubjectResponderAggregate return; } - $request = new PromptSubjectRequest($authenticatedUser); + $request = new PromptSubjectRequest( + authenticatedUser: $authenticatedUser, + prompt: $prompt, + ); $response = new PromptSubjectResponse($inactivityTimeout); - SwooleCoroutineHelper::mustGo(static function () use ($request, $responder, $response) { + coroutineMustGo(static function () use ($request, $responder, $response) { $responder->respondToPromptSubject($request, $response); }); diff --git a/src/SwooleContextRequestResponseReader.php b/src/SwooleContextRequestResponseReader.php index f77c7fc96948cc26a4473d75a7ffafe7995743d6..f25e44aa0c4bc51c9dd40fe2180cb2810c609b95 100644 --- a/src/SwooleContextRequestResponseReader.php +++ b/src/SwooleContextRequestResponseReader.php @@ -8,6 +8,8 @@ use Assert\Assertion; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use function Distantmagic\Resonance\helpers\coroutineMustGetContext; + readonly class SwooleContextRequestResponseReader { public const CONTEXT_KEY_REQUEST = 'psr_http_request'; @@ -27,7 +29,7 @@ readonly class SwooleContextRequestResponseReader return; } - $context = SwooleCoroutineHelper::mustGetContext(); + $context = coroutineMustGetContext(); /** * @var mixed explicitly mixed for typechecks diff --git a/src/SwooleCoroutineHelper.php b/src/SwooleCoroutineHelper.php deleted file mode 100644 index 0ce6a61fa135277caef15d6b83b0edb315692ed4..0000000000000000000000000000000000000000 --- a/src/SwooleCoroutineHelper.php +++ /dev/null @@ -1,94 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Distantmagic\Resonance; - -use RuntimeException; -use Swoole\Coroutine; -use Swoole\Coroutine\Context; -use Throwable; - -use function Swoole\Coroutine\go; -use function Swoole\Coroutine\run; - -final readonly class SwooleCoroutineHelper -{ - public static function mustGetContext(): Context - { - /** - * @var null|Context - */ - $context = Coroutine::getContext(); - - if (is_null($context)) { - throw new RuntimeException('Unable to get coroutine context'); - } - - return $context; - } - - /** - * @param callable() $callback - */ - public static function mustGo(callable $callback): int - { - /** - * @var false|int $cid - */ - $cid = go($callback); - - if (!is_int($cid)) { - throw new RuntimeException('Unable to start a coroutine'); - } - - return $cid; - } - - /** - * @template TReturn - * - * @param callable():TReturn $callback - * - * @return TReturn - */ - public static function mustRun(callable $callback): mixed - { - /** - * @var null|TReturn $ret - */ - $ret = null; - - /** - * Bringing this reference out of the coroutine event loops allows the - * console component to catch that exception and format it. - * - * @var null|Throwable - */ - $exception = null; - - /** - * @var bool - */ - $coroutineResult = run(static function () use ($callback, &$exception, &$ret): void { - try { - $ret = $callback(); - } catch (Throwable $throwable) { - $exception = $throwable; - } - }); - - if (!$coroutineResult) { - throw new RuntimeException('Unable to start coroutine loop'); - } - - if ($exception) { - throw $exception; - } - - /** - * @var TReturn might also be null, so no way to check for that - */ - return $ret; - } -} diff --git a/src/SwooleTimeoutScheduled.php b/src/SwooleTimeoutScheduled.php index d1937073d6aed089886a3a05ada351913ace343f..9cf7317a7c8faa4d4d18bb54f6705c7aeb2ee3a7 100644 --- a/src/SwooleTimeoutScheduled.php +++ b/src/SwooleTimeoutScheduled.php @@ -26,6 +26,9 @@ readonly class SwooleTimeoutScheduled return Timer::clear($this->timeoutId); } + /** + * @psalm-suppress PossiblyUnusedReturnValue used in apps + */ public function reschedule(float $timeout): ?self { if (!$this->cancel()) { diff --git a/src/WebSocketJsonRPCResponder/LlamaCppSubjectActionPromptResponder.php b/src/WebSocketJsonRPCResponder/LlamaCppSubjectActionPromptResponder.php index 3b70ccbd6e2c38d38169d5b1f102a475a130b6ff..63fab044980e16d529e3adb5cc29524f1d276531 100644 --- a/src/WebSocketJsonRPCResponder/LlamaCppSubjectActionPromptResponder.php +++ b/src/WebSocketJsonRPCResponder/LlamaCppSubjectActionPromptResponder.php @@ -12,8 +12,8 @@ use Distantmagic\Resonance\LlamaCppCompletionRequest; use Distantmagic\Resonance\LlmChatHistory; use Distantmagic\Resonance\LlmChatMessage; use Distantmagic\Resonance\LlmPrompt\SubjectActionPrompt; +use Distantmagic\Resonance\ObservableTask; use Distantmagic\Resonance\ObservableTaskCategory; -use Distantmagic\Resonance\ObservableTaskFactory; use Distantmagic\Resonance\ObservableTaskStatus; use Distantmagic\Resonance\ObservableTaskStatusUpdate; use Distantmagic\Resonance\ObservableTaskTable; @@ -84,7 +84,7 @@ abstract readonly class LlamaCppSubjectActionPromptResponder extends WebSocketJs WebSocketConnection $webSocketConnection, JsonRPCRequest $rpcRequest, ): void { - $this->observableTaskTable->observe(ObservableTaskFactory::withTimeout( + $this->observableTaskTable->observe(new ObservableTask( iterableTask: function () use ( $webSocketAuthResolution, $webSocketConnection, @@ -96,7 +96,6 @@ abstract readonly class LlamaCppSubjectActionPromptResponder extends WebSocketJs $rpcRequest, ); }, - inactivityTimeout: 5.0, name: 'websocket_jsonrpc_response', category: ObservableTaskCategory::LlamaCpp->value, )); diff --git a/src/helpers/coroutineMustGetContext.php b/src/helpers/coroutineMustGetContext.php new file mode 100644 index 0000000000000000000000000000000000000000..25d969cfba7686bbf709f425f41275acfd889a11 --- /dev/null +++ b/src/helpers/coroutineMustGetContext.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\helpers; + +use RuntimeException; +use Swoole\Coroutine; +use Swoole\Coroutine\Context; + +function coroutineMustGetContext(): Context +{ + /** + * @var null|Context + */ + $context = Coroutine::getContext(); + + if (is_null($context)) { + throw new RuntimeException('Unable to get coroutine context'); + } + + return $context; +} diff --git a/src/helpers/coroutineMustGo.php b/src/helpers/coroutineMustGo.php new file mode 100644 index 0000000000000000000000000000000000000000..1519b87156350da569ecaf75873fedadb5106a1c --- /dev/null +++ b/src/helpers/coroutineMustGo.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\helpers; + +use RuntimeException; + +/** + * @param callable() $callback + */ +function coroutineMustGo(callable $callback): int +{ + $cid = go($callback); + + if (!is_int($cid)) { + throw new RuntimeException('Unable to start a coroutine'); + } + + return $cid; +} diff --git a/src/helpers/coroutineMustRun.php b/src/helpers/coroutineMustRun.php new file mode 100644 index 0000000000000000000000000000000000000000..dace96dc4618737acedf79672d60e06ca322d33f --- /dev/null +++ b/src/helpers/coroutineMustRun.php @@ -0,0 +1,58 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\helpers; + +use RuntimeException; + +use Throwable; + +use function Swoole\Coroutine\run; + +/** + * @template TReturn + * + * @param callable():TReturn $callback + * + * @return TReturn + */ +function coroutineMustRun(callable $callback): mixed +{ + /** + * @var null|TReturn $ret + */ + $ret = null; + + /** + * Bringing this reference out of the coroutine event loops allows the + * console component to catch that exception and format it. + * + * @var null|Throwable + */ + $exception = null; + + /** + * @var bool + */ + $coroutineResult = run(static function () use ($callback, &$exception, &$ret): void { + try { + $ret = $callback(); + } catch (Throwable $throwable) { + $exception = $throwable; + } + }); + + if (!$coroutineResult) { + throw new RuntimeException('Unable to start coroutine loop'); + } + + if ($exception) { + throw $exception; + } + + /** + * @var TReturn might also be null, so no way to check for that + */ + return $ret; +} diff --git a/src/helpers/generatorGetReturn.php b/src/helpers/generatorGetReturn.php new file mode 100644 index 0000000000000000000000000000000000000000..a269ba5b83d557c7c8e816f801320fc9561e439e --- /dev/null +++ b/src/helpers/generatorGetReturn.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\helpers; + +use Generator; + +/** + * @psalm-suppress UnusedForeachValue we need to consume the iterator + * + * @template TReturn + * + * @param Generator<mixed,mixed,mixed,TReturn> $generator + * + * @return TReturn + */ +function generatorGetReturn(Generator $generator): mixed +{ + /** + * @var mixed $value explicitly mixed for typechecks + */ + foreach ($generator as $value) { + } + + return $generator->getReturn(); +} diff --git a/tools/php-cs-fixer/composer.lock b/tools/php-cs-fixer/composer.lock index 00d221ec872476f45f490c7f77cd1a69e7508577..658ff70337e86b9d0c3c5822e8559e9431a5b923 100644 --- a/tools/php-cs-fixer/composer.lock +++ b/tools/php-cs-fixer/composer.lock @@ -226,16 +226,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.52.1", + "version": "v3.54.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "6e77207f0d851862ceeb6da63e6e22c01b1587bc" + "reference": "2aecbc8640d7906c38777b3dcab6f4ca79004d08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/6e77207f0d851862ceeb6da63e6e22c01b1587bc", - "reference": "6e77207f0d851862ceeb6da63e6e22c01b1587bc", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2aecbc8640d7906c38777b3dcab6f4ca79004d08", + "reference": "2aecbc8640d7906c38777b3dcab6f4ca79004d08", "shasum": "" }, "require": { @@ -259,6 +259,7 @@ }, "require-dev": { "facile-it/paraunit": "^1.3 || ^2.0", + "infection/infection": "^0.27.11", "justinrainbow/json-schema": "^5.2", "keradus/cli-executor": "^2.1", "mikey179/vfsstream": "^1.6.11", @@ -306,7 +307,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.52.1" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.54.0" }, "funding": [ { @@ -314,7 +315,7 @@ "type": "github" } ], - "time": "2024-03-19T21:02:43+00:00" + "time": "2024-04-17T08:12:13+00:00" }, { "name": "psr/container", diff --git a/tools/psalm/composer.lock b/tools/psalm/composer.lock index d4c71d210c6ca5ad07a4d520d52088f12cc2d7b4..3a616aa7c8d4c401037695ae3deae4ac71b2ade3 100644 --- a/tools/psalm/composer.lock +++ b/tools/psalm/composer.lock @@ -97,16 +97,16 @@ }, { "name": "amphp/byte-stream", - "version": "v1.8.1", + "version": "v1.8.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", "shasum": "" }, "require": { @@ -122,11 +122,6 @@ "psalm/phar": "^3.11.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "files": [ "lib/functions.php" @@ -150,7 +145,7 @@ } ], "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "http://amphp.org/byte-stream", + "homepage": "https://amphp.org/byte-stream", "keywords": [ "amp", "amphp", @@ -160,9 +155,8 @@ "stream" ], "support": { - "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" }, "funding": [ { @@ -170,7 +164,7 @@ "type": "github" } ], - "time": "2021-03-30T17:13:30+00:00" + "time": "2024-04-13T18:00:56+00:00" }, { "name": "composer/pcre", @@ -798,28 +792,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", + "version": "5.4.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + "reference": "298d2febfe79d03fe714eb871d5538da55205b1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/298d2febfe79d03fe714eb871d5538da55205b1a", + "reference": "298d2febfe79d03fe714eb871d5538da55205b1a", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.1", "ext-filter": "*", - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" + "mockery/mockery": "~1.3.5", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.13" }, "type": "library", "extra": { @@ -843,15 +844,15 @@ }, { "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "email": "opensource@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.0" }, - "time": "2021-10-19T17:43:47+00:00" + "time": "2024-04-09T21:13:58+00:00" }, { "name": "phpdocumentor/type-resolver",