From f56959d02017f6b0412fa911c6cd82cb5a31a61b Mon Sep 17 00:00:00 2001 From: Mateusz Charytoniuk <mateusz.charytoniuk@protonmail.com> Date: Wed, 3 Apr 2024 15:21:40 +0200 Subject: [PATCH] feat: dialogue nodes --- composer.json | 4 +- composer.lock | 288 +++++++++++++----- .../features/ai/server/llama-cpp/index.md | 8 +- docs/pages/index.md | 2 +- .../index.md | 4 +- .../how-to-serve-llm-completions/index.md | 4 +- phpunit.xml | 2 +- src/Attribute/RespondsToHttp.php | 2 +- src/Command/LlamaCppGenerate.php | 4 +- src/Command/LlamaCppGenerate/Embedding.php | 4 +- src/Command/LlamaCppHealth.php | 4 +- src/Command/LlamaCppInfill.php | 4 +- src/Command/StaticPagesMakeEmbeddings.php | 4 +- src/DialogueInput.php | 7 + src/DialogueInput/UserInput.php | 19 ++ src/DialogueInputInterface.php | 10 + src/DialogueMessageChunk.php | 21 ++ src/DialogueMessageProducer.php | 7 + .../ConstMessageProducer.php | 28 ++ .../ConstMessageProducerTest.php | 34 +++ .../EmptyMessageProducer.php | 23 ++ src/DialogueMessageProducerInterface.php | 12 + src/DialogueNode.php | 37 +++ src/DialogueNodeInterface.php | 14 + src/DialogueNodeTest.php | 62 ++++ src/DialogueResponse.php | 23 ++ src/DialogueResponseCondition.php | 7 + .../ExactInputCondition.php | 25 ++ .../LlamaCppInputCondition.php | 24 ++ src/DialogueResponseConditionInterface.php | 12 + src/DialogueResponseDiscriminator.php | 27 ++ ...DialogueResponseDiscriminatorInterface.php | 16 + src/DialogueResponseInterface.php | 12 + src/DialogueResponseResolution.php | 17 ++ src/DialogueResponseResolutionInterface.php | 10 + src/DialogueResponseResolutionStatus.php | 7 + src/DialogueResponseSortedIterator.php | 54 ++++ src/DialogueResponseSortedIteratorTest.php | 54 ++++ src/EsbuildMetaBuilder.php | 6 +- src/HttpInterceptor.php | 4 +- src/HttpResponder.php | 4 +- src/LlamaCppClient.php | 4 +- src/LlamaCppClientInterface.php | 21 ++ src/PsrStringStream.php | 12 +- src/StaticPageInternalLinkNodeRenderer.php | 8 +- .../LlamaCppSubjectActionPromptResponder.php | 4 +- 46 files changed, 839 insertions(+), 120 deletions(-) create mode 100644 src/DialogueInput.php create mode 100644 src/DialogueInput/UserInput.php create mode 100644 src/DialogueInputInterface.php create mode 100644 src/DialogueMessageChunk.php create mode 100644 src/DialogueMessageProducer.php create mode 100644 src/DialogueMessageProducer/ConstMessageProducer.php create mode 100644 src/DialogueMessageProducer/ConstMessageProducerTest.php create mode 100644 src/DialogueMessageProducer/EmptyMessageProducer.php create mode 100644 src/DialogueMessageProducerInterface.php create mode 100644 src/DialogueNode.php create mode 100644 src/DialogueNodeInterface.php create mode 100644 src/DialogueNodeTest.php create mode 100644 src/DialogueResponse.php create mode 100644 src/DialogueResponseCondition.php create mode 100644 src/DialogueResponseCondition/ExactInputCondition.php create mode 100644 src/DialogueResponseCondition/LlamaCppInputCondition.php create mode 100644 src/DialogueResponseConditionInterface.php create mode 100644 src/DialogueResponseDiscriminator.php create mode 100644 src/DialogueResponseDiscriminatorInterface.php create mode 100644 src/DialogueResponseInterface.php create mode 100644 src/DialogueResponseResolution.php create mode 100644 src/DialogueResponseResolutionInterface.php create mode 100644 src/DialogueResponseResolutionStatus.php create mode 100644 src/DialogueResponseSortedIterator.php create mode 100644 src/DialogueResponseSortedIteratorTest.php create mode 100644 src/LlamaCppClientInterface.php diff --git a/composer.json b/composer.json index 7a5d9ed0..5e96603c 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "dragonmantank/cron-expression": "^3.3", "ezyang/htmlpurifier": "^4.16", "grpc/grpc": "^1.57", + "hyperf/coroutine": "^3.1", "hyperf/grpc-client": "^3.1", "league/commonmark": "^2.4", "league/oauth2-client": "^2.7", @@ -60,7 +61,8 @@ "require-dev": { "phpunit/phpunit": "^11.0", "swoole/ide-helper": "^5.1", - "symfony/var-dumper": "^7.0" + "symfony/var-dumper": "^7.0", + "mockery/mockery": "^1.6" }, "suggest": { "ext-ds": "For better memory management", diff --git a/composer.lock b/composer.lock index 2de4e957..0cafaf88 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6cc5c7a01d9bb04a890e15a3e326d795", + "content-hash": "2e42788f572c165ae18374fbd867a9b7", "packages": [ { "name": "brick/math", @@ -1841,16 +1841,16 @@ }, { "name": "hyperf/code-parser", - "version": "v3.1.4", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/code-parser.git", - "reference": "e36ac337cf7852aaa817db61ade0941d8826f0d8" + "reference": "820e8e6680f0d04e4187a3037a2a3eaf7141913d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/code-parser/zipball/e36ac337cf7852aaa817db61ade0941d8826f0d8", - "reference": "e36ac337cf7852aaa817db61ade0941d8826f0d8", + "url": "https://api.github.com/repos/hyperf/code-parser/zipball/820e8e6680f0d04e4187a3037a2a3eaf7141913d", + "reference": "820e8e6680f0d04e4187a3037a2a3eaf7141913d", "shasum": "" }, "require": { @@ -1902,20 +1902,20 @@ "type": "open_collective" } ], - "time": "2023-12-28T08:46:40+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/collection", - "version": "v3.1.7", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/collection.git", - "reference": "40e25ca34f798e173a1f9a6871d56a6abae43dbb" + "reference": "d0ac957987a704c8b2a16de4333b81f1f56a724a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/collection/zipball/40e25ca34f798e173a1f9a6871d56a6abae43dbb", - "reference": "40e25ca34f798e173a1f9a6871d56a6abae43dbb", + "url": "https://api.github.com/repos/hyperf/collection/zipball/d0ac957987a704c8b2a16de4333b81f1f56a724a", + "reference": "d0ac957987a704c8b2a16de4333b81f1f56a724a", "shasum": "" }, "require": { @@ -1965,20 +1965,20 @@ "type": "open_collective" } ], - "time": "2024-01-26T01:52:03+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/conditionable", - "version": "v3.1.0", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/conditionable.git", - "reference": "18da1405ae39a775bd3fae8cec98841eaa22f013" + "reference": "2c1555891d136904b890ba6d812d9ff50ca13ae3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/conditionable/zipball/18da1405ae39a775bd3fae8cec98841eaa22f013", - "reference": "18da1405ae39a775bd3fae8cec98841eaa22f013", + "url": "https://api.github.com/repos/hyperf/conditionable/zipball/2c1555891d136904b890ba6d812d9ff50ca13ae3", + "reference": "2c1555891d136904b890ba6d812d9ff50ca13ae3", "shasum": "" }, "require": { @@ -2023,20 +2023,20 @@ "type": "open_collective" } ], - "time": "2023-11-24T03:10:53+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/context", - "version": "v3.1.0", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/context.git", - "reference": "8307d6c924ed67c7abd47874ec14f0e2e3e4b732" + "reference": "ad913fd50eb5f738c038e172c120bc6956c0da69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/context/zipball/8307d6c924ed67c7abd47874ec14f0e2e3e4b732", - "reference": "8307d6c924ed67c7abd47874ec14f0e2e3e4b732", + "url": "https://api.github.com/repos/hyperf/context/zipball/ad913fd50eb5f738c038e172c120bc6956c0da69", + "reference": "ad913fd50eb5f738c038e172c120bc6956c0da69", "shasum": "" }, "require": { @@ -2085,20 +2085,20 @@ "type": "open_collective" } ], - "time": "2023-11-24T03:10:53+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/contract", - "version": "v3.1.2", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/contract.git", - "reference": "f5379df6df65363d645506f373888372135ac0c6" + "reference": "9950abe963cc6b30c6d3506fa5b3adbd58cb1945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/contract/zipball/f5379df6df65363d645506f373888372135ac0c6", - "reference": "f5379df6df65363d645506f373888372135ac0c6", + "url": "https://api.github.com/repos/hyperf/contract/zipball/9950abe963cc6b30c6d3506fa5b3adbd58cb1945", + "reference": "9950abe963cc6b30c6d3506fa5b3adbd58cb1945", "shasum": "" }, "require": { @@ -2142,20 +2142,20 @@ "type": "open_collective" } ], - "time": "2023-12-11T03:14:01+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/coroutine", - "version": "v3.1.1", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/coroutine.git", - "reference": "cd5bad67724c5c7a7ad749d8e9eb045470488d75" + "reference": "8f4c573a9457646db3e629dacabe064eebaf8cc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/coroutine/zipball/cd5bad67724c5c7a7ad749d8e9eb045470488d75", - "reference": "cd5bad67724c5c7a7ad749d8e9eb045470488d75", + "url": "https://api.github.com/repos/hyperf/coroutine/zipball/8f4c573a9457646db3e629dacabe064eebaf8cc1", + "reference": "8f4c573a9457646db3e629dacabe064eebaf8cc1", "shasum": "" }, "require": { @@ -2206,7 +2206,7 @@ "type": "open_collective" } ], - "time": "2023-12-01T06:59:45+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/engine", @@ -2357,16 +2357,16 @@ }, { "name": "hyperf/event", - "version": "v3.1.0", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/event.git", - "reference": "6eada5f74ce80786c567d5aed0361d51175217bb" + "reference": "8d008682c028e958197589e90232bb2a1d3c77d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/event/zipball/6eada5f74ce80786c567d5aed0361d51175217bb", - "reference": "6eada5f74ce80786c567d5aed0361d51175217bb", + "url": "https://api.github.com/repos/hyperf/event/zipball/8d008682c028e958197589e90232bb2a1d3c77d9", + "reference": "8d008682c028e958197589e90232bb2a1d3c77d9", "shasum": "" }, "require": { @@ -2420,20 +2420,20 @@ "type": "open_collective" } ], - "time": "2023-11-24T03:10:53+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/grpc", - "version": "v3.1.11", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/grpc.git", - "reference": "63d8c8aac9a9fd7e586aa1298d9928107e6e0e27" + "reference": "7b0424ce1b9af5016e2c8580d6de9eb9c6ec4bed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/grpc/zipball/63d8c8aac9a9fd7e586aa1298d9928107e6e0e27", - "reference": "63d8c8aac9a9fd7e586aa1298d9928107e6e0e27", + "url": "https://api.github.com/repos/hyperf/grpc/zipball/7b0424ce1b9af5016e2c8580d6de9eb9c6ec4bed", + "reference": "7b0424ce1b9af5016e2c8580d6de9eb9c6ec4bed", "shasum": "" }, "require": { @@ -2483,20 +2483,20 @@ "type": "open_collective" } ], - "time": "2024-02-28T03:13:03+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/grpc-client", - "version": "v3.1.7", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/grpc-client.git", - "reference": "ce4cbf105f5f890f43bdd1997b9d69d4d09f5549" + "reference": "4f8f1aeef090d7789020e87a058ed35dcf3fc127" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/grpc-client/zipball/ce4cbf105f5f890f43bdd1997b9d69d4d09f5549", - "reference": "ce4cbf105f5f890f43bdd1997b9d69d4d09f5549", + "url": "https://api.github.com/repos/hyperf/grpc-client/zipball/4f8f1aeef090d7789020e87a058ed35dcf3fc127", + "reference": "4f8f1aeef090d7789020e87a058ed35dcf3fc127", "shasum": "" }, "require": { @@ -2555,20 +2555,20 @@ "type": "open_collective" } ], - "time": "2024-01-22T08:45:43+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/macroable", - "version": "v3.1.0", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/macroable.git", - "reference": "de5b07be74d666f04ecef4ce5ee6ceb97d846cfa" + "reference": "8912b5de69d25451b8ca103e4e47f0935e81072b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/macroable/zipball/de5b07be74d666f04ecef4ce5ee6ceb97d846cfa", - "reference": "de5b07be74d666f04ecef4ce5ee6ceb97d846cfa", + "url": "https://api.github.com/repos/hyperf/macroable/zipball/8912b5de69d25451b8ca103e4e47f0935e81072b", + "reference": "8912b5de69d25451b8ca103e4e47f0935e81072b", "shasum": "" }, "require": { @@ -2613,20 +2613,20 @@ "type": "open_collective" } ], - "time": "2023-11-24T03:10:53+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/stdlib", - "version": "v3.1.0", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/stdlib.git", - "reference": "25b73da235551d0d71d9157324709abaea36c455" + "reference": "636fdc1f15d9357b4747fa649874725f2276b118" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/stdlib/zipball/25b73da235551d0d71d9157324709abaea36c455", - "reference": "25b73da235551d0d71d9157324709abaea36c455", + "url": "https://api.github.com/repos/hyperf/stdlib/zipball/636fdc1f15d9357b4747fa649874725f2276b118", + "reference": "636fdc1f15d9357b4747fa649874725f2276b118", "shasum": "" }, "require": { @@ -2671,20 +2671,20 @@ "type": "open_collective" } ], - "time": "2023-11-24T03:10:53+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/stringable", - "version": "v3.1.13", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/stringable.git", - "reference": "b79f7204c35874a4ea0e917239b74fd230c93e1f" + "reference": "6cbd6f220d833c3f2c28c8263dccffee48021dcb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/stringable/zipball/b79f7204c35874a4ea0e917239b74fd230c93e1f", - "reference": "b79f7204c35874a4ea0e917239b74fd230c93e1f", + "url": "https://api.github.com/repos/hyperf/stringable/zipball/6cbd6f220d833c3f2c28c8263dccffee48021dcb", + "reference": "6cbd6f220d833c3f2c28c8263dccffee48021dcb", "shasum": "" }, "require": { @@ -2742,20 +2742,20 @@ "type": "open_collective" } ], - "time": "2024-03-10T09:19:40+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/support", - "version": "v3.1.13", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/support.git", - "reference": "cd5d284d83d0c031be38e3c5d07f6b96b837a42f" + "reference": "3fb5c6c5a4f795cb0304a032f6f5b85f62a5f872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/support/zipball/cd5d284d83d0c031be38e3c5d07f6b96b837a42f", - "reference": "cd5d284d83d0c031be38e3c5d07f6b96b837a42f", + "url": "https://api.github.com/repos/hyperf/support/zipball/3fb5c6c5a4f795cb0304a032f6f5b85f62a5f872", + "reference": "3fb5c6c5a4f795cb0304a032f6f5b85f62a5f872", "shasum": "" }, "require": { @@ -2812,20 +2812,20 @@ "type": "open_collective" } ], - "time": "2024-03-13T08:47:07+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "hyperf/tappable", - "version": "v3.1.0", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/hyperf/tappable.git", - "reference": "f640e37006dad09ca6f2b9a4cf047907aaebf002" + "reference": "69f22bbc8ecb5b930cc95a49ae9bf0ca0efbfdf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hyperf/tappable/zipball/f640e37006dad09ca6f2b9a4cf047907aaebf002", - "reference": "f640e37006dad09ca6f2b9a4cf047907aaebf002", + "url": "https://api.github.com/repos/hyperf/tappable/zipball/69f22bbc8ecb5b930cc95a49ae9bf0ca0efbfdf1", + "reference": "69f22bbc8ecb5b930cc95a49ae9bf0ca0efbfdf1", "shasum": "" }, "require": { @@ -2873,7 +2873,7 @@ "type": "open_collective" } ], - "time": "2023-11-24T03:10:53+00:00" + "time": "2024-03-23T11:28:51+00:00" }, { "name": "jean85/pretty-package-versions", @@ -7667,6 +7667,140 @@ } ], "packages-dev": [ + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.11", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "81a161d0b135df89951abd52296adf97deb0723d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/81a161d0b135df89951abd52296adf97deb0723d", + "reference": "81a161d0b135df89951abd52296adf97deb0723d", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-03-21T18:34:15+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.11.1", @@ -8227,16 +8361,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.0.8", + "version": "11.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "48ea58408879a9aad630022186398364051482fc" + "reference": "591bbfe416400385527d5086b346b92c06de404b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/48ea58408879a9aad630022186398364051482fc", - "reference": "48ea58408879a9aad630022186398364051482fc", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/591bbfe416400385527d5086b346b92c06de404b", + "reference": "591bbfe416400385527d5086b346b92c06de404b", "shasum": "" }, "require": { @@ -8307,7 +8441,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.0.8" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.0.9" }, "funding": [ { @@ -8323,7 +8457,7 @@ "type": "tidelift" } ], - "time": "2024-03-22T04:21:01+00:00" + "time": "2024-03-28T10:09:42+00:00" }, { "name": "sebastian/cli-parser", diff --git a/docs/pages/docs/features/ai/server/llama-cpp/index.md b/docs/pages/docs/features/ai/server/llama-cpp/index.md index 88047a59..30f63337 100644 --- a/docs/pages/docs/features/ai/server/llama-cpp/index.md +++ b/docs/pages/docs/features/ai/server/llama-cpp/index.md @@ -46,14 +46,14 @@ you are serving. In the following example we will use namespace App; -use Distantmagic\Resonance\LlamaCppClient; +use Distantmagic\Resonance\LlamaCppClientInterface; use Distantmagic\Resonance\LlamaCppCompletionRequest; use Distantmagic\Resonance\LlamaCppPromptTemplate\MistralInstructChat; #[Singleton] class LlamaCppGenerate { - public function __construct(protected LlamaCppClient $llamaCppClient) + public function __construct(protected LlamaCppClientInterface $llamaCppClient) { } @@ -104,13 +104,13 @@ foreach ($completion as $token) { namespace App; -use Distantmagic\Resonance\LlamaCppClient; +use Distantmagic\Resonance\LlamaCppClientInterface; use Distantmagic\Resonance\LlamaCppEmbeddingRequest; #[Singleton] class LlamaCppGenerate { - public function __construct(protected LlamaCppClient $llamaCppClient) + public function __construct(protected LlamaCppClientInterface $llamaCppClient) { } diff --git a/docs/pages/index.md b/docs/pages/index.md index 663aca8f..b165868b 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -109,7 +109,7 @@ readonly class CatAdopt implements PromptSubjectResponderInterface data-hljs-language-value="php" >class LlamaCppGenerate { - public function __construct(protected LlamaCppClient $llamaCppClient) + public function __construct(protected LlamaCppClientInterface $llamaCppClient) { } diff --git a/docs/pages/tutorials/how-to-create-llm-websocket-chat-with-llama-cpp/index.md b/docs/pages/tutorials/how-to-create-llm-websocket-chat-with-llama-cpp/index.md index a02b430c..81406a4d 100644 --- a/docs/pages/tutorials/how-to-create-llm-websocket-chat-with-llama-cpp/index.md +++ b/docs/pages/tutorials/how-to-create-llm-websocket-chat-with-llama-cpp/index.md @@ -212,7 +212,7 @@ use Distantmagic\Resonance\Feature; use Distantmagic\Resonance\JsonRPCNotification; use Distantmagic\Resonance\JsonRPCRequest; use Distantmagic\Resonance\JsonRPCResponse; -use Distantmagic\Resonance\LlamaCppClient; +use Distantmagic\Resonance\LlamaCppClientInterface; use Distantmagic\Resonance\LlamaCppCompletionRequest; use Distantmagic\Resonance\SingletonCollection; use Distantmagic\Resonance\WebSocketAuthResolution; @@ -225,7 +225,7 @@ use Distantmagic\Resonance\WebSocketJsonRPCResponder; final readonly class LlmChatPromptResponder extends WebSocketJsonRPCResponder { public function __construct( - private LlamaCppClient $llamaCppClient, + private LlamaCppClientInterface $llamaCppClient, ) {} public function getConstraint(): Constraint diff --git a/docs/pages/tutorials/how-to-serve-llm-completions/index.md b/docs/pages/tutorials/how-to-serve-llm-completions/index.md index 4305e4a8..09a12072 100644 --- a/docs/pages/tutorials/how-to-serve-llm-completions/index.md +++ b/docs/pages/tutorials/how-to-serve-llm-completions/index.md @@ -111,13 +111,13 @@ inject `LlamaCppClient`: namespace App; -use Distantmagic\Resonance\LlamaCppClient; +use Distantmagic\Resonance\LlamaCppClientInterface; use Distantmagic\Resonance\LlamaCppCompletionRequest; #[Singleton] class LlamaCppGenerate { - public function __construct(protected LlamaCppClient $llamaCppClient) + public function __construct(protected LlamaCppClientInterface $llamaCppClient) { } diff --git a/phpunit.xml b/phpunit.xml index f960f454..3658f468 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -2,7 +2,7 @@ <phpunit beStrictAboutCoverageMetadata="true" beStrictAboutChangesToGlobalState="true" - beStrictAboutTestsThatDoNotTestAnything="false" + beStrictAboutTestsThatDoNotTestAnything="true" bootstrap="phpunit_bootstrap.php" colors="true" displayDetailsOnTestsThatTriggerWarnings="true" diff --git a/src/Attribute/RespondsToHttp.php b/src/Attribute/RespondsToHttp.php index 49890d77..befd9cbe 100644 --- a/src/Attribute/RespondsToHttp.php +++ b/src/Attribute/RespondsToHttp.php @@ -9,7 +9,7 @@ use Distantmagic\Resonance\Attribute as BaseAttribute; use Distantmagic\Resonance\HttpRouteSymbolInterface; use Distantmagic\Resonance\RequestMethod; -#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +#[Attribute(Attribute::TARGET_CLASS)] final readonly class RespondsToHttp extends BaseAttribute { /** diff --git a/src/Command/LlamaCppGenerate.php b/src/Command/LlamaCppGenerate.php index 2fab6a39..fe2468ee 100644 --- a/src/Command/LlamaCppGenerate.php +++ b/src/Command/LlamaCppGenerate.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Command; use Distantmagic\Resonance\CoroutineCommand; -use Distantmagic\Resonance\LlamaCppClient; +use Distantmagic\Resonance\LlamaCppClientInterface; use Distantmagic\Resonance\SwooleConfiguration; use RuntimeException; use Symfony\Component\Console\Input\InputArgument; @@ -20,7 +20,7 @@ abstract class LlamaCppGenerate extends CoroutineCommand abstract protected function executeLlamaCppCommand(InputInterface $input, OutputInterface $output, string $prompt): int; public function __construct( - protected LlamaCppClient $llamaCppClient, + protected LlamaCppClientInterface $llamaCppClient, SwooleConfiguration $swooleConfiguration, ) { parent::__construct($swooleConfiguration); diff --git a/src/Command/LlamaCppGenerate/Embedding.php b/src/Command/LlamaCppGenerate/Embedding.php index 7d5d376a..3a9e0545 100644 --- a/src/Command/LlamaCppGenerate/Embedding.php +++ b/src/Command/LlamaCppGenerate/Embedding.php @@ -8,7 +8,7 @@ use Distantmagic\Resonance\Attribute\ConsoleCommand; use Distantmagic\Resonance\Command; use Distantmagic\Resonance\Command\LlamaCppGenerate; use Distantmagic\Resonance\JsonSerializer; -use Distantmagic\Resonance\LlamaCppClient; +use Distantmagic\Resonance\LlamaCppClientInterface; use Distantmagic\Resonance\LlamaCppEmbeddingRequest; use Distantmagic\Resonance\SwooleConfiguration; use Symfony\Component\Console\Input\InputInterface; @@ -22,7 +22,7 @@ final class Embedding extends LlamaCppGenerate { public function __construct( private readonly JsonSerializer $jsonSerializer, - LlamaCppClient $llamaCppClient, + LlamaCppClientInterface $llamaCppClient, SwooleConfiguration $swooleConfiguration, ) { parent::__construct($llamaCppClient, $swooleConfiguration); diff --git a/src/Command/LlamaCppHealth.php b/src/Command/LlamaCppHealth.php index a5b64759..388391c7 100644 --- a/src/Command/LlamaCppHealth.php +++ b/src/Command/LlamaCppHealth.php @@ -7,7 +7,7 @@ namespace Distantmagic\Resonance\Command; use Distantmagic\Resonance\Attribute\ConsoleCommand; use Distantmagic\Resonance\Command; use Distantmagic\Resonance\CoroutineCommand; -use Distantmagic\Resonance\LlamaCppClient; +use Distantmagic\Resonance\LlamaCppClientInterface; use Distantmagic\Resonance\SwooleConfiguration; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -19,7 +19,7 @@ use Symfony\Component\Console\Output\OutputInterface; final class LlamaCppHealth extends CoroutineCommand { public function __construct( - private readonly LlamaCppClient $llamaCppClient, + private readonly LlamaCppClientInterface $llamaCppClient, SwooleConfiguration $swooleConfiguration, ) { parent::__construct($swooleConfiguration); diff --git a/src/Command/LlamaCppInfill.php b/src/Command/LlamaCppInfill.php index 16f8908e..ecb5222f 100644 --- a/src/Command/LlamaCppInfill.php +++ b/src/Command/LlamaCppInfill.php @@ -8,7 +8,7 @@ use Distantmagic\Resonance\Attribute\ConsoleCommand; use Distantmagic\Resonance\Command; use Distantmagic\Resonance\CoroutineCommand; use Distantmagic\Resonance\JsonSerializer; -use Distantmagic\Resonance\LlamaCppClient; +use Distantmagic\Resonance\LlamaCppClientInterface; use Distantmagic\Resonance\LlamaCppInfillRequest; use Distantmagic\Resonance\SwooleConfiguration; use Symfony\Component\Console\Input\InputInterface; @@ -22,7 +22,7 @@ final class LlamaCppInfill extends CoroutineCommand { public function __construct( private readonly JsonSerializer $jsonSerializer, - private readonly LlamaCppClient $llamaCppClient, + private readonly LlamaCppClientInterface $llamaCppClient, SwooleConfiguration $swooleConfiguration, ) { parent::__construct($swooleConfiguration); diff --git a/src/Command/StaticPagesMakeEmbeddings.php b/src/Command/StaticPagesMakeEmbeddings.php index b948cfc6..9b6f8ad1 100644 --- a/src/Command/StaticPagesMakeEmbeddings.php +++ b/src/Command/StaticPagesMakeEmbeddings.php @@ -9,7 +9,7 @@ use Distantmagic\Resonance\Attribute\WantsFeature; use Distantmagic\Resonance\Command; use Distantmagic\Resonance\Feature; use Distantmagic\Resonance\JsonSerializer; -use Distantmagic\Resonance\LlamaCppClient; +use Distantmagic\Resonance\LlamaCppClientInterface; use Distantmagic\Resonance\LlamaCppEmbeddingRequest; use Distantmagic\Resonance\SQLiteVSSConnectionBuilder; use Distantmagic\Resonance\StaticPageChunkIterator; @@ -29,7 +29,7 @@ final class StaticPagesMakeEmbeddings extends Command public function __construct( private readonly JsonSerializer $jsonSerializer, - private readonly LlamaCppClient $llamaCppClient, + private readonly LlamaCppClientInterface $llamaCppClient, private readonly SQLiteVSSConnectionBuilder $sqliteVSSConnectionBuilder, private readonly StaticPageChunkIterator $staticPageChunkIterator, ) { diff --git a/src/DialogueInput.php b/src/DialogueInput.php new file mode 100644 index 00000000..f22e1acb --- /dev/null +++ b/src/DialogueInput.php @@ -0,0 +1,7 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +abstract readonly class DialogueInput implements DialogueInputInterface {} diff --git a/src/DialogueInput/UserInput.php b/src/DialogueInput/UserInput.php new file mode 100644 index 00000000..10bcb293 --- /dev/null +++ b/src/DialogueInput/UserInput.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\DialogueInput; + +use Distantmagic\Resonance\DialogueInputInterface; + +readonly class UserInput implements DialogueInputInterface +{ + public function __construct( + private string $content, + ) {} + + public function getContent(): string + { + return $this->content; + } +} diff --git a/src/DialogueInputInterface.php b/src/DialogueInputInterface.php new file mode 100644 index 00000000..d68037a5 --- /dev/null +++ b/src/DialogueInputInterface.php @@ -0,0 +1,10 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +interface DialogueInputInterface +{ + public function getContent(): string; +} diff --git a/src/DialogueMessageChunk.php b/src/DialogueMessageChunk.php new file mode 100644 index 00000000..7df4c922 --- /dev/null +++ b/src/DialogueMessageChunk.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use Stringable; + +readonly class DialogueMessageChunk implements Stringable +{ + public function __construct( + public string $content, + public bool $isFailed, + public bool $isLastToken, + ) {} + + public function __toString(): string + { + return $this->content; + } +} diff --git a/src/DialogueMessageProducer.php b/src/DialogueMessageProducer.php new file mode 100644 index 00000000..c4cfacec --- /dev/null +++ b/src/DialogueMessageProducer.php @@ -0,0 +1,7 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +abstract readonly class DialogueMessageProducer implements DialogueMessageProducerInterface {} diff --git a/src/DialogueMessageProducer/ConstMessageProducer.php b/src/DialogueMessageProducer/ConstMessageProducer.php new file mode 100644 index 00000000..4d3a24c8 --- /dev/null +++ b/src/DialogueMessageProducer/ConstMessageProducer.php @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\DialogueMessageProducer; + +use Distantmagic\Resonance\DialogueMessageChunk; +use Distantmagic\Resonance\DialogueMessageProducer; +use Generator; + +readonly class ConstMessageProducer extends DialogueMessageProducer +{ + public function __construct( + public string $content, + ) {} + + /** + * @return Generator<DialogueMessageChunk> + */ + public function getIterator(): Generator + { + yield new DialogueMessageChunk( + content: $this->content, + isFailed: false, + isLastToken: true, + ); + } +} diff --git a/src/DialogueMessageProducer/ConstMessageProducerTest.php b/src/DialogueMessageProducer/ConstMessageProducerTest.php new file mode 100644 index 00000000..bbae0441 --- /dev/null +++ b/src/DialogueMessageProducer/ConstMessageProducerTest.php @@ -0,0 +1,34 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\DialogueMessageProducer; + +use Distantmagic\Resonance\DialogueMessageChunk; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +/** + * @internal + */ +#[CoversClass(ConstMessageProducer::class)] +final class ConstMessageProducerTest extends TestCase +{ + public function test_message_is_produced(): void + { + $inputMessage = 'What is your current role?'; + $messageProducer = new ConstMessageProducer($inputMessage); + + $message = ''; + + foreach ($messageProducer as $messageChunk) { + self::assertInstanceOf(DialogueMessageChunk::class, $messageChunk); + self::assertFalse($messageChunk->isFailed); + self::assertTrue($messageChunk->isLastToken); + + $message .= $messageChunk->content; + } + + self::assertSame($inputMessage, $message); + } +} diff --git a/src/DialogueMessageProducer/EmptyMessageProducer.php b/src/DialogueMessageProducer/EmptyMessageProducer.php new file mode 100644 index 00000000..b3f1fba3 --- /dev/null +++ b/src/DialogueMessageProducer/EmptyMessageProducer.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\DialogueMessageProducer; + +use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\DialogueMessageChunk; +use Distantmagic\Resonance\DialogueMessageProducer; +use Generator; + +#[Singleton] +readonly class EmptyMessageProducer extends DialogueMessageProducer +{ + public function getIterator(): Generator + { + yield new DialogueMessageChunk( + content: '', + isFailed: false, + isLastToken: true, + ); + } +} diff --git a/src/DialogueMessageProducerInterface.php b/src/DialogueMessageProducerInterface.php new file mode 100644 index 00000000..7cdfec89 --- /dev/null +++ b/src/DialogueMessageProducerInterface.php @@ -0,0 +1,12 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use IteratorAggregate; + +/** + * @template-extends IteratorAggregate<DialogueMessageChunk> + */ +interface DialogueMessageProducerInterface extends IteratorAggregate {} diff --git a/src/DialogueNode.php b/src/DialogueNode.php new file mode 100644 index 00000000..5943acf3 --- /dev/null +++ b/src/DialogueNode.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use Ds\Set; + +readonly class DialogueNode implements DialogueNodeInterface +{ + /** + * @var Set<DialogueResponseInterface> + */ + private Set $responses; + + public function __construct( + private DialogueMessageProducerInterface $message, + private DialogueResponseDiscriminatorInterface $responseDiscriminator, + ) { + $this->responses = new Set(); + } + + public function addResponse(DialogueResponseInterface $response): void + { + $this->responses->add($response); + } + + public function getMessageProducer(): DialogueMessageProducerInterface + { + return $this->message; + } + + public function respondTo(DialogueInputInterface $prompt): ?DialogueNodeInterface + { + return $this->responseDiscriminator->discriminate($this->responses, $prompt); + } +} diff --git a/src/DialogueNodeInterface.php b/src/DialogueNodeInterface.php new file mode 100644 index 00000000..875b1c9a --- /dev/null +++ b/src/DialogueNodeInterface.php @@ -0,0 +1,14 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +interface DialogueNodeInterface +{ + public function addResponse(DialogueResponseInterface $response): void; + + public function getMessageProducer(): DialogueMessageProducerInterface; + + public function respondTo(DialogueInputInterface $prompt): ?self; +} diff --git a/src/DialogueNodeTest.php b/src/DialogueNodeTest.php new file mode 100644 index 00000000..64b86b70 --- /dev/null +++ b/src/DialogueNodeTest.php @@ -0,0 +1,62 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use Distantmagic\Resonance\DialogueInput\UserInput; +use Distantmagic\Resonance\DialogueMessageProducer\ConstMessageProducer; +use Distantmagic\Resonance\DialogueResponseCondition\ExactInputCondition; +use Distantmagic\Resonance\DialogueResponseCondition\LlamaCppInputCondition; +use Mockery; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +/** + * @internal + */ +#[CoversClass(DialogueNode::class)] +final class DialogueNodeTest extends TestCase +{ + public function test_dialogue_produces_no_response(): void + { + $responseDiscriminator = new DialogueResponseDiscriminator(); + + $rootNode = new DialogueNode( + message: new ConstMessageProducer('What is your current role?'), + responseDiscriminator: $responseDiscriminator, + ); + + $marketingNode = new DialogueNode( + message: new ConstMessageProducer('Hello, marketer!'), + responseDiscriminator: $responseDiscriminator, + ); + + $rootNode->addResponse(new DialogueResponse( + when: new LlamaCppInputCondition( + Mockery::mock(LlamaCppClientInterface::class), + 'marketing' + ), + followUp: $marketingNode, + )); + + $rootNode->addResponse(new DialogueResponse( + when: new ExactInputCondition('marketing'), + followUp: $marketingNode, + )); + + $invalidNode = new DialogueNode( + message: new ConstMessageProducer('nope :('), + responseDiscriminator: $responseDiscriminator, + ); + + $rootNode->addResponse(new DialogueResponse( + when: new ExactInputCondition('not_a_marketing'), + followUp: $invalidNode, + )); + + $response = $rootNode->respondTo(new UserInput('marketing')); + + self::assertSame($response, $marketingNode); + } +} diff --git a/src/DialogueResponse.php b/src/DialogueResponse.php new file mode 100644 index 00000000..8dfb37d4 --- /dev/null +++ b/src/DialogueResponse.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +readonly class DialogueResponse implements DialogueResponseInterface +{ + public function __construct( + private DialogueNodeInterface $followUp, + private DialogueResponseConditionInterface $when, + ) {} + + public function getCondition(): DialogueResponseConditionInterface + { + return $this->when; + } + + public function getFollowUp(): DialogueNodeInterface + { + return $this->followUp; + } +} diff --git a/src/DialogueResponseCondition.php b/src/DialogueResponseCondition.php new file mode 100644 index 00000000..9196226c --- /dev/null +++ b/src/DialogueResponseCondition.php @@ -0,0 +1,7 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +abstract readonly class DialogueResponseCondition implements DialogueResponseConditionInterface {} diff --git a/src/DialogueResponseCondition/ExactInputCondition.php b/src/DialogueResponseCondition/ExactInputCondition.php new file mode 100644 index 00000000..20449344 --- /dev/null +++ b/src/DialogueResponseCondition/ExactInputCondition.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\DialogueResponseCondition; + +use Distantmagic\Resonance\DialogueInputInterface; +use Distantmagic\Resonance\DialogueResponseCondition; + +readonly class ExactInputCondition extends DialogueResponseCondition +{ + public function __construct( + public string $content, + ) {} + + public function getCost(): int + { + return 2; + } + + public function isMetBy(DialogueInputInterface $dialogueInput): bool + { + return $dialogueInput->getContent() === $this->content; + } +} diff --git a/src/DialogueResponseCondition/LlamaCppInputCondition.php b/src/DialogueResponseCondition/LlamaCppInputCondition.php new file mode 100644 index 00000000..97d99fe5 --- /dev/null +++ b/src/DialogueResponseCondition/LlamaCppInputCondition.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\DialogueResponseCondition; + +use Distantmagic\Resonance\DialogueInputInterface; +use Distantmagic\Resonance\DialogueResponseCondition; +use Distantmagic\Resonance\LlamaCppClientInterface; + +readonly class LlamaCppInputCondition extends DialogueResponseCondition +{ + public function __construct( + public LlamaCppClientInterface $llamaCppClient, + public string $content, + ) {} + + public function getCost(): int + { + return 50; + } + + public function isMetBy(DialogueInputInterface $dialogueInput): bool {} +} diff --git a/src/DialogueResponseConditionInterface.php b/src/DialogueResponseConditionInterface.php new file mode 100644 index 00000000..80e50ff9 --- /dev/null +++ b/src/DialogueResponseConditionInterface.php @@ -0,0 +1,12 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +interface DialogueResponseConditionInterface +{ + public function getCost(): int; + + public function isMetBy(DialogueInputInterface $dialogueInput): bool; +} diff --git a/src/DialogueResponseDiscriminator.php b/src/DialogueResponseDiscriminator.php new file mode 100644 index 00000000..628af587 --- /dev/null +++ b/src/DialogueResponseDiscriminator.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use Distantmagic\Resonance\Attribute\Singleton; + +#[Singleton] +readonly class DialogueResponseDiscriminator implements DialogueResponseDiscriminatorInterface +{ + /** + * @param iterable<DialogueResponseInterface> $responses + */ + public function discriminate( + iterable $responses, + DialogueInputInterface $dialogueInput, + ): ?DialogueNodeInterface { + foreach (new DialogueResponseSortedIterator($responses) as $response) { + if ($response->getCondition()->isMetBy($dialogueInput)) { + return $response->getFollowUp(); + } + } + + return null; + } +} diff --git a/src/DialogueResponseDiscriminatorInterface.php b/src/DialogueResponseDiscriminatorInterface.php new file mode 100644 index 00000000..60002459 --- /dev/null +++ b/src/DialogueResponseDiscriminatorInterface.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +interface DialogueResponseDiscriminatorInterface +{ + /** + * @param iterable<DialogueResponseInterface> $responses + */ + public function discriminate( + iterable $responses, + DialogueInputInterface $dialogueInput, + ): ?DialogueNodeInterface; +} diff --git a/src/DialogueResponseInterface.php b/src/DialogueResponseInterface.php new file mode 100644 index 00000000..df2c6784 --- /dev/null +++ b/src/DialogueResponseInterface.php @@ -0,0 +1,12 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +interface DialogueResponseInterface +{ + public function getCondition(): DialogueResponseConditionInterface; + + public function getFollowUp(): DialogueNodeInterface; +} diff --git a/src/DialogueResponseResolution.php b/src/DialogueResponseResolution.php new file mode 100644 index 00000000..3dcfa0d8 --- /dev/null +++ b/src/DialogueResponseResolution.php @@ -0,0 +1,17 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +readonly class DialogueResponseResolution implements DialogueResponseResolutionInterface +{ + public function __construct( + private DialogueResponseResolutionStatus $status, + ) {} + + public function getStatus(): DialogueResponseResolutionStatus + { + return $this->status; + } +} diff --git a/src/DialogueResponseResolutionInterface.php b/src/DialogueResponseResolutionInterface.php new file mode 100644 index 00000000..6ee9d1b4 --- /dev/null +++ b/src/DialogueResponseResolutionInterface.php @@ -0,0 +1,10 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +interface DialogueResponseResolutionInterface +{ + public function getStatus(): DialogueResponseResolutionStatus; +} diff --git a/src/DialogueResponseResolutionStatus.php b/src/DialogueResponseResolutionStatus.php new file mode 100644 index 00000000..136588fa --- /dev/null +++ b/src/DialogueResponseResolutionStatus.php @@ -0,0 +1,7 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +enum DialogueResponseResolutionStatus {} diff --git a/src/DialogueResponseSortedIterator.php b/src/DialogueResponseSortedIterator.php new file mode 100644 index 00000000..de0aa579 --- /dev/null +++ b/src/DialogueResponseSortedIterator.php @@ -0,0 +1,54 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use Ds\PriorityQueue; +use Ds\Stack; +use Generator; +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate<DialogueResponseInterface> + */ +readonly class DialogueResponseSortedIterator implements IteratorAggregate +{ + /** + * @param iterable<DialogueResponseInterface> $responses + */ + public function __construct( + private iterable $responses, + ) {} + + /** + * @return Generator<DialogueResponseInterface> + */ + public function getIterator(): Generator + { + /** + * @var PriorityQueue<DialogueResponseInterface> $responsesPriorityQueue + */ + $responsesPriorityQueue = new PriorityQueue(); + + foreach ($this->responses as $response) { + $responsesPriorityQueue->push( + $response, + $response->getCondition()->getCost(), + ); + } + + /** + * @var Stack<DialogueResponseInterface> $sortedResponses + */ + $sortedResponses = new Stack(); + + foreach ($responsesPriorityQueue as $response) { + $sortedResponses->push($response); + } + + foreach ($sortedResponses as $response) { + yield $response; + } + } +} diff --git a/src/DialogueResponseSortedIteratorTest.php b/src/DialogueResponseSortedIteratorTest.php new file mode 100644 index 00000000..82fa3b26 --- /dev/null +++ b/src/DialogueResponseSortedIteratorTest.php @@ -0,0 +1,54 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use Distantmagic\Resonance\DialogueMessageProducer\ConstMessageProducer; +use Distantmagic\Resonance\DialogueResponseCondition\ExactInputCondition; +use Distantmagic\Resonance\DialogueResponseCondition\LlamaCppInputCondition; +use Mockery; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +/** + * @internal + */ +#[CoversClass(DialogueResponseSortedIterator::class)] +final class DialogueResponseSortedIteratorTest extends TestCase +{ + public function test_dialogue_responses_are_sorted_by_cost(): void + { + $responseDiscriminator = new DialogueResponseDiscriminator(); + + $marketingNode = new DialogueNode( + message: new ConstMessageProducer('Hello, marketer!'), + responseDiscriminator: $responseDiscriminator, + ); + + $response1 = new DialogueResponse( + when: new LlamaCppInputCondition( + Mockery::mock(LlamaCppClientInterface::class), + 'test' + ), + followUp: $marketingNode, + ); + + $response2 = new DialogueResponse( + when: new ExactInputCondition('marketing'), + followUp: $marketingNode, + ); + + $responses = [ + $response1, + $response2, + ]; + + $sortedResponses = iterator_to_array(new DialogueResponseSortedIterator($responses)); + + self::assertEquals([ + $response2, + $response1, + ], $sortedResponses); + } +} diff --git a/src/EsbuildMetaBuilder.php b/src/EsbuildMetaBuilder.php index 3c872062..91565d5f 100644 --- a/src/EsbuildMetaBuilder.php +++ b/src/EsbuildMetaBuilder.php @@ -151,13 +151,13 @@ readonly class EsbuildMetaBuilder throw new RuntimeException('Esbuild meta manifest is not readable: '.$esbuildMetafile); } - $contents = Coroutine::readFile($esbuildMetafile); + $content = Coroutine::readFile($esbuildMetafile); - if (!is_string($contents)) { + if (!is_string($content)) { throw new RuntimeException('Unable to read esbuild manifest: '.$esbuildMetafile); } - return $contents; + return $content; } private function getEsbuildMetaDecoded(string $esbuildMetafile): object diff --git a/src/HttpInterceptor.php b/src/HttpInterceptor.php index d0aad021..d0f4e5a6 100644 --- a/src/HttpInterceptor.php +++ b/src/HttpInterceptor.php @@ -14,8 +14,8 @@ use Stringable; */ abstract readonly class HttpInterceptor implements HttpInterceptorInterface { - public function createStream(string|Stringable $contents): StreamInterface + public function createStream(string|Stringable $content): StreamInterface { - return new PsrStringStream($contents); + return new PsrStringStream($content); } } diff --git a/src/HttpResponder.php b/src/HttpResponder.php index 85b40c6d..a0004514 100644 --- a/src/HttpResponder.php +++ b/src/HttpResponder.php @@ -9,8 +9,8 @@ use Stringable; abstract readonly class HttpResponder implements HttpResponderInterface { - public function createStream(string|Stringable $contents): StreamInterface + public function createStream(string|Stringable $content): StreamInterface { - return new PsrStringStream($contents); + return new PsrStringStream($content); } } diff --git a/src/LlamaCppClient.php b/src/LlamaCppClient.php index b991ee4e..831878ab 100644 --- a/src/LlamaCppClient.php +++ b/src/LlamaCppClient.php @@ -15,8 +15,8 @@ use Swoole\Coroutine; use Swoole\Coroutine\Channel; #[RequiresPhpExtension('curl')] -#[Singleton] -readonly class LlamaCppClient +#[Singleton(provides: LlamaCppClientInterface::class)] +readonly class LlamaCppClient implements LlamaCppClientInterface { public function __construct( private JsonSerializer $jsonSerializer, diff --git a/src/LlamaCppClientInterface.php b/src/LlamaCppClientInterface.php new file mode 100644 index 00000000..c2ce4b46 --- /dev/null +++ b/src/LlamaCppClientInterface.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance; + +use Generator; + +interface LlamaCppClientInterface +{ + public function generateCompletion(LlamaCppCompletionRequest $request): LlamaCppCompletionIterator; + + public function generateEmbedding(LlamaCppEmbeddingRequest $request): LlamaCppEmbedding; + + /** + * @return Generator<LlamaCppInfill> + */ + public function generateInfill(LlamaCppInfillRequest $request): Generator; + + public function getHealth(): LlamaCppHealthStatus; +} diff --git a/src/PsrStringStream.php b/src/PsrStringStream.php index 8c9dc8a3..94d5b0dd 100644 --- a/src/PsrStringStream.php +++ b/src/PsrStringStream.php @@ -9,16 +9,16 @@ use Stringable; readonly class PsrStringStream implements StreamInterface { - private string $contents; + private string $content; - public function __construct(string|Stringable $contents) + public function __construct(string|Stringable $content) { - $this->contents = (string) $contents; + $this->content = (string) $content; } public function __toString(): string { - return $this->contents; + return $this->content; } public function close(): void {} @@ -32,7 +32,7 @@ readonly class PsrStringStream implements StreamInterface public function getContents(): string { - return $this->contents; + return $this->content; } public function getMetadata($key = null) @@ -42,7 +42,7 @@ readonly class PsrStringStream implements StreamInterface public function getSize(): ?int { - return strlen($this->contents); + return strlen($this->content); } public function isReadable(): bool diff --git a/src/StaticPageInternalLinkNodeRenderer.php b/src/StaticPageInternalLinkNodeRenderer.php index b0ba59a1..9df9cf1c 100644 --- a/src/StaticPageInternalLinkNodeRenderer.php +++ b/src/StaticPageInternalLinkNodeRenderer.php @@ -103,9 +103,9 @@ readonly class StaticPageInternalLinkNodeRenderer implements NodeRendererInterfa private function renderStaticPageBlockLink(StaticPage $staticPage): Stringable { /** - * @list<HtmlElement> $contents + * @list<HtmlElement> $content */ - $contents = [ + $content = [ new HtmlElement( 'div', [ @@ -135,7 +135,7 @@ readonly class StaticPageInternalLinkNodeRenderer implements NodeRendererInterfa ); } - $contents[] = new HtmlElement( + $content[] = new HtmlElement( 'ol', [ 'class' => 'document-links-group__link__tags', @@ -150,7 +150,7 @@ readonly class StaticPageInternalLinkNodeRenderer implements NodeRendererInterfa 'class' => 'document-links-group__link', 'href' => $staticPage->getHref(), ], - $contents, + $content, ); } diff --git a/src/WebSocketJsonRPCResponder/LlamaCppSubjectActionPromptResponder.php b/src/WebSocketJsonRPCResponder/LlamaCppSubjectActionPromptResponder.php index 8c3603de..bf45ed90 100644 --- a/src/WebSocketJsonRPCResponder/LlamaCppSubjectActionPromptResponder.php +++ b/src/WebSocketJsonRPCResponder/LlamaCppSubjectActionPromptResponder.php @@ -6,7 +6,7 @@ namespace Distantmagic\Resonance\WebSocketJsonRPCResponder; use Distantmagic\Resonance\BackusNaurFormGrammar\SubjectActionGrammar; use Distantmagic\Resonance\JsonRPCRequest; -use Distantmagic\Resonance\LlamaCppClient; +use Distantmagic\Resonance\LlamaCppClientInterface; use Distantmagic\Resonance\LlamaCppCompletionIterator; use Distantmagic\Resonance\LlamaCppCompletionRequest; use Distantmagic\Resonance\LlmPrompt\SubjectActionPrompt; @@ -59,7 +59,7 @@ abstract readonly class LlamaCppSubjectActionPromptResponder extends WebSocketJs abstract protected function toPromptTemplate(string $prompt): LlmPromptTemplate; public function __construct( - private LlamaCppClient $llamaCppClient, + private LlamaCppClientInterface $llamaCppClient, private LoggerInterface $logger, private ObservableTaskTable $observableTaskTable, private PromptSubjectResponderAggregate $promptSubjectResponderAggregate, -- GitLab