diff --git a/LICENSE b/LICENSE index 6c37a958ffd68e166da0a58e6d55aaac03f704cb..43c35547190ea7ef95e8b33f47bcfa59de4f0639 100644 --- a/LICENSE +++ b/LICENSE @@ -1,16 +1,18 @@ -Copyright 2023 Distant Magic Mateusz Charytoniuk +The MIT License (MIT) -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the “Softwareâ€), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: +Copyright (c) 2023-2024 Distantmagic Mateusz Charytoniuk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED “AS ISâ€, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER diff --git a/Makefile b/Makefile index 645d4b4f03a6185493916ce64bcb2f4014148eff..ea4baa64fe66340174f5326e9513da86dd4e3b0a 100644 --- a/Makefile +++ b/Makefile @@ -54,8 +54,7 @@ docs/artifact.tar: docs/build docs/build: config.ini esbuild vendor $(MD_SOURCES) $(PHP_SOURCES) ${PHP_BIN} ./bin/resonance.php static-pages:build; - cp resources/images/ogimage.webp docs/build/ogimage.webp; - cp resources/images/favicon.ico docs/build/favicon.ico; + cp resources/images/* docs/build; node_modules: yarn.lock yarnpkg install --check-files --frozen-lockfile --non-interactive; diff --git a/app/Template/StaticPageLayout/Turbo/Tutorial.php b/app/Template/StaticPageLayout/Turbo/Tutorial.php index 16c87f348c21553e8f982ede0a1513a58aa5ad8f..206b29c2f82ec69d5316dc552a70dbb0938cada7 100644 --- a/app/Template/StaticPageLayout/Turbo/Tutorial.php +++ b/app/Template/StaticPageLayout/Turbo/Tutorial.php @@ -132,6 +132,18 @@ final readonly class Tutorial extends Turbo <div class="formatted-content tutorial__readme"> {$renderedOutput} </div> + <div class="formatted-content tutorial__comments"> + <h3>Comments</h3> + <p> + If you want to leave a comment + <a + href="https://github.com/distantmagic/resonance/discussions" + target="_blank" + >Start a discussion on GitHub</a> + or join our + <a href="/community/">Community</a> + </p> + </div> </div> </div> HTML; diff --git a/docs/pages/index.md b/docs/pages/index.md index baac3f89d28adbfea7df0aab65f0030fa3d16403..6445c24771019c6d64203f772d8850184b715f8b 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -42,6 +42,23 @@ description: > <div class="homepage-gallery"> <h3>New Releases</h3> <ul class="homepage-gallery__items"> + <li class="homepage-gallery__item"> + <h4> + <a href="/tutorials/semi-scripted-conversational-applications/"> + Semi-Scripted Conversational Applications + <span class="homepage-gallery__version">tutorial</span> + </a> + </h4> + <p> + Large Language Models, when used as a user interface, enable + users to create advanced applications that can replace most + traditional interfaces. + </p> + <a + class="homepage-gallery__item__learnmore" + href="/tutorials/semi-scripted-conversational-applications/" + >Learn More</a> + </li> <li class="homepage-gallery__item"> <h4> <a href="/docs/features/conversational-applications/dialogue-nodes/"> diff --git a/docs/pages/tutorials/semi-scripted-conversational-applications/index.md b/docs/pages/tutorials/semi-scripted-conversational-applications/index.md new file mode 100644 index 0000000000000000000000000000000000000000..ae0e77bccd581e72a8bffce06902181fca1576a0 --- /dev/null +++ b/docs/pages/tutorials/semi-scripted-conversational-applications/index.md @@ -0,0 +1,275 @@ +--- +layout: dm:tutorial +parent: index +title: Semi-Scripted Conversational Applications +description: > + Large Language Models, when used as a user interface, enable users to + create advanced applications that can replace most traditional interfaces. +--- + +## What Are Conversational Applications? + +Conversational applications are more than just chatbots. Large Language Models, +when used as a user interface, enables users to create advanced applications that +can replace most of the traditional interfaces. + +They can use LLM (Large Language Models) or other means of processing +human input as a front-end. The simplest example is a custom GPT that can use +your API to interact with your application. + +Is it a silver bullet to solve all the problems? Of course not. Currently most +of AI integrations act as copilots or assistants. What if we try to +inverse that dynamic where CUI (Conversational User Interface) is an add-on to +the Graphical User Interface and make GUI components a complimentary feature +instead? + +### How It Started + +Ok, maybe [Zork](https://en.wikipedia.org/wiki/Zork) is not really a +conversational application, but it is really symbolic for me. + +All the interactions with the game are done through a series of specific +commands. That kind of interface is rigorous in its usage because +although it theoretically allows us to input any text, we still have to +guess our options. + +It is strict both on input and output data with no room for interpretation - +like using some scripting programming language. + + +*image source: [lemon64](https://www.lemon64.com/game/zork-1-golden-edition)* + +### How Is It Going + +Several observations: + +1. We have an AI that can recreate the Zork experience in seconds without + requiring almost any human labor. +2. AI understands any input that we can throw at it. +3. AI responds in the style and tone we asked, making the experience + uniquely personalized. + +Now, we can create a conversational application that can +understand any input and provide a unique experience for +each user. It is, however, very general in its use cases - which is both a +blessing and a curse in disguise (see: +[infamous Chevrolet chatbot incident](https://www.reddit.com/r/ChatGPT/comments/18kvlzc/i_gaslit_the_chevrolet_support_bot_into_thinking/)). + + +*image source: [ChatGPT](https://chat.openai.com/)* + +### Where Is It Going? + +What if we could mold a general-use AI to fit our specific applications and use +cases while benefiting from its reasoning and language capabilities? To +be flexible when accepting inputs and simultaneously avoiding hallucinations. +How about building the applications themselves without writing code? + +## Semi-Scripted Dialogues + +Let us meet the AI halfway. We will create a semi-scripted dialogue to list +predefined dialogue nodes and potential responses. The system will accept any +input and try to match it with one of the predefined responses. This allows us +flexibility when accepting inputs and be strict and predictable about our o +utputs. + +### Ingredients + +1. [Swoole](https://swoole.com/) +1. {{docs/features/ai/server/llama-cpp/extractors/index}} + (to interpret the user's input and allow the AI to make further decisions based + on it) +1. {{docs/features/conversational-applications/dialogue-nodes/index}} + (to create a conversational flow) +1. {{docs/features/websockets/index}} (to actually serve the application) + +### What Are We Going to Build? + +We will create a simple CRUD task management system. User will be able to name +a new task, add a description, and list available tasks through a conversational +interface. + +We are going to use an LLM (through {{docs/features/ai/server/llama-cpp/index}}) +to match user inputs with specific conversational nodes. + +The application itself will alternate between handling potential responses from +the user and executing side effects. + +```graphviz render +digraph { + dialogue_node [label="Dialogue Node"]; + response [label="Response"]; + side_effects [label="Side Effects"]; + + dialogue_node -> response; + response -> side_effects; + response -> dialogue_node; +} +``` + +We will model the following conversation in a semi-scripted way so it will be +capable of handling pretty much any user input: + +``` +System: + Message: Hello! How can I help you with your tasks? + Potential Responses: + - I want to create a task + - I want to list all my tasks. +``` + +You can serve it in any way, either as a standalone application, a +[Telegram](https://telegram.org/) bot or a web application. We will focus on +the core of the conversational application and skip the delivery method for +now. + +### Dialogue Controllers + +We are all familiar with HTTP {{docs/features/http/controllers}} who mediate +between the user and the application. Why don't we reuse a similar concept and +create a dialogue controller? + +It should keep the general state of the conversation and be able to react to +user requests. + +First, let us define the initial dialogue node and all the potential responses: + +```php file:app/DialogueCOntroller/TaskConversationController.php +#[Singleton] +readonly class TaskConversationController extends DialogueController +{ + private DialogueNodeInterface $rootNode; + + public function __construct( + private DoctrineEntityManagerRepository $doctrineEntityManagerRepository, + private HelpfulAssistant $helpfulAssistantPersona, + private LlamaCppClientInterface $llamaCppClient, + LlamaCppExtractWhenInterface $llamaCppExtractWhen, + private LlamaCppExtractSubject $llamaCppExtractSubject, + ) { + $this->rootNode = DialogueNode::withMessage('Hello! How can I help you with your tasks?'); + $this->rootNode->addPotentialResponse(new CatchAllResponse( + followUp: $this->welcomeNode, + )); + + $this->welcomeNode->addPotentialResponse(new LlamaCppExtractWhenResponse( + llamaCppExtractWhen: $llamaCppExtractWhen, + condition: 'User wants to create a new task', + persona: $helpfulAssistantPersona, + whenProvided: $this->whenUserWantsToCreateTask(...) + )); + + $this->welcomeNode->addPotentialResponse(new LlamaCppExtractWhenResponse( + llamaCppExtractWhen: $llamaCppExtractWhen, + condition: 'User wants to list all their tasks', + persona: $helpfulAssistantPersona, + whenProvided: $this->whenUserWantsToListAllTasks(...) + )); + } + + public function getRootDialogueNode(): DialogueNodeInterface + { + return $this->rootNode; + } + +// (...) +``` + +Now, we need to handle all the predefined responses. Let's start with the +one to create a new task. Let us try to extract the task name. If that is +successful, let us move forward and add the task; in any other case, ask the +user to specify further: + +```php file:app/DialogueCOntroller/TaskConversationController.php +// (...) + + private function whenUserWantsToCreateTask(LlamaCppExtractWhenResult $response): Generator + { + if ($response->result->isNo()) { + return DialogueResponseResolution::cannotRespond(); + } + + $result = $this->llamaCppExtractSubject->extract( + input: $response->input, + topic: 'task name', + ); + + //user provided a name of the task + if ($result->content) { + $followUp->addSideEffect(new CreateTask( + taskName: $result->content, + )); + + $followUp = new DialogueNode(new LlamaCppPromptMessageProducer( + llamaCppClient: $this->llamaCppClient, + request: new LlamaCppCompletionRequest( + llmChatHistory: new LlmChatHistory([ + new LlmChatMessage( + actor: 'system', + message: 'Let user know that their task is added', + ), + ]), + ), + )); + } else { + $followUp = new DialogueNode(new LlamaCppPromptMessageProducer( + llamaCppClient: $this->llamaCppClient, + request: new LlamaCppCompletionRequest( + llmChatHistory: new LlmChatHistory([ + new LlmChatMessage( + actor: 'system', + message: 'Ask user to specify the name of the task', + ), + ]), + ), + )); + } + + // Go back to the primary dialogue node in any case + $followUp->copyResponsesFrom($this->rootNode); + + return DialogueResponseResolution::canRespond($followUp); + } + +// (...) +``` + +When a user wants to list all tasks, just produce a response in a similar way +you would produce an HTML view: + +```php file:app/DialogueCOntroller/TaskConversationController.php +// (...) + + private function whenUserWantsToCreateTask(LlamaCppExtractWhenResult $response): Generator + { + if ($response->result->isNo()) { + return DialogueResponseResolution::cannotRespond(); + } + + $responseMessage = "Your tasks:\n%s"; + + $tasks = $this + ->doctrineEntityManagerRepository + ->withEntityManager(function (EntityManagerInterface $entityManager) { + // ...obtain user tasks somehow + }) + ; + + foreach ($tasks as $task) { + $responseMessage .= "- {$task->name}\n"; + } + + $followUp = DialogueNode::withMessage($responseMessage); + + // Go back to the primary dialogue node in any case + $followUp->copyResponsesFrom($this->rootNode); + + return DialogueResponseResolution::canRespond($followUp); + } +} +``` + +## Summary + +It was a simple example of a conversational application; it's like writing a +basic "Hello, world!". I hope it inspires you to create something incredible! diff --git a/resources/css/docs-formatted-content.css b/resources/css/docs-formatted-content.css index fd40881bbbaad69a36648dfdc66bfb91e1e990ff..44f8ef223d402ff43d83d48ff5a9e3f3327e5f52 100644 --- a/resources/css/docs-formatted-content.css +++ b/resources/css/docs-formatted-content.css @@ -156,6 +156,17 @@ width: 100%; } + img + em { + align-items: center; + column-gap: 1ch; + display: flex; + flex-direction: row; + font-size: var(--font-size-smaller); + justify-content: center; + margin-top: -24px; + text-align: center; + } + ol:not([class]), ul:not([class]) { display: flex; diff --git a/resources/css/docs-page-homepage.css b/resources/css/docs-page-homepage.css index 483f41802348a0d6e8a6e9018a408146f560a964..f6579c8f27cc2793f1a7492d35fb446f81921234 100644 --- a/resources/css/docs-page-homepage.css +++ b/resources/css/docs-page-homepage.css @@ -58,6 +58,7 @@ h4 a { align-items: center; border-bottom: 1px solid var(--color-border); + column-gap: 1ch; display: flex; font-weight: bold; justify-content: space-between; diff --git a/resources/css/docs-page-tutorial.css b/resources/css/docs-page-tutorial.css index e122bbc158b5e8f1b8dc6ea99509040a12f3cbc4..7a34ccad1d99c277310f695eab90d1ad1971af39 100644 --- a/resources/css/docs-page-tutorial.css +++ b/resources/css/docs-page-tutorial.css @@ -29,6 +29,15 @@ max-width: 100vw; } +.tutorial__comments { + border-top: 1px solid var(--color-border); + display: flex; + flex-direction: column; + margin: 80px 0 1em 0; + padding: 20px 0; + row-gap: 1em; +} + .tutorial__content { display: grid; padding-bottom: 60px; diff --git a/resources/images/chatgpt.webp b/resources/images/chatgpt.webp new file mode 100644 index 0000000000000000000000000000000000000000..f96a7b302a8d575a80fb6fe21c4ec6263c804c54 Binary files /dev/null and b/resources/images/chatgpt.webp differ diff --git a/resources/images/intentt_background_small.webp b/resources/images/intentt_background_small.webp deleted file mode 100644 index 55dc7d43faec34d025828469996b4146045a9841..0000000000000000000000000000000000000000 Binary files a/resources/images/intentt_background_small.webp and /dev/null differ diff --git a/resources/images/zork.png b/resources/images/zork.png new file mode 100644 index 0000000000000000000000000000000000000000..35dc3bf2e6c9fda7f7dc224ff4213a371cfc142b Binary files /dev/null and b/resources/images/zork.png differ