From eff218defbcfcc025cdbaa3e9c70b8ba3bf03a83 Mon Sep 17 00:00:00 2001 From: Pavel Buchnev Date: Sun, 1 Sep 2024 18:06:22 +0400 Subject: [PATCH] Adds chat service and agents --- .env.example | 15 +- Makefile | 16 + README.md | 204 ++- app/Agents/AgentsCaller/AskAgentInput.php | 19 + app/Agents/AgentsCaller/AskAgentTool.php | 74 + app/Agents/CodeReviewer/CodeReviewAgent.php | 65 + .../CodeReviewer/CodeReviewAgentFactory.php | 16 + app/Agents/CodeReviewer/ListProjectInput.php | 15 + app/Agents/CodeReviewer/ListProjectTool.php | 30 + app/Agents/CodeReviewer/ReadFileInput.php | 15 + app/Agents/CodeReviewer/ReadFileTool.php | 87 + app/Agents/CodeReviewer/ReviewInput.php | 19 + app/Agents/CodeReviewer/ReviewTool.php | 30 + app/Agents/Delivery/DeliveryAgent.php | 102 ++ app/Agents/Delivery/DeliveryAgentFactory.php | 17 + app/Agents/Delivery/DeliveryDateInput.php | 15 + app/Agents/Delivery/GetDeliveryDateTool.php | 32 + app/Agents/Delivery/GetOrderNumberTool.php | 33 + app/Agents/Delivery/GetProfileTool.php | 34 + app/Agents/Delivery/OrderNumberInput.php | 15 + app/Agents/Delivery/ProfileInput.php | 15 + app/Agents/Delivery/StatusCheckOutput.php | 19 + .../DynamicMemoryTool/DynamicMemoryInput.php | 17 + .../DynamicMemoryService.php | 46 + .../DynamicMemoryTool/DynamicMemoryTool.php | 39 + app/Agents/DynamicMemoryTool/Memories.php | 28 + app/Agents/PhpTool.php | 20 + .../SmartHomeControl/ControlDeviceInput.php | 25 + .../SmartHomeControl/ControlDeviceTool.php | 41 + app/Agents/SmartHomeControl/DeviceAction.php | 18 + app/Agents/SmartHomeControl/DeviceParam.php | 17 + .../GetDeviceDetailsInput.php | 15 + .../SmartHomeControl/GetDeviceDetailsTool.php | 44 + .../SmartHomeControl/GetRoomListInput.php | 17 + .../SmartHomeControl/GetRoomListTool.php | 35 + .../SmartHomeControl/ListRoomDevicesInput.php | 15 + .../SmartHomeControl/ListRoomDevicesTool.php | 48 + .../SmartHome/Devices/Light.php | 80 + .../SmartHome/Devices/SmartAppliance.php | 128 ++ .../SmartHome/Devices/SmartDevice.php | 47 + .../SmartHomeControl/SmartHome/Devices/TV.php | 78 + .../SmartHome/Devices/Thermostat.php | 78 + .../SmartHome/SmartHomeSystem.php | 83 + .../SmartHomeControlAgent.php | 112 ++ .../SmartHomeControlAgentFactory.php | 16 + .../TaskSplitter/GetProjectDescription.php | 60 + .../TaskSplitter/ProjectDescriptionInput.php | 15 + app/Agents/TaskSplitter/TaskCreateInput.php | 24 + app/Agents/TaskSplitter/TaskCreateTool.php | 67 + app/Agents/TaskSplitter/TaskSplitterAgent.php | 69 + .../TaskSplitter/TaskSplitterAgentFactory.php | 16 + app/Chat/ChatHistoryRepository.php | 38 + app/Chat/SimpleChatService.php | 209 +++ app/Console/Commands/AgentListCommand.php | 30 + app/Console/Commands/ChatSessionCommand.php | 44 + app/Console/Commands/ChatWindowCommand.php | 29 + app/Console/Commands/ToolListCommand.php | 22 + app/Event/ChatEventsListener.php | 73 + app/Models/Casts/History.php | 28 + app/Models/Casts/Uuid.php | 21 + app/Models/Session.php | 60 + app/Models/ValueObject/History.php | 48 + app/Providers/AgentsChatServiceProvider.php | 31 + app/Providers/AgentsServiceProvider.php | 51 + app/Providers/AppServiceProvider.php | 15 +- app/Providers/SmartHomeServiceProvider.php | 122 ++ bootstrap/providers.php | 4 + composer.json | 10 +- composer.lock | 1561 +++++++++++++---- config/agents.php | 53 + config/openai.php | 28 + ...2024_09_01_072614_create_session_table.php | 33 + docker-compose.yml | 56 + phpunit.xml | 3 +- 74 files changed, 4363 insertions(+), 391 deletions(-) create mode 100644 Makefile create mode 100644 app/Agents/AgentsCaller/AskAgentInput.php create mode 100644 app/Agents/AgentsCaller/AskAgentTool.php create mode 100644 app/Agents/CodeReviewer/CodeReviewAgent.php create mode 100644 app/Agents/CodeReviewer/CodeReviewAgentFactory.php create mode 100644 app/Agents/CodeReviewer/ListProjectInput.php create mode 100644 app/Agents/CodeReviewer/ListProjectTool.php create mode 100644 app/Agents/CodeReviewer/ReadFileInput.php create mode 100644 app/Agents/CodeReviewer/ReadFileTool.php create mode 100644 app/Agents/CodeReviewer/ReviewInput.php create mode 100644 app/Agents/CodeReviewer/ReviewTool.php create mode 100644 app/Agents/Delivery/DeliveryAgent.php create mode 100644 app/Agents/Delivery/DeliveryAgentFactory.php create mode 100644 app/Agents/Delivery/DeliveryDateInput.php create mode 100644 app/Agents/Delivery/GetDeliveryDateTool.php create mode 100644 app/Agents/Delivery/GetOrderNumberTool.php create mode 100644 app/Agents/Delivery/GetProfileTool.php create mode 100644 app/Agents/Delivery/OrderNumberInput.php create mode 100644 app/Agents/Delivery/ProfileInput.php create mode 100644 app/Agents/Delivery/StatusCheckOutput.php create mode 100644 app/Agents/DynamicMemoryTool/DynamicMemoryInput.php create mode 100644 app/Agents/DynamicMemoryTool/DynamicMemoryService.php create mode 100644 app/Agents/DynamicMemoryTool/DynamicMemoryTool.php create mode 100644 app/Agents/DynamicMemoryTool/Memories.php create mode 100644 app/Agents/PhpTool.php create mode 100644 app/Agents/SmartHomeControl/ControlDeviceInput.php create mode 100644 app/Agents/SmartHomeControl/ControlDeviceTool.php create mode 100644 app/Agents/SmartHomeControl/DeviceAction.php create mode 100644 app/Agents/SmartHomeControl/DeviceParam.php create mode 100644 app/Agents/SmartHomeControl/GetDeviceDetailsInput.php create mode 100644 app/Agents/SmartHomeControl/GetDeviceDetailsTool.php create mode 100644 app/Agents/SmartHomeControl/GetRoomListInput.php create mode 100644 app/Agents/SmartHomeControl/GetRoomListTool.php create mode 100644 app/Agents/SmartHomeControl/ListRoomDevicesInput.php create mode 100644 app/Agents/SmartHomeControl/ListRoomDevicesTool.php create mode 100644 app/Agents/SmartHomeControl/SmartHome/Devices/Light.php create mode 100644 app/Agents/SmartHomeControl/SmartHome/Devices/SmartAppliance.php create mode 100644 app/Agents/SmartHomeControl/SmartHome/Devices/SmartDevice.php create mode 100644 app/Agents/SmartHomeControl/SmartHome/Devices/TV.php create mode 100644 app/Agents/SmartHomeControl/SmartHome/Devices/Thermostat.php create mode 100644 app/Agents/SmartHomeControl/SmartHome/SmartHomeSystem.php create mode 100644 app/Agents/SmartHomeControl/SmartHomeControlAgent.php create mode 100644 app/Agents/SmartHomeControl/SmartHomeControlAgentFactory.php create mode 100644 app/Agents/TaskSplitter/GetProjectDescription.php create mode 100644 app/Agents/TaskSplitter/ProjectDescriptionInput.php create mode 100644 app/Agents/TaskSplitter/TaskCreateInput.php create mode 100644 app/Agents/TaskSplitter/TaskCreateTool.php create mode 100644 app/Agents/TaskSplitter/TaskSplitterAgent.php create mode 100644 app/Agents/TaskSplitter/TaskSplitterAgentFactory.php create mode 100644 app/Chat/ChatHistoryRepository.php create mode 100644 app/Chat/SimpleChatService.php create mode 100644 app/Console/Commands/AgentListCommand.php create mode 100644 app/Console/Commands/ChatSessionCommand.php create mode 100644 app/Console/Commands/ChatWindowCommand.php create mode 100644 app/Console/Commands/ToolListCommand.php create mode 100644 app/Event/ChatEventsListener.php create mode 100644 app/Models/Casts/History.php create mode 100644 app/Models/Casts/Uuid.php create mode 100644 app/Models/Session.php create mode 100644 app/Models/ValueObject/History.php create mode 100644 app/Providers/AgentsChatServiceProvider.php create mode 100644 app/Providers/AgentsServiceProvider.php create mode 100644 app/Providers/SmartHomeServiceProvider.php create mode 100644 config/agents.php create mode 100644 config/openai.php create mode 100644 database/migrations/2024_09_01_072614_create_session_table.php create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example index 2a4a8b7..a33da90 100644 --- a/.env.example +++ b/.env.example @@ -19,12 +19,12 @@ LOG_STACK=single LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug -DB_CONNECTION=sqlite -# DB_HOST=127.0.0.1 -# DB_PORT=3306 -# DB_DATABASE=laravel -# DB_USERNAME=root -# DB_PASSWORD= +DB_CONNECTION=pgsql +DB_HOST=pgsql +DB_PORT=5432 +DB_DATABASE=laravel +DB_USERNAME=sail +DB_PASSWORD=password SESSION_DRIVER=database SESSION_LIFETIME=120 @@ -62,3 +62,6 @@ AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false VITE_APP_NAME="${APP_NAME}" + +OPENAI_API_KEY= +OPENAI_ORGANIZATION= diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8a4bcf4 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +up: + ./vendor/bin/sail up -d + ./vendor/bin/sail exec laravel.test php artisan migrate + +down: + ./vendor/bin/sail down + +restart: + ./vendor/bin/sail down + ./vendor/bin/sail up -d + +chat: + ./vendor/bin/sail artisan chat + +bash: + ./vendor/bin/sail exec laravel.test /bin/bash diff --git a/README.md b/README.md index 1a4c26b..2a30e08 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,182 @@ -

Laravel Logo

+# LLM Agents Sample App - Laravel edition -

-Build Status -Total Downloads -Latest Stable Version -License -

+This sample application demonstrates the practical implementation and usage patterns of the LLM Agents library. -## About Laravel +> For more information about the LLM Agents package and its capabilities, please refer to +> the [LLM Agents documentation](https://github.com/llm-agents-php/agents). -Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: +It provides a CLI interface to interact with various AI agents, showcasing the power and flexibility of the LLM Agents +package. -- [Simple, fast routing engine](https://laravel.com/docs/routing). -- [Powerful dependency injection container](https://laravel.com/docs/container). -- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. -- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). -- Database agnostic [schema migrations](https://laravel.com/docs/migrations). -- [Robust background job processing](https://laravel.com/docs/queues). -- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). +![image](https://github.com/user-attachments/assets/53104067-d3df-4983-8a59-435708f2b70c) -Laravel is accessible, powerful, and provides tools required for large, robust applications. +## Features -## Learning Laravel +- Multiple pre-configured AI agents with different capabilities +- CLI interface for easy interaction with agents +- Integration with OpenAI's GPT models +- Database support for session persistence -Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. +## Prerequisites -You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. +- PHP 8.3 or higher +- Composer +- Git +- OpenAI API key -If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. +## Quick Start with Docker -## Laravel Sponsors +The easiest way to run the app is using our pre-built Docker image. -We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). +**Follow these steps to get started:** -### Premium Partners +1. Make sure you have Docker installed on your system. -- **[Vehikl](https://vehikl.com/)** -- **[Tighten Co.](https://tighten.co)** -- **[WebReinvent](https://webreinvent.com/)** -- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** -- **[64 Robots](https://64robots.com)** -- **[Curotec](https://www.curotec.com/services/technologies/laravel/)** -- **[Cyber-Duck](https://cyber-duck.co.uk)** -- **[DevSquad](https://devsquad.com/hire-laravel-developers)** -- **[Jump24](https://jump24.co.uk)** -- **[Redberry](https://redberry.international/laravel/)** -- **[Active Logic](https://activelogic.com)** -- **[byte5](https://byte5.de)** -- **[OP.GG](https://op.gg)** +2. Create a `.env` file in the project root directory: -## Contributing +```bash +cp .env.example .env +``` + +and add your OpenAI API key to the `.env` file: + +```bash +OPENAI_API_KEY=your_api_key_here +``` + +> Replace `` with your OpenAI API key. + +3. Run the Docker container with the following command: + +```bash +make up +``` + +4Once the container is running, you can interact with the app using the following command: + +## Usage + +### Chatting with Agents + +To start a chat session with an AI agent: + +1. Run the following command: + +**Using docker container** + +```bash +make chat +``` + +2. You will see a list of available agents and their descriptions. Choose the desired agent by entering its number. + +![image](https://github.com/user-attachments/assets/3cd223a8-3ab0-4879-9e85-83539c93003f) + +3. After selecting an agent, you will see a message like this: + +![image](https://github.com/user-attachments/assets/0d18ca6c-9ee9-4942-b383-fc42abf18bc7) + +```bash +************************************************************ +* Run the following command to see the AI response * +************************************************************ + +php artisan chat:session -v +``` + +**Using docker container** -Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). +```bash +make bash +``` -## Code of Conduct +Then run the following command: -In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). +```bash +php artisan chat:session -v +``` -## Security Vulnerabilities +> Replace `` with the actual session UUID. + +5. Copy the provided command and run it in a new terminal tab. This command will show the AI response to your message. + +![image](https://github.com/user-attachments/assets/1dfdfdd1-f69d-44af-afb2-807f9fa2da84) + +## Available CLI Commands + +The sample app provides several CLI commands for interacting with agents and managing the application: + +- `php artisan agent:list`: List all available agents +- `php artisan tool:list`: List all available tools +- `php artisan chat`: Start a new chat session +- `php artisan chat:session `: Continue an existing chat session + +Use the `-h` or `--help` option with any command to see more details about its usage. + +## Available Agents + +The sample app comes with several pre-configured agents, each designed for specific tasks: + +### Site Status Checker + +- **Key**: `site_status_checker` +- **Description**: This agent specializes in checking the online status of websites. It can verify if a given URL is + accessible, retrieve basic information about the site, and provide insights on potential issues if a site is + offline. +- **Capabilities**: + - Check site availability + - Retrieve DNS information + - Perform ping tests + - Provide troubleshooting steps for offline sites + +### Order Assistant + +- **Key**: `order_assistant` +- **Description**: This agent helps customers with order-related questions. It can retrieve order information, check + delivery status, and provide customer support for e-commerce related queries. +- **Capabilities**: + - Retrieve order numbers + - Check delivery dates + - Access customer profiles + - Provide personalized assistance based on customer age and preferences + +### Smart Home Control Assistant + +- **Key**: `smart_home_control` +- **Description**: This agent manages and controls various smart home devices across multiple rooms, including + lights, thermostats, and TVs. +- **Capabilities**: + - List devices in specific rooms + - Control individual devices (turn on/off, adjust settings) + - Retrieve device status and details + - Suggest energy-efficient settings + +### Code Review Agent + +- **Key**: `code_review` +- **Description**: This agent specializes in reviewing code. It can analyze code files, provide feedback, and + suggest improvements. +- **Capabilities**: + - List files in a project + - Read file contents + - Perform code reviews + - Submit review comments + +### Task Splitter + +- **Key**: `task_splitter` +- **Description**: This agent analyzes project descriptions and breaks them down into structured task lists with + subtasks. +- **Capabilities**: + - Retrieve project descriptions + - Create hierarchical task structures + - Assign task priorities + - Generate detailed subtasks + +## Contributing -If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. +Contributions are welcome! Please feel free to submit a Pull Request. ## License -The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). +This sample app is open-source software licensed under the MIT license. diff --git a/app/Agents/AgentsCaller/AskAgentInput.php b/app/Agents/AgentsCaller/AskAgentInput.php new file mode 100644 index 0000000..2fa6da6 --- /dev/null +++ b/app/Agents/AgentsCaller/AskAgentInput.php @@ -0,0 +1,19 @@ + + */ +final class AskAgentTool extends PhpTool +{ + public const NAME = 'ask_agent'; + + public function __construct( + private readonly AgentExecutor $executor, + private readonly ToolExecutor $toolExecutor, + ) { + parent::__construct( + name: self::NAME, + inputSchema: AskAgentInput::class, + description: 'Ask an agent with given name to execute a task.', + ); + } + + public function execute(object $input): string|\Stringable + { + $prompt = \sprintf( + <<<'PROMPT' +%s +Important rules: +- Think before responding to the user. +- Don not markup the content. Only JSON is allowed. +- Don't write anything except the answer using JSON schema. +- Answer in JSON using this schema: +%s +PROMPT + , + $input->question, + $input->outputSchema, + ); + + // TODO: make async + while (true) { + $execution = $this->executor->execute($input->name, $prompt); + $result = $execution->result; + $prompt = $execution->prompt; + + if ($result instanceof ToolCalledResponse) { + foreach ($result->tools as $tool) { + $functionResult = $this->toolExecutor->execute($tool->name, $tool->arguments); + + $prompt = $prompt->withAddedMessage( + new ToolCallResultMessage( + id: $tool->id, + content: [$functionResult], + ), + ); + } + + continue; + } + + break; + } + + return \json_encode($result->content); + } +} diff --git a/app/Agents/CodeReviewer/CodeReviewAgent.php b/app/Agents/CodeReviewer/CodeReviewAgent.php new file mode 100644 index 0000000..4013679 --- /dev/null +++ b/app/Agents/CodeReviewer/CodeReviewAgent.php @@ -0,0 +1,65 @@ +addMetadata( + new SolutionMetadata( + type: MetadataType::Memory, + key: 'user_submitted_code_review', + content: 'Always submit code reviews using proper tool.', + ), + new SolutionMetadata( + type: MetadataType::Memory, + key: 'code_review_tip', + content: 'Always submit constructive feedback and suggestions for improvement in your code reviews.', + ), + new SolutionMetadata( + type: MetadataType::Memory, + key: 'customer_name', + content: 'If you know the customer name say hello to them.', + ), + new SolutionMetadata( + type: MetadataType::Configuration, + key: Option::MaxTokens->value, + content: 3000, + ), + ); + + $model = new Model(model: OpenAIModel::Gpt4oMini->value); + $aggregate->addAssociation($model); + + $aggregate->addAssociation(new ToolLink(name: ListProjectTool::NAME)); + $aggregate->addAssociation(new ToolLink(name: ReadFileTool::NAME)); + $aggregate->addAssociation(new ToolLink(name: ReviewTool::NAME)); + + return $aggregate; + } +} diff --git a/app/Agents/CodeReviewer/CodeReviewAgentFactory.php b/app/Agents/CodeReviewer/CodeReviewAgentFactory.php new file mode 100644 index 0000000..4ecd4a4 --- /dev/null +++ b/app/Agents/CodeReviewer/CodeReviewAgentFactory.php @@ -0,0 +1,16 @@ + + */ +final class ListProjectTool extends PhpTool +{ + public const NAME = 'list_project'; + + public function __construct() + { + parent::__construct( + name: self::NAME, + inputSchema: ListProjectInput::class, + description: 'List all files in a project with the given project name.', + ); + } + + public function execute(object $input): string + { + // Implementation to list project files + return json_encode(['files' => ['file1.php', 'file2.php']]); + } +} diff --git a/app/Agents/CodeReviewer/ReadFileInput.php b/app/Agents/CodeReviewer/ReadFileInput.php new file mode 100644 index 0000000..539ffc8 --- /dev/null +++ b/app/Agents/CodeReviewer/ReadFileInput.php @@ -0,0 +1,15 @@ + + */ +final class ReadFileTool extends PhpTool +{ + public const NAME = 'read_file'; + + public function __construct() + { + parent::__construct( + name: self::NAME, + inputSchema: ReadFileInput::class, + description: 'Read the contents of a file at the given path.', + ); + } + + public function execute(object $input): string + { + if ($input->path === 'file1.php') { + return json_encode([ + 'content' => <<<'PHP' +class ReviewTool extends \App\Domain\Tool\Tool +{ + public function __construct() + { + parent::__construct( + name: 'review' + inputSchema: ReviewInput::class, + description: 'Submit a code review for a file at the given path.', + ); + } + + public function getLanguage(): \App\Domain\Tool\ToolLanguage + { + return \App\Domain\Tool\ToolLanguage::PHP; + } + + public function execute(object $input): string + { + // Implementation to submit code review + return json_encode(['status' => 'success', 'message' => 'Code review submitted']); + } +} +PHP, + ]); + } + + if ($input->path === 'file2.php') { + return json_encode([ + 'content' => <<<'PHP' +class ReadFileTool extends \App\Domain\Tool\Tool +{ + public function __construct() + { + parent::__construct( + name: 'read_file', + inputSchema: ReadFileInput::class, + description: 'Read the contents of a file at the given path.', + ); + } + + public function getLanguage(): \App\Domain\Tool\ToolLanguage + { + return \App\Domain\Tool\ToolLanguage:PHP; + } + + public function execute(object $input): string + { + // Implementation to read file contents + return json_encode(['content' => 'File contents here']); + } +} +PHP, + ]); + } + + return 'File not found'; + } +} diff --git a/app/Agents/CodeReviewer/ReviewInput.php b/app/Agents/CodeReviewer/ReviewInput.php new file mode 100644 index 0000000..7ab50fc --- /dev/null +++ b/app/Agents/CodeReviewer/ReviewInput.php @@ -0,0 +1,19 @@ + + */ +final class ReviewTool extends PhpTool +{ + public const NAME = 'submit_review'; + + public function __construct() + { + parent::__construct( + name: self::NAME, + inputSchema: ReviewInput::class, + description: 'Submit a code review for a pull request. Call this whenever you need to submit a code review for a pull request.', + ); + } + + public function execute(object $input): string + { + // Implementation to submit code review + return json_encode(['status' => 'OK']); + } +} diff --git a/app/Agents/Delivery/DeliveryAgent.php b/app/Agents/Delivery/DeliveryAgent.php new file mode 100644 index 0000000..daf8777 --- /dev/null +++ b/app/Agents/Delivery/DeliveryAgent.php @@ -0,0 +1,102 @@ +addMetadata( + // Memory + new SolutionMetadata( + type: MetadataType::Memory, + key: 'order_tip', + content: 'First, retrieve the customer profile to provide personalized service.', + ), + new SolutionMetadata( + type: MetadataType::Memory, + key: 'order_tip_server', + content: 'Always check the server [google.com] status before providing any information.', + ), + new SolutionMetadata( + type: MetadataType::Memory, + key: 'order_tip_repeat', + content: 'Don\'t repeat the same information to the customer. If you have already provided the order number, don\'t repeat it. Provide only new information.', + ), + new SolutionMetadata( + type: MetadataType::Memory, + key: 'order_tip_age', + content: 'Tone of conversation is important, pay attention on age and fit the conversation to the age of the customer.', + ), + + // Prompt + new SolutionMetadata( + type: MetadataType::Prompt, + key: 'server_status', + content: 'Check the server [google.com] status to ensure that the system is operational.', + ), + new SolutionMetadata( + type: MetadataType::Prompt, + key: 'what_is_order_number', + content: 'What is my order number?', + ), + new SolutionMetadata( + type: MetadataType::Prompt, + key: 'when_is_delivery', + content: 'When will my order be delivered?', + ), + new SolutionMetadata( + type: MetadataType::Prompt, + key: 'my_profile', + content: 'Can you tell me more about my profile?', + ), + ); + + // Add a model to the agent + $model = new Model(model: OpenAIModel::Gpt4oMini->value); + $aggregate->addAssociation($model); + + $aggregate->addAssociation(new ToolLink(name: GetDeliveryDateTool::NAME)); + $aggregate->addAssociation(new ToolLink(name: GetOrderNumberTool::NAME)); + $aggregate->addAssociation(new ToolLink(name: GetProfileTool::NAME)); + + $aggregate->addAssociation(new ToolLink(name: AskAgentTool::NAME)); + $aggregate->addAssociation( + new AgentLink( + name: SiteStatusCheckerAgent::NAME, + outputSchema: StatusCheckOutput::class, + ), + ); + + return $aggregate; + } +} diff --git a/app/Agents/Delivery/DeliveryAgentFactory.php b/app/Agents/Delivery/DeliveryAgentFactory.php new file mode 100644 index 0000000..45a974a --- /dev/null +++ b/app/Agents/Delivery/DeliveryAgentFactory.php @@ -0,0 +1,17 @@ + + */ +final class GetDeliveryDateTool extends PhpTool +{ + public const NAME = 'get_delivery_date'; + + public function __construct() + { + parent::__construct( + name: self::NAME, + inputSchema: DeliveryDateInput::class, + description: 'Get the delivery date for a customer\'s order. Call this whenever you need to know the delivery date, for example when a customer asks \'Where is my package\'', + ); + } + + public function execute(object $input): string + { + return \json_encode([ + 'delivery_date' => Carbon::now()->addDays(\rand(1, 100))->toDateString(), + ]); + } +} diff --git a/app/Agents/Delivery/GetOrderNumberTool.php b/app/Agents/Delivery/GetOrderNumberTool.php new file mode 100644 index 0000000..4f44af7 --- /dev/null +++ b/app/Agents/Delivery/GetOrderNumberTool.php @@ -0,0 +1,33 @@ + + */ +final class GetOrderNumberTool extends PhpTool +{ + public const NAME = 'get_order_number'; + + public function __construct() + { + parent::__construct( + name: self::NAME, + inputSchema: OrderNumberInput::class, + description: 'Get the order number for a customer.', + ); + } + + public function execute(object $input): string + { + return \json_encode([ + 'customer_id' => $input->customerId, + 'customer' => 'John Doe', + 'order_number' => 'abc-' . $input->customerId, + ]); + } +} diff --git a/app/Agents/Delivery/GetProfileTool.php b/app/Agents/Delivery/GetProfileTool.php new file mode 100644 index 0000000..be0b252 --- /dev/null +++ b/app/Agents/Delivery/GetProfileTool.php @@ -0,0 +1,34 @@ + + */ +final class GetProfileTool extends PhpTool +{ + public const NAME = 'get_profile'; + + public function __construct() + { + parent::__construct( + name: self::NAME, + inputSchema: ProfileInput::class, + description: 'Get the customer\'s profile information.', + ); + } + + public function execute(object $input): string + { + return \json_encode([ + 'account_uuid' => $input->accountId, + 'first_name' => 'John', + 'last_name' => 'Doe', + 'age' => \rand(10, 100), + ]); + } +} diff --git a/app/Agents/Delivery/OrderNumberInput.php b/app/Agents/Delivery/OrderNumberInput.php new file mode 100644 index 0000000..90e39b7 --- /dev/null +++ b/app/Agents/Delivery/OrderNumberInput.php @@ -0,0 +1,15 @@ +getCurrentMemory($sessionUuid); + + $memories->addMemory($metadata); + + $this->cache->set($this->getKey($sessionUuid), $memories); + } + + public function updateMemory(UuidInterface $sessionUuid, SolutionMetadata $metadata): void + { + $memories = $this->getCurrentMemory($sessionUuid); + $memories->updateMemory($metadata); + + $this->cache->set($this->getKey($sessionUuid), $memories); + } + + public function getCurrentMemory(UuidInterface $sessionUuid): Memories + { + return $this->cache->get($this->getKey($sessionUuid)) ?? new Memories( + Uuid::uuid4(), + ); + } + + private function getKey(UuidInterface $sessionUuid): string + { + return 'user_memory'; + } +} diff --git a/app/Agents/DynamicMemoryTool/DynamicMemoryTool.php b/app/Agents/DynamicMemoryTool/DynamicMemoryTool.php new file mode 100644 index 0000000..c615519 --- /dev/null +++ b/app/Agents/DynamicMemoryTool/DynamicMemoryTool.php @@ -0,0 +1,39 @@ +preference, + ); + + $sessionUuid = Uuid::fromString($input->sessionId); + $this->memoryService->addMemory($sessionUuid, $metadata); + + return 'Memory updated'; + } +} diff --git a/app/Agents/DynamicMemoryTool/Memories.php b/app/Agents/DynamicMemoryTool/Memories.php new file mode 100644 index 0000000..0cf573a --- /dev/null +++ b/app/Agents/DynamicMemoryTool/Memories.php @@ -0,0 +1,28 @@ + */ + public array $memories = [], + ) {} + + public function addMemory(SolutionMetadata $metadata): void + { + $this->memories[] = $metadata; + } + + public function getIterator(): Traversable + { + yield from $this->memories; + } +} diff --git a/app/Agents/PhpTool.php b/app/Agents/PhpTool.php new file mode 100644 index 0000000..03f895c --- /dev/null +++ b/app/Agents/PhpTool.php @@ -0,0 +1,20 @@ + + */ +abstract class PhpTool extends Tool +{ + public function getLanguage(): ToolLanguage + { + return ToolLanguage::PHP; + } +} diff --git a/app/Agents/SmartHomeControl/ControlDeviceInput.php b/app/Agents/SmartHomeControl/ControlDeviceInput.php new file mode 100644 index 0000000..9267f04 --- /dev/null +++ b/app/Agents/SmartHomeControl/ControlDeviceInput.php @@ -0,0 +1,25 @@ + + */ + #[Field(title: 'Parameters', description: 'Additional parameters for the action. If the action does not require parameters, this field should be an empty array')] + public array $params, + ) {} +} diff --git a/app/Agents/SmartHomeControl/ControlDeviceTool.php b/app/Agents/SmartHomeControl/ControlDeviceTool.php new file mode 100644 index 0000000..5f1c4e5 --- /dev/null +++ b/app/Agents/SmartHomeControl/ControlDeviceTool.php @@ -0,0 +1,41 @@ + + */ +final class ControlDeviceTool extends PhpTool +{ + public const NAME = 'control_device'; + + public function __construct( + private readonly SmartHomeSystem $smartHome, + ) { + parent::__construct( + name: self::NAME, + inputSchema: ControlDeviceInput::class, + description: 'Controls a specific device by performing the specified action with given parameters.', + ); + } + + public function execute(object $input): string + { + try { + $result = $this->smartHome->controlDevice($input->deviceId, $input->action, $input->params); + + return json_encode([ + 'id' => $input->deviceId, + 'action' => $input->action->value, + 'result' => $result, + ]); + } catch (\InvalidArgumentException $e) { + return json_encode(['error' => $e->getMessage()]); + } + } +} diff --git a/app/Agents/SmartHomeControl/DeviceAction.php b/app/Agents/SmartHomeControl/DeviceAction.php new file mode 100644 index 0000000..fcfd326 --- /dev/null +++ b/app/Agents/SmartHomeControl/DeviceAction.php @@ -0,0 +1,18 @@ + + */ +final class GetDeviceDetailsTool extends PhpTool +{ + public const NAME = 'get_device_details'; + + public function __construct( + private readonly SmartHomeSystem $smartHome, + ) { + parent::__construct( + name: self::NAME, + inputSchema: GetDeviceDetailsInput::class, + description: 'Retrieves detailed information about a specific device.', + ); + } + + public function execute(object $input): string + { + $device = $this->smartHome->getDevice($input->deviceId); + + if (!$device) { + return json_encode(['error' => 'Device not found']); + } + + return json_encode([ + 'id' => $device->id, + 'name' => $device->name, + 'room' => $device->room, + 'type' => get_class($device), + 'params' => $device->getDetails(), + 'controlSchema' => $device->getControlSchema(), + ]); + } +} diff --git a/app/Agents/SmartHomeControl/GetRoomListInput.php b/app/Agents/SmartHomeControl/GetRoomListInput.php new file mode 100644 index 0000000..91b90ec --- /dev/null +++ b/app/Agents/SmartHomeControl/GetRoomListInput.php @@ -0,0 +1,17 @@ + + */ +final class GetRoomListTool extends PhpTool +{ + public const NAME = 'get_room_list'; + + public function __construct( + private readonly SmartHomeSystem $smartHome, + ) { + parent::__construct( + name: self::NAME, + inputSchema: GetRoomListInput::class, + description: 'Retrieves a list of all room names in the smart home system.', + ); + } + + public function execute(object $input): string + { + $rooms = $this->smartHome->getRoomList(); + + return \json_encode([ + 'rooms' => $rooms, + ]); + } +} diff --git a/app/Agents/SmartHomeControl/ListRoomDevicesInput.php b/app/Agents/SmartHomeControl/ListRoomDevicesInput.php new file mode 100644 index 0000000..5abea7a --- /dev/null +++ b/app/Agents/SmartHomeControl/ListRoomDevicesInput.php @@ -0,0 +1,15 @@ + + */ +final class ListRoomDevicesTool extends PhpTool +{ + public const NAME = 'list_room_devices'; + + public function __construct( + private readonly SmartHomeSystem $smartHome, + ) { + parent::__construct( + name: self::NAME, + inputSchema: ListRoomDevicesInput::class, + description: 'Lists all smart devices in a specified room.', + ); + } + + public function execute(object $input): string + { + $devices = $this->smartHome->getRoomDevices($input->roomName); + $deviceList = []; + + foreach ($devices as $device) { + $deviceList[] = [ + 'id' => $device->id, + 'name' => $device->name, + 'type' => get_class($device), + 'status' => $device->getStatus() ? 'on' : 'off', + 'params' => $device->getDetails(), + 'controlSchema' => $device->getControlSchema(), + ]; + } + + return json_encode([ + 'room' => $input->roomName, + 'devices' => $deviceList, + ]); + } +} diff --git a/app/Agents/SmartHomeControl/SmartHome/Devices/Light.php b/app/Agents/SmartHomeControl/SmartHome/Devices/Light.php new file mode 100644 index 0000000..69c0b23 --- /dev/null +++ b/app/Agents/SmartHomeControl/SmartHome/Devices/Light.php @@ -0,0 +1,80 @@ +brightness = max(0, min(100, $level)); + $this->status = $this->brightness > 0; + } + + public function setColor(?string $color): void + { + $this->color = $color; + } + + public function getDetails(): array + { + return [ + 'type' => $this->type, + 'status' => $this->status ? 'on' : 'off', + 'brightness' => $this->brightness, + 'color' => $this->color, + ]; + } + + public function getControlSchema(): array + { + return [ + 'type' => 'object', + 'properties' => [ + 'action' => [ + 'type' => 'string', + 'enum' => [ + DeviceAction::TurnOn->value, + DeviceAction::TurnOff->value, + DeviceAction::SetBrightness->value, + DeviceAction::SetColor->value, + ], + ], + 'brightness' => [ + 'type' => 'integer', + 'minimum' => 0, + 'maximum' => 100, + ], + 'color' => [ + 'type' => 'string', + ], + ], + 'required' => ['action'], + ]; + } + + public function executeAction(DeviceAction $action, array $params): static + { + match ($action) { + DeviceAction::SetBrightness => $this->setBrightness($params['brightness']), + DeviceAction::SetColor => $this->setColor($params['color']), + default => parent::executeAction($action, $params) + }; + + return $this; + } +} diff --git a/app/Agents/SmartHomeControl/SmartHome/Devices/SmartAppliance.php b/app/Agents/SmartHomeControl/SmartHome/Devices/SmartAppliance.php new file mode 100644 index 0000000..dbf7672 --- /dev/null +++ b/app/Agents/SmartHomeControl/SmartHome/Devices/SmartAppliance.php @@ -0,0 +1,128 @@ +attributes[$key] = $value; + } + + /** + * @param array $attributes + */ + public function setAttributes(array $attributes): void + { + foreach ($attributes as $attribute) { + $this->setAttribute($attribute->name, $attribute->value); + } + } + + public function getAttribute(string $key) + { + return $this->attributes[$key] ?? null; + } + + public function getDetails(): array + { + return array_merge( + $this->attributes, + [ + 'status' => $this->status ? 'on' : 'off', + 'type' => $this->type, + ], + ); + } + + public function getControlSchema(): array + { + $schema = [ + 'type' => 'object', + 'properties' => [ + 'action' => [ + 'type' => 'string', + 'enum' => [ + DeviceAction::TurnOn->value, + DeviceAction::TurnOff->value, + DeviceAction::SetAttribute->value, + ], + ], + 'params' => [ + 'type' => 'object', + 'properties' => [ + 'name' => [ + 'type' => 'string', + ], + 'value' => [ + 'type' => ['string', 'number', 'boolean'], + ], + ], + 'required' => ['name', 'value',], + ], + ], + 'required' => ['action'], + ]; + + // Add specific schemas based on the appliance type + switch ($this->type) { + case 'fireplace': + $schema['properties']['intensity'] = [ + 'type' => 'integer', + 'minimum' => 1, + 'maximum' => 5, + ]; + break; + case 'speaker': + $schema['properties']['volume'] = [ + 'type' => 'integer', + 'minimum' => 0, + 'maximum' => 100, + ]; + $schema['properties']['radio_station'] = [ + 'type' => 'string', + 'enum' => ['rock', 'pop', 'jazz', 'classical', 'news', 'talk', 'sports'], + ]; + $schema['properties']['playback'] = [ + 'type' => 'string', + 'enum' => ['play', 'pause', 'stop', 'next', 'previous'], + ]; + break; + case 'fan': + $schema['properties']['speed'] = [ + 'type' => 'integer', + 'minimum' => 0, + 'maximum' => 5, + ]; + break; + } + + return $schema; + } + + public function executeAction(DeviceAction $action, array $params): static + { + match ($action) { + DeviceAction::SetAttribute => $this->setAttributes($params), + default => parent::executeAction($action, $params), + }; + + return $this; + } +} diff --git a/app/Agents/SmartHomeControl/SmartHome/Devices/SmartDevice.php b/app/Agents/SmartHomeControl/SmartHome/Devices/SmartDevice.php new file mode 100644 index 0000000..5cc5a66 --- /dev/null +++ b/app/Agents/SmartHomeControl/SmartHome/Devices/SmartDevice.php @@ -0,0 +1,47 @@ +status = true; + } + + public function turnOff(): void + { + $this->status = false; + } + + public function getStatus(): bool + { + return $this->status; + } + + abstract public function getDetails(): array; + + abstract public function getControlSchema(): array; + + public function executeAction(DeviceAction $action, array $params): static + { + match ($action) { + DeviceAction::TurnOn => $this->turnOn(), + DeviceAction::TurnOff => $this->turnOff(), + default => throw new \InvalidArgumentException("Unsupported action: {$action->value}"), + }; + + return $this; + } +} diff --git a/app/Agents/SmartHomeControl/SmartHome/Devices/TV.php b/app/Agents/SmartHomeControl/SmartHome/Devices/TV.php new file mode 100644 index 0000000..448f947 --- /dev/null +++ b/app/Agents/SmartHomeControl/SmartHome/Devices/TV.php @@ -0,0 +1,78 @@ +volume = max(0, min(100, $volume)); + } + + public function setInput(string $input): void + { + $this->input = $input; + } + + public function getDetails(): array + { + return [ + 'status' => $this->status ? 'on' : 'off', + 'volume' => $this->volume, + 'input' => $this->input, + ]; + } + + public function getControlSchema(): array + { + return [ + 'type' => 'object', + 'properties' => [ + 'action' => [ + 'type' => 'string', + 'enum' => [ + DeviceAction::TurnOn->value, + DeviceAction::TurnOff->value, + DeviceAction::SetVolume->value, + DeviceAction::SetInput->value, + ], + ], + 'volume' => [ + 'type' => 'integer', + 'minimum' => 0, + 'maximum' => 100, + ], + 'input' => [ + 'type' => 'string', + 'enum' => ['HDMI 1', 'HDMI 2', 'HDMI 3', 'TV', 'AV'], + ], + ], + 'required' => ['action'], + ]; + } + + public function executeAction(DeviceAction $action, array $params): static + { + match ($action) { + DeviceAction::SetVolume => $this->setVolume($params['volume']), + DeviceAction::SetInput => $this->setInput($params['input']), + default => parent::executeAction($action, $params), + }; + + return $this; + } +} diff --git a/app/Agents/SmartHomeControl/SmartHome/Devices/Thermostat.php b/app/Agents/SmartHomeControl/SmartHome/Devices/Thermostat.php new file mode 100644 index 0000000..a28e2cc --- /dev/null +++ b/app/Agents/SmartHomeControl/SmartHome/Devices/Thermostat.php @@ -0,0 +1,78 @@ +temperature = $temperature; + } + + public function setMode(string $mode): void + { + $this->mode = $mode; + } + + public function getDetails(): array + { + return [ + 'status' => $this->status ? 'on' : 'off', + 'temperature' => $this->temperature, + 'mode' => $this->mode, + ]; + } + + public function getControlSchema(): array + { + return [ + 'type' => 'object', + 'properties' => [ + 'action' => [ + 'type' => 'string', + 'enum' => [ + DeviceAction::TurnOn->value, + DeviceAction::TurnOff->value, + DeviceAction::SetTemperature->value, + DeviceAction::SetMode->value, + ], + ], + 'temperature' => [ + 'type' => 'integer', + 'minimum' => 60, + 'maximum' => 90, + ], + 'mode' => [ + 'type' => 'string', + 'enum' => ['auto', 'cool', 'heat', 'fan'], + ], + ], + 'required' => ['action'], + ]; + } + + public function executeAction(DeviceAction $action, array $params): static + { + match ($action) { + DeviceAction::SetTemperature => $this->setTemperature($params['temperature']), + DeviceAction::SetMode => $this->setMode($params['mode']), + default => parent::executeAction($action, $params), + }; + + return $this; + } +} diff --git a/app/Agents/SmartHomeControl/SmartHome/SmartHomeSystem.php b/app/Agents/SmartHomeControl/SmartHome/SmartHomeSystem.php new file mode 100644 index 0000000..0649e3f --- /dev/null +++ b/app/Agents/SmartHomeControl/SmartHome/SmartHomeSystem.php @@ -0,0 +1,83 @@ + */ + private array $devices = []; + + public function __construct( + private readonly CacheInterface $cache, + ) {} + + public function addDevice(SmartDevice $device): void + { + $this->devices[$device->id] = $device; + } + + public function getDevice(string $id): ?SmartDevice + { + $device = $this->cache->get('device_' . $id, $this->devices[$id] ?? null); + + return $device; + } + + public function getRoomList(): array + { + $rooms = \array_unique(\array_map(fn($device) => $device->room, $this->devices)); + \sort($rooms); + return $rooms; + } + + public function getRoomDevices(string $room): array + { + return \array_filter($this->getCachedDevices(), static fn($device): bool => $device->room === $room); + } + + private function getCachedDevices(): array + { + $devices = []; + foreach ($this->devices as $id => $device) { + $devices[$id] = $this->getDevice($id); + } + + return \array_filter($devices); + } + + public function controlDevice(string $id, DeviceAction $action, array $params = []): array + { + $device = $this->getDevice($id); + if ($device === null) { + return ['error' => 'Device not found']; + } + + $this->cache->set( + 'device_' . $id, + $device->executeAction($action, $params), + CarbonInterval::hour(), + ); + + $this->cache->set('last_action', \time(), CarbonInterval::hour()); + + return [ + 'id' => $device->id, + 'name' => $device->name, + 'room' => $device->room, + 'type' => \get_class($device), + 'params' => $device->getDetails(), + ]; + } + + public function getLastActionTime(): ?int + { + return $this->cache->get('last_action') ?? null; + } +} diff --git a/app/Agents/SmartHomeControl/SmartHomeControlAgent.php b/app/Agents/SmartHomeControl/SmartHomeControlAgent.php new file mode 100644 index 0000000..fc00f31 --- /dev/null +++ b/app/Agents/SmartHomeControl/SmartHomeControlAgent.php @@ -0,0 +1,112 @@ +addMetadata( + new SolutionMetadata( + type: MetadataType::Memory, + key: 'room_validation', + content: 'Important! Before request devices in any room, use the get_room_list tool to know correct room names.', + ), + new SolutionMetadata( + type: MetadataType::Memory, + key: 'energy_efficiency', + content: 'Remember to suggest energy-efficient settings when appropriate.', + ), + new SolutionMetadata( + type: MetadataType::Memory, + key: 'device_status', + content: 'Important! Check device status before performing any action. Because a state can be changed by another user or system.', + ), + new SolutionMetadata( + type: MetadataType::Memory, + key: 'home_name', + content: 'We are currently in the "Home" home.', + ), + new SolutionMetadata( + type: MetadataType::Memory, + key: 'store_important_memory', + content: 'Store important information in memory for future reference. For example if user tells that he likes some specific setting, store it in memory.', + ), + + new SolutionMetadata( + type: MetadataType::Configuration, + key: Option::MaxTokens->value, + content: 3000, + ), + + new SolutionMetadata( + type: MetadataType::Prompt, + key: 'query_devices', + content: 'What devices are in the living room?', + ), + new SolutionMetadata( + type: MetadataType::Prompt, + key: 'control_light', + content: 'Turn on the lights in the bedroom.', + ), + new SolutionMetadata( + type: MetadataType::Prompt, + key: 'control_fireplace', + content: 'Set the fireplace in the living room to medium intensity.', + ), + new SolutionMetadata( + type: MetadataType::Prompt, + key: 'control_tv', + content: 'Today is rainy. I\'m in the living room and in a bad mood, could you do something to cheer me up?', + ), + new SolutionMetadata( + type: MetadataType::Prompt, + key: 'check_device_status', + content: 'Is the kitchen light on?', + ), + new SolutionMetadata( + type: MetadataType::Prompt, + key: 'control_multiple_devices', + content: 'Turn off all devices in the master bedroom.', + ), + ); + + $model = new Model(model: OpenAIModel::Gpt4oMini->value); + $aggregate->addAssociation($model); + + $aggregate->addAssociation(new ToolLink(name: ListRoomDevicesTool::NAME)); + $aggregate->addAssociation(new ToolLink(name: GetDeviceDetailsTool::NAME)); + $aggregate->addAssociation(new ToolLink(name: ControlDeviceTool::NAME)); + $aggregate->addAssociation(new ToolLink(name: GetRoomListTool::NAME)); + $aggregate->addAssociation(new ToolLink(name: DynamicMemoryTool::NAME)); + + return $aggregate; + } +} diff --git a/app/Agents/SmartHomeControl/SmartHomeControlAgentFactory.php b/app/Agents/SmartHomeControl/SmartHomeControlAgentFactory.php new file mode 100644 index 0000000..951968e --- /dev/null +++ b/app/Agents/SmartHomeControl/SmartHomeControlAgentFactory.php @@ -0,0 +1,16 @@ + + */ +final class GetProjectDescription extends PhpTool +{ + public const NAME = 'get_project_description'; + + public function __construct() + { + parent::__construct( + name: self::NAME, + inputSchema: ProjectDescriptionInput::class, + description: 'Get the description of a project from the project management system.', + ); + } + + public function execute(object $input): string + { + return json_encode([ + 'uuid' => $input->uuid, + 'title' => 'Оплата услуг клиентом', + 'description' => <<<'TEXT' +**As a customer, I want to be able to:** + +- **Choose a subscription plan during registration:** + - When creating an account on the service's website, I should be offered a choice of different subscription plans. + - Each plan should include a clear description of the services provided and their costs. + +- **Subscribe to a plan using a credit card:** + - After selecting a plan, I need to provide my credit card details for payment. + - There should be an option to save my card details for automatic monthly payments. + +- **Receive monthly payment notifications:** + - I expect to receive notifications via email or through my personal account about upcoming subscription charges. + - The notification should arrive a few days before the charge, giving me enough time to ensure sufficient funds are available. + +- **Access all necessary documents in my personal account:** + - All financial documents, such as receipts and invoices, should be available for download at any time in my personal account. + +- **Cancel the service if needed:** + - I should be able to easily cancel my subscription through my personal account without needing to make additional calls or contact customer support. + +- **Add a new card if the current one expires:** + - If my credit card expires, I want to easily add a new card to the system through my personal account. + +- **Continue using the service after cancellation until the end of the paid period:** + - If I cancel my subscription, I expect to continue using the service until the end of the already paid period. +TEXT + , + ]); + } +} diff --git a/app/Agents/TaskSplitter/ProjectDescriptionInput.php b/app/Agents/TaskSplitter/ProjectDescriptionInput.php new file mode 100644 index 0000000..476c442 --- /dev/null +++ b/app/Agents/TaskSplitter/ProjectDescriptionInput.php @@ -0,0 +1,15 @@ + + */ +final class TaskCreateTool extends PhpTool +{ + public const NAME = 'create_task'; + + public function __construct( + private readonly Application $app, + ) { + parent::__construct( + name: self::NAME, + inputSchema: TaskCreateInput::class, + description: 'Create a task or subtask in the task management system.', + ); + } + + public function execute(object $input): string + { + $uuid = (string) Uuid::uuid4(); + $dir = $this->app->storagePath('tasks/'); + + if ($input->parentTaskUuid !== '') { + $path = \sprintf('%s/%s/%s.json', $input->projectUuid, $input->parentTaskUuid, $uuid); + } else { + $path = \sprintf('%s/%s.json', $input->projectUuid, $uuid); + } + + $fullPath = $dir . $path; + + Storage::createDirectory(\dirname($fullPath)); + Storage::write( + $fullPath, + \sprintf( + <<<'CONTENT' +uuid: %s +parent_uuid: %s +project_uuid: %s + +--- + +## %s + +%s +CONTENT, + $uuid, + $input->parentTaskUuid ?? '-', + $input->projectUuid, + $input->name, + \trim($input->description), + ), + ); + + return json_encode(['task_uuid' => $uuid]); + } +} diff --git a/app/Agents/TaskSplitter/TaskSplitterAgent.php b/app/Agents/TaskSplitter/TaskSplitterAgent.php new file mode 100644 index 0000000..b994400 --- /dev/null +++ b/app/Agents/TaskSplitter/TaskSplitterAgent.php @@ -0,0 +1,69 @@ +addMetadata( + new SolutionMetadata( + type: MetadataType::Memory, + key: 'task_organization_tip', + content: 'Always aim to create a logical hierarchy of tasks and subtasks. Main tasks should be broad objectives, while subtasks should be specific, actionable items.', + ), + new SolutionMetadata( + type: MetadataType::Memory, + key: 'efficiency_tip', + content: 'Try to keep the number of main tasks between 3 and 7 for better manageability. Break down complex tasks into subtasks when necessary.', + ), + + // Prompts examples + new SolutionMetadata( + type: MetadataType::Prompt, + key: 'split_task', + content: 'Split the project description f47ac10b-58cc-4372-a567-0e02b2c3d479 into a list of tasks and subtasks.', + ), + new SolutionMetadata( + type: MetadataType::Configuration, + key: Option::MaxTokens->value, + content: 3000, + ), + ); + + $model = new Model(model: OpenAIModel::Gpt4oMini->value); + $aggregate->addAssociation($model); + + $aggregate->addAssociation(new ToolLink(name: TaskCreateTool::NAME)); + $aggregate->addAssociation(new ToolLink(name: GetProjectDescription::NAME)); + + return $aggregate; + } +} diff --git a/app/Agents/TaskSplitter/TaskSplitterAgentFactory.php b/app/Agents/TaskSplitter/TaskSplitterAgentFactory.php new file mode 100644 index 0000000..9c03c97 --- /dev/null +++ b/app/Agents/TaskSplitter/TaskSplitterAgentFactory.php @@ -0,0 +1,16 @@ +cache->get((string) $sessionUuid); + + foreach ($messages as $message) { + yield $message; + } + } + + public function addMessage(UuidInterface $sessionUuid, object $message): void + { + $messages = (array) $this->cache->get((string) $sessionUuid); + $messages[] = $message; + + $this->cache->set((string) $sessionUuid, $messages); + } + + public function clear(UuidInterface $sessionUuid): void + { + $this->cache->delete((string) $sessionUuid); + } +} diff --git a/app/Chat/SimpleChatService.php b/app/Chat/SimpleChatService.php new file mode 100644 index 0000000..c4a489e --- /dev/null +++ b/app/Chat/SimpleChatService.php @@ -0,0 +1,209 @@ +agents->has($agentName)) { + throw new AgentNotFoundException($agentName); + } + + $agent = $this->agents->get($agentName); + + $session = Session::create([ + 'id' => Uuid::uuid7(), + 'account_uuid' => $accountUuid, + 'agent_name' => $agentName, + 'title' => $agent->getDescription(), + ]); + + $session->save(); + + return $session->getUuid(); + } + + public function ask(UuidInterface $sessionUuid, string|\Stringable $message): UuidInterface + { + $session = $this->getSession($sessionUuid); + + $prompt = null; + if (!$session->history->isEmpty()) { + $prompt = $session->history->toPrompt(); + } + + $messageUuid = Uuid::uuid4(); + $this->eventDispatcher?->dispatch( + new \LLM\Agents\Chat\Event\Question( + sessionUuid: $session->getUuid(), + messageUuid: $messageUuid, + createdAt: new \DateTimeImmutable(), + message: $message, + ), + ); + + $execution = $this->buildAgent( + session: $session, + prompt: $prompt, + )->ask($message); + + $this->handleResult($execution, $session); + + return $messageUuid; + } + + public function closeSession(UuidInterface $sessionUuid): void + { + $session = $this->getSession($sessionUuid); + $session->finished_at = now(); + + $this->updateSession($session); + } + + public function updateSession(SessionInterface $session): void + { + $session->save(); + } + + private function handleResult(Execution $execution, SessionInterface $session): void + { + $finished = false; + while (true) { + $result = $execution->result; + $prompt = $execution->prompt; + + if ($result instanceof ToolCalledResponse) { + // First, call all tools. + $toolsResponse = []; + foreach ($result->tools as $tool) { + $toolsResponse[] = $this->callTool($session, $tool); + } + + // Then add the tools responses to the prompt. + foreach ($toolsResponse as $toolResponse) { + $prompt = $prompt->withAddedMessage($toolResponse); + } + + $execution = $this->buildAgent( + session: $session, + prompt: $prompt, + )->continue(); + } elseif ($result instanceof ChatResponse) { + $finished = true; + + $this->eventDispatcher?->dispatch( + new \LLM\Agents\Chat\Event\Message( + sessionUuid: $session->getUuid(), + createdAt: new \DateTimeImmutable(), + message: $result->content, + ), + ); + } + + $session->updateHistory($prompt->toArray()); + $this->updateSession($session); + + if ($finished) { + break; + } + } + } + + private function buildAgent(SessionInterface $session, ?Prompt $prompt): AgentExecutorBuilder + { + $agent = $this->builder + ->withAgentKey($session->getAgentName()) + ->withStreamChunkCallback( + new StreamChunkCallback( + sessionUuid: $session->getUuid(), + eventDispatcher: $this->eventDispatcher, + ), + ) + ->withSessionContext([ + 'account_uuid' => (string) $session->account_uuid, + 'session_uuid' => (string) $session->getUuid(), + ]); + + if ($prompt === null) { + return $agent; + } + + $memories = $this->memoryService->getCurrentMemory($session->getUuid()); + + return $agent->withPrompt($prompt->withValues([ + 'dynamic_memory' => \implode( + "\n", + \array_map( + fn(SolutionMetadata $memory) => $memory->content, + $memories->memories, + ), + ), + ])); + } + + private function callTool(SessionInterface $session, ToolCall $tool): ToolCallResultMessage + { + $this->eventDispatcher?->dispatch( + new \LLM\Agents\Chat\Event\ToolCall( + sessionUuid: $session->getUuid(), + id: $tool->id, + tool: $tool->name, + arguments: $tool->arguments, + createdAt: new \DateTimeImmutable(), + ), + ); + + $functionResult = $this->toolExecutor->execute($tool->name, $tool->arguments); + + $this->eventDispatcher?->dispatch( + new \LLM\Agents\Chat\Event\ToolCallResult( + sessionUuid: $session->getUuid(), + id: $tool->id, + tool: $tool->name, + result: $functionResult, + createdAt: new \DateTimeImmutable(), + ), + ); + + return new ToolCallResultMessage( + id: $tool->id, + content: [$functionResult], + ); + } +} diff --git a/app/Console/Commands/AgentListCommand.php b/app/Console/Commands/AgentListCommand.php new file mode 100644 index 0000000..9cdbb8d --- /dev/null +++ b/app/Console/Commands/AgentListCommand.php @@ -0,0 +1,30 @@ +info('Available agents:'); + + $rows = []; + foreach ($agents->all() as $agent) { + $tools = \array_map(static fn(ToolLink $tool): string => '- ' . $tool->getName(), $agent->getTools()); + $rows[] = [ + $agent->getKey() . PHP_EOL . '- ' . $agent->getModel()->name, + \implode(PHP_EOL, $tools), + \wordwrap($agent->getDescription(), 50, "\n", true), + ]; + } + + $this->table(['Agent', 'Tools', 'Description'], $rows); + } +} diff --git a/app/Console/Commands/ChatSessionCommand.php b/app/Console/Commands/ChatSessionCommand.php new file mode 100644 index 0000000..e6e7772 --- /dev/null +++ b/app/Console/Commands/ChatSessionCommand.php @@ -0,0 +1,44 @@ +output); + $cursor->clearScreen(); + Artisan::call('agent:list'); + + $chat = new ChatSession( + input: $this->input, + output: $this->output, + agents: $agents, + chat: $chat, + chatHistory: $chatHistory, + tools: $tools, + ); + + $chat->run( + accountUuid: Uuid::fromString('00000000-0000-0000-0000-000000000000'), + binPath: 'artisan', + ); + } +} diff --git a/app/Console/Commands/ChatWindowCommand.php b/app/Console/Commands/ChatWindowCommand.php new file mode 100644 index 0000000..00bf262 --- /dev/null +++ b/app/Console/Commands/ChatWindowCommand.php @@ -0,0 +1,29 @@ +input, + output: $this->output, + chatHistory: $chatHistory, + chat: $chatService, + ); + + $chatWindow->run(Uuid::fromString($this->argument('session_uuid'))); + } +} diff --git a/app/Console/Commands/ToolListCommand.php b/app/Console/Commands/ToolListCommand.php new file mode 100644 index 0000000..a1ca780 --- /dev/null +++ b/app/Console/Commands/ToolListCommand.php @@ -0,0 +1,22 @@ +all() as $tool) { + $rows[] = [$tool->getName(), $tool->getDescription()]; + } + + $this->table(['Tool', 'Description'], $rows); + } +} diff --git a/app/Event/ChatEventsListener.php b/app/Event/ChatEventsListener.php new file mode 100644 index 0000000..566ae75 --- /dev/null +++ b/app/Event/ChatEventsListener.php @@ -0,0 +1,73 @@ +history->addMessage($message->sessionUuid, $message); + } + + public function listenMessage(Message $message): void + { + $this->history->addMessage($message->sessionUuid, $message); + } + + public function listenMessageChunk(MessageChunk $message): void + { + $this->history->addMessage($message->sessionUuid, $message); + } + + public function listenQuestion(Question $message): void + { + $this->history->addMessage($message->sessionUuid, $message); + } + + public function listenToolCallResult(ToolCallResult $message): void + { + $this->history->addMessage($message->sessionUuid, $message); + } + + public function subscribe(Dispatcher $events): void + { + $events->listen( + ToolCall::class, + [ChatEventsListener::class, 'listenToolCall'] + ); + + $events->listen( + Message::class, + [ChatEventsListener::class, 'listenMessage'] + ); + + $events->listen( + MessageChunk::class, + [ChatEventsListener::class, 'listenMessageChunk'] + ); + + $events->listen( + Question::class, + [ChatEventsListener::class, 'listenQuestion'] + ); + + $events->listen( + ToolCallResult::class, + [ChatEventsListener::class, 'listenToolCallResult'] + ); + } +} diff --git a/app/Models/Casts/History.php b/app/Models/Casts/History.php new file mode 100644 index 0000000..dc8485c --- /dev/null +++ b/app/Models/Casts/History.php @@ -0,0 +1,28 @@ +toString(); + } +} diff --git a/app/Models/Session.php b/app/Models/Session.php new file mode 100644 index 0000000..7131a53 --- /dev/null +++ b/app/Models/Session.php @@ -0,0 +1,60 @@ + Uuid::class, + 'account_uuid' => Uuid::class, + 'finished_at' => 'datetime', + 'history' => History::class, + ]; + } + + public function getUuid(): UuidInterface + { + return $this->getKey(); + } + + public function getAgentName(): string + { + return $this->agent_name; + } + + public function updateHistory(array $messages): void + { + $this->history = new ValueObject\History($messages); + } + + public function isFinished(): bool + { + return $this->trashed(); + } +} diff --git a/app/Models/ValueObject/History.php b/app/Models/ValueObject/History.php new file mode 100644 index 0000000..784ac71 --- /dev/null +++ b/app/Models/ValueObject/History.php @@ -0,0 +1,48 @@ +data === []; + } + + public function toPrompt(): Prompt + { + return Prompt::fromArray($this->data); + } + + public function jsonSerialize(): array + { + return $this->data instanceof \JsonSerializable + ? $this->data->jsonSerialize() + : $this->data; + } + + public function __toString(): string + { + return \json_encode($this); + } + + public function getIterator(): Traversable + { + return new \ArrayIterator($this->data); + } +} + diff --git a/app/Providers/AgentsChatServiceProvider.php b/app/Providers/AgentsChatServiceProvider.php new file mode 100644 index 0000000..81aac4f --- /dev/null +++ b/app/Providers/AgentsChatServiceProvider.php @@ -0,0 +1,31 @@ +app->singleton(ChatServiceInterface::class, SimpleChatService::class); + $this->app->singleton(AgentPromptGeneratorInterface::class, AgentPromptGenerator::class); + + // Register ChatHistoryRepositoryInterface here + $this->app->singleton( + ChatHistoryRepositoryInterface::class, + static function (Application $app) { + return new ChatHistoryRepository($app->make('cache.store')); + }, + ); + } +} diff --git a/app/Providers/AgentsServiceProvider.php b/app/Providers/AgentsServiceProvider.php new file mode 100644 index 0000000..c025c12 --- /dev/null +++ b/app/Providers/AgentsServiceProvider.php @@ -0,0 +1,51 @@ +app->singleton(ToolRegistry::class, ToolRegistry::class); + $this->app->singleton(ToolRegistryInterface::class, ToolRegistry::class); + $this->app->singleton(ToolRepositoryInterface::class, ToolRegistry::class); + + $this->app->singleton(AgentRegistry::class, AgentRegistry::class); + $this->app->singleton(AgentRegistryInterface::class, AgentRegistry::class); + $this->app->singleton(AgentRepositoryInterface::class, AgentRegistry::class); + + $this->app->singleton(OptionsFactoryInterface::class, OptionsFactory::class); + $this->app->singleton(ContextFactoryInterface::class, ContextFactory::class); + + $this->app->singleton(SchemaMapperInterface::class, SchemaMapper::class); + } + + public function boot( + AgentRegistryInterface $agents, + ToolRegistryInterface $tools, + ): void { + foreach (config('agents.agents') as $agent) { + $agents->register($this->app->make($agent)->create()); + } + + foreach (config('agents.tools') as $tool) { + $tools->register($this->app->make($tool)); + } + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..76acbc9 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,7 +2,10 @@ namespace App\Providers; +use App\Event\ChatEventsListener; +use Illuminate\Support\Facades\Event; use Illuminate\Support\ServiceProvider; +use Psr\EventDispatcher\EventDispatcherInterface; class AppServiceProvider extends ServiceProvider { @@ -11,7 +14,15 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { - // + $this->app->singleton(EventDispatcherInterface::class, function () { + return new class implements EventDispatcherInterface { + public function dispatch(object $event): object + { + Event::dispatch($event); + return $event; + } + }; + }); } /** @@ -19,6 +30,6 @@ public function register(): void */ public function boot(): void { - // + Event::subscribe(ChatEventsListener::class); } } diff --git a/app/Providers/SmartHomeServiceProvider.php b/app/Providers/SmartHomeServiceProvider.php new file mode 100644 index 0000000..67ab01e --- /dev/null +++ b/app/Providers/SmartHomeServiceProvider.php @@ -0,0 +1,122 @@ +app->singleton(SmartHomeSystem::class, static function (Application $app) { + $cache = $app->make(CacheInterface::class); + + $smartHome = new SmartHomeSystem($cache); + + // Living Room Devices + $livingRoomAirConditioner = new SmartAppliance( + 'LR_AC_01', + 'Living Room Air Conditioner', + 'living_room', + 'air_conditioner', + [ + 'temperature' => 0, + 'mode' => 'cool', + ], + ); + $livingRoomMainLight = new Light('LR_MAIN_01', 'Living Room Main Light', 'living_room', 'dimmable'); + $livingRoomTableLamp = new Light('LR_LAMP_01', 'Living Room Table Lamp', 'living_room', 'color'); + $livingRoomThermostat = new Thermostat('LR_THERM_01', 'Living Room Thermostat', 'living_room', 24); + $livingRoomTV = new TV('LR_TV_01', 'Living Room TV', 'living_room', 20, 'HDMI 1'); + $livingRoomFireplace = new SmartAppliance( + 'LR_FIRE_01', + 'Living Room Fireplace', + 'living_room', + 'fireplace', + [ + 'temperature' => 0, + ], + ); + $livingRoomSpeaker = new SmartAppliance( + 'LR_SPEAK_01', + 'Living Room Smart Speaker', + 'living_room', + 'speaker', + [ + 'volume' => 0, + 'radio_station' => 'Classical FM', + ], + ); + + // Kitchen Devices + $kitchenMainLight = new Light('KT_MAIN_01', 'Kitchen Main Light', 'kitchen', 'dimmable'); + $kitchenPendantLights = new Light('KT_PEND_01', 'Kitchen Pendant Lights', 'kitchen', 'dimmable'); + $kitchenRefrigerator = new SmartAppliance( + 'KT_FRIDGE_01', + 'Smart Refrigerator', + 'kitchen', + 'refrigerator', + [ + 'temperature' => 37, + 'mode' => 'normal', + ], + ); + $kitchenOven = new SmartAppliance('KT_OVEN_01', 'Smart Oven', 'kitchen', 'oven'); + $kitchenCoffeeMaker = new SmartAppliance( + 'KT_COFFEE_01', 'Smart Coffee Maker', 'kitchen', 'coffee_maker', + ); + + // Bedroom Devices + $bedroomMainLight = new Light('BR_MAIN_01', 'Bedroom Main Light', 'bedroom', 'dimmable'); + $bedroomNightstandLeft = new Light('BR_NIGHT_L_01', 'Left Nightstand Lamp', 'bedroom', 'color'); + $bedroomNightstandRight = new Light('BR_NIGHT_R_01', 'Right Nightstand Lamp', 'bedroom', 'color'); + $bedroomThermostat = new Thermostat('BR_THERM_01', 'Bedroom Thermostat', 'bedroom', 68); + $bedroomTV = new TV('BR_TV_01', 'Bedroom TV', 'bedroom', 15, 'HDMI 1'); + $bedroomCeilingFan = new SmartAppliance('BR_FAN_01', 'Bedroom Ceiling Fan', 'bedroom', 'fan'); + + // Bathroom Devices + $bathroomMainLight = new Light('BA_MAIN_01', 'Bathroom Main Light', 'bathroom', 'dimmable'); + $bathroomMirrorLight = new Light('BA_MIRROR_01', 'Bathroom Mirror Light', 'bathroom', 'color'); + $bathroomExhaustFan = new SmartAppliance('BA_FAN_01', 'Bathroom Exhaust Fan', 'bathroom', 'fan'); + $bathroomSmartScale = new SmartAppliance('BA_SCALE_01', 'Smart Scale', 'bathroom', 'scale'); + + // Add all devices to the smart home system + $smartHome->addDevice($livingRoomAirConditioner); + $smartHome->addDevice($livingRoomMainLight); + $smartHome->addDevice($livingRoomTableLamp); + $smartHome->addDevice($livingRoomThermostat); + $smartHome->addDevice($livingRoomTV); + $smartHome->addDevice($livingRoomFireplace); + $smartHome->addDevice($livingRoomSpeaker); + + $smartHome->addDevice($kitchenMainLight); + $smartHome->addDevice($kitchenPendantLights); + $smartHome->addDevice($kitchenRefrigerator); + $smartHome->addDevice($kitchenOven); + $smartHome->addDevice($kitchenCoffeeMaker); + + $smartHome->addDevice($bedroomMainLight); + $smartHome->addDevice($bedroomNightstandLeft); + $smartHome->addDevice($bedroomNightstandRight); + $smartHome->addDevice($bedroomThermostat); + $smartHome->addDevice($bedroomTV); + $smartHome->addDevice($bedroomCeilingFan); + + $smartHome->addDevice($bathroomMainLight); + $smartHome->addDevice($bathroomMirrorLight); + $smartHome->addDevice($bathroomExhaustFan); + $smartHome->addDevice($bathroomSmartScale); + + return $smartHome; + }); + } +} diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 38b258d..278d87d 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -1,5 +1,9 @@ =7.4" + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.0.4" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" + "ergebnis/phpstan-rules": "^2.2.0", + "illuminate/console": "^11.0.0", + "laravel/pint": "^1.14.0", + "mockery/mockery": "^1.6.7", + "pestphp/pest": "^2.34.1", + "phpstan/phpstan": "^1.10.59", + "phpstan/phpstan-strict-rules": "^1.5.2", + "symfony/var-dumper": "^7.0.4", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, - "bin": [ - "bin/php-parse" - ], "type": "library", "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, "branch-alias": { - "dev-master": "5.0-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { + "files": [ + "src/Functions.php" + ], "psr-4": { - "PhpParser\\": "lib/PhpParser" + "Termwind\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Nikita Popov" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "A PHP parser written in PHP", + "description": "Its like Tailwind CSS, but for the console.", "keywords": [ - "parser", - "php" + "cli", + "console", + "css", + "package", + "php", + "style" ], "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.0.1" }, - "time": "2024-07-01T20:03:41+00:00" + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2024-03-06T16:17:14+00:00" }, { - "name": "nunomaduro/termwind", - "version": "v2.0.1", + "name": "openai-php/client", + "version": "v0.10.1", "source": { "type": "git", - "url": "https://github.com/nunomaduro/termwind.git", - "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a" + "url": "https://github.com/openai-php/client.git", + "reference": "8b63d27a2f009a7ce4714fda77769e93d883c8da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/58c4c58cf23df7f498daeb97092e34f5259feb6a", - "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a", + "url": "https://api.github.com/repos/openai-php/client/zipball/8b63d27a2f009a7ce4714fda77769e93d883c8da", + "reference": "8b63d27a2f009a7ce4714fda77769e93d883c8da", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": "^8.2", - "symfony/console": "^7.0.4" + "php": "^8.1.0", + "php-http/discovery": "^1.19.4", + "php-http/multipart-stream-builder": "^1.3.0", + "psr/http-client": "^1.0.3", + "psr/http-client-implementation": "^1.0.1", + "psr/http-factory-implementation": "*", + "psr/http-message": "^1.1.0|^2.0.0" }, "require-dev": { - "ergebnis/phpstan-rules": "^2.2.0", - "illuminate/console": "^11.0.0", - "laravel/pint": "^1.14.0", - "mockery/mockery": "^1.6.7", - "pestphp/pest": "^2.34.1", - "phpstan/phpstan": "^1.10.59", - "phpstan/phpstan-strict-rules": "^1.5.2", - "symfony/var-dumper": "^7.0.4", - "thecodingmachine/phpstan-strict-rules": "^1.0.0" + "guzzlehttp/guzzle": "^7.8.1", + "guzzlehttp/psr7": "^2.6.2", + "laravel/pint": "^1.16.0", + "mockery/mockery": "^1.6.12", + "nunomaduro/collision": "^7.10.0", + "pestphp/pest": "^2.34.7", + "pestphp/pest-plugin-arch": "^2.7", + "pestphp/pest-plugin-type-coverage": "^2.8.2", + "phpstan/phpstan": "^1.11.2", + "rector/rector": "^1.1.0", + "symfony/var-dumper": "^6.4.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/OpenAI.php" + ], + "psr-4": { + "OpenAI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + }, + { + "name": "Sandro Gehri" + } + ], + "description": "OpenAI PHP is a supercharged PHP API client that allows you to interact with the Open AI API", + "keywords": [ + "GPT-3", + "api", + "client", + "codex", + "dall-e", + "language", + "natural", + "openai", + "php", + "processing", + "sdk" + ], + "support": { + "issues": "https://github.com/openai-php/client/issues", + "source": "https://github.com/openai-php/client/tree/v0.10.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/gehrisandro", + "type": "github" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-06-06T20:27:51+00:00" + }, + { + "name": "openai-php/laravel", + "version": "v0.10.1", + "source": { + "type": "git", + "url": "https://github.com/openai-php/laravel.git", + "reference": "3a49161c46faf22e1ab8332cc1aecd7fff29dd2a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/openai-php/laravel/zipball/3a49161c46faf22e1ab8332cc1aecd7fff29dd2a", + "reference": "3a49161c46faf22e1ab8332cc1aecd7fff29dd2a", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.8.1", + "laravel/framework": "^9.46.0|^10.34.2|^11.0", + "openai-php/client": "^v0.10.1", + "php": "^8.1.0" + }, + "require-dev": { + "laravel/pint": "^1.13.6", + "pestphp/pest": "^2.27.0", + "pestphp/pest-plugin-arch": "^2.4.1", + "phpstan/phpstan": "^1.10.47", + "symfony/var-dumper": "^6.4.0|^7.0.1" }, "type": "library", "extra": { "laravel": { "providers": [ - "Termwind\\Laravel\\TermwindServiceProvider" + "OpenAI\\Laravel\\ServiceProvider" ] - }, - "branch-alias": { - "dev-2.x": "2.x-dev" } }, "autoload": { - "files": [ - "src/Functions.php" - ], "psr-4": { - "Termwind\\": "src/" + "OpenAI\\Laravel\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2289,18 +2674,24 @@ "email": "enunomaduro@gmail.com" } ], - "description": "Its like Tailwind CSS, but for the console.", + "description": "OpenAI PHP for Laravel is a supercharged PHP API client that allows you to interact with the Open AI API", "keywords": [ - "cli", - "console", - "css", - "package", + "GPT-3", + "api", + "client", + "codex", + "dall-e", + "language", + "laravel", + "natural", + "openai", "php", - "style" + "processing", + "sdk" ], "support": { - "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.0.1" + "issues": "https://github.com/openai-php/laravel/issues", + "source": "https://github.com/openai-php/laravel/tree/v0.10.1" }, "funding": [ { @@ -2308,15 +2699,329 @@ "type": "custom" }, { - "url": "https://github.com/nunomaduro", + "url": "https://github.com/gehrisandro", "type": "github" }, { - "url": "https://github.com/xiCO2k", + "url": "https://github.com/nunomaduro", "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" } ], - "time": "2024-03-06T16:17:14+00:00" + "time": "2024-06-06T20:31:52+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.19.4", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "0700efda8d7526335132360167315fdab3aeb599" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/0700efda8d7526335132360167315fdab3aeb599", + "reference": "0700efda8d7526335132360167315fdab3aeb599", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.19.4" + }, + "time": "2024-03-29T13:00:05+00:00" + }, + { + "name": "php-http/multipart-stream-builder", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/multipart-stream-builder.git", + "reference": "ad3d012c44d6a5a1686fea99cbfa560f0fb80448" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/multipart-stream-builder/zipball/ad3d012c44d6a5a1686fea99cbfa560f0fb80448", + "reference": "ad3d012c44d6a5a1686fea99cbfa560f0fb80448", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "php-http/discovery": "^1.15", + "psr/http-factory-implementation": "^1.0" + }, + "require-dev": { + "nyholm/psr7": "^1.0", + "php-http/message": "^1.5", + "php-http/message-factory": "^1.0.2", + "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Message\\MultipartStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + } + ], + "description": "A builder class that help you create a multipart stream", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "multipart stream", + "stream" + ], + "support": { + "issues": "https://github.com/php-http/multipart-stream-builder/issues", + "source": "https://github.com/php-http/multipart-stream-builder/tree/1.4.0" + }, + "time": "2024-09-01T11:08:55+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.4.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "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": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "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.4.1" + }, + "time": "2024-05-21T05:55:05+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "153ae662783729388a584b4361f2545e4d841e3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + }, + "time": "2024-02-23T11:10:43+00:00" }, { "name": "phpoption/phpoption", @@ -2393,6 +3098,53 @@ ], "time": "2024-07-20T21:41:07+00:00" }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.30.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/5ceb0e384997db59f38774bf79c2a6134252c08f", + "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.0" + }, + "time": "2024-08-29T09:54:52+00:00" + }, { "name": "psr/clock", "version": "1.0.0", @@ -2805,85 +3557,6 @@ }, "time": "2021-10-29T13:26:27+00:00" }, - { - "name": "psy/psysh", - "version": "v0.12.4", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/psysh.git", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/2fd717afa05341b4f8152547f142cd2f130f6818", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "nikic/php-parser": "^5.0 || ^4.0", - "php": "^8.0 || ^7.4", - "symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", - "symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" - }, - "conflict": { - "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.2" - }, - "suggest": { - "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", - "ext-pdo-sqlite": "The doc command requires SQLite to work.", - "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." - }, - "bin": [ - "bin/psysh" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "0.12.x-dev" - }, - "bamarni-bin": { - "bin-links": false, - "forward-command": false - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Psy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "An interactive shell for modern PHP.", - "homepage": "http://psysh.org", - "keywords": [ - "REPL", - "console", - "interactive", - "shell" - ], - "support": { - "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.4" - }, - "time": "2024-06-10T01:18:23+00:00" - }, { "name": "ralouphie/getallheaders", "version": "3.0.3", @@ -3097,17 +3770,79 @@ "issues": "https://github.com/ramsey/uuid/issues", "source": "https://github.com/ramsey/uuid/tree/4.7.6" }, - "funding": [ + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2024-04-27T21:32:50+00:00" + }, + { + "name": "spiral/json-schema-generator", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/spiral/json-schema-generator.git", + "reference": "a707e2f21f1eee67f48aeb392662dd26da13a117" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spiral/json-schema-generator/zipball/a707e2f21f1eee67f48aeb392662dd26da13a117", + "reference": "a707e2f21f1eee67f48aeb392662dd26da13a117", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "phpdocumentor/reflection-docblock": "^5.3", + "phpstan/phpdoc-parser": "^1.24", + "symfony/property-info": "^6.3 || ^7.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.38", + "phpunit/phpunit": "^10.4", + "roave/security-advisories": "dev-latest", + "vimeo/psalm": "^5.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spiral\\JsonSchemaGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://github.com/ramsey", - "type": "github" + "name": "Anton Titov (wolfy-j)", + "email": "wolfy-j@spiralscout.com" }, { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" + "name": "Pavel Buchnev (butschster)", + "email": "pavel.buchnev@spiralscout.com" + }, + { + "name": "Aleksei Gagarin (roxblnfk)", + "email": "alexey.gagarin@spiralscout.com" + }, + { + "name": "Maksim Smakouz (msmakouz)", + "email": "maksim.smakouz@spiralscout.com" } ], - "time": "2024-04-27T21:32:50+00:00" + "description": "Provides the ability to generate JSON schemas from Data Transfer Object (DTO) classes", + "homepage": "https://github.com/spiral/json-schema-generator", + "support": { + "issues": "https://github.com/spiral/json-schema-generator/issues", + "source": "https://github.com/spiral/json-schema-generator/tree/v1.1.0" + }, + "time": "2023-12-06T12:12:00+00:00" }, { "name": "symfony/clock", @@ -3185,16 +3920,16 @@ }, { "name": "symfony/console", - "version": "v7.1.3", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9" + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", - "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", + "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111", "shasum": "" }, "require": { @@ -3258,7 +3993,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.3" + "source": "https://github.com/symfony/console/tree/v7.1.4" }, "funding": [ { @@ -3274,7 +4009,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:41:01+00:00" + "time": "2024-08-15T22:48:53+00:00" }, { "name": "symfony/css-selector", @@ -3641,16 +4376,16 @@ }, { "name": "symfony/finder", - "version": "v7.1.3", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "717c6329886f32dc65e27461f80f2a465412fdca" + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca", - "reference": "717c6329886f32dc65e27461f80f2a465412fdca", + "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823", "shasum": "" }, "require": { @@ -3685,7 +4420,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.3" + "source": "https://github.com/symfony/finder/tree/v7.1.4" }, "funding": [ { @@ -3701,7 +4436,7 @@ "type": "tidelift" } ], - "time": "2024-07-24T07:08:44+00:00" + "time": "2024-08-13T14:28:19+00:00" }, { "name": "symfony/http-foundation", @@ -3782,16 +4517,16 @@ }, { "name": "symfony/http-kernel", - "version": "v7.1.3", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "db9702f3a04cc471ec8c70e881825db26ac5f186" + "reference": "6efcbd1b3f444f631c386504fc83eeca25963747" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/db9702f3a04cc471ec8c70e881825db26ac5f186", - "reference": "db9702f3a04cc471ec8c70e881825db26ac5f186", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6efcbd1b3f444f631c386504fc83eeca25963747", + "reference": "6efcbd1b3f444f631c386504fc83eeca25963747", "shasum": "" }, "require": { @@ -3876,7 +4611,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.1.3" + "source": "https://github.com/symfony/http-kernel/tree/v7.1.4" }, "funding": [ { @@ -3892,7 +4627,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T14:58:15+00:00" + "time": "2024-08-30T17:02:28+00:00" }, { "name": "symfony/mailer", @@ -3976,16 +4711,16 @@ }, { "name": "symfony/mime", - "version": "v7.1.2", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "26a00b85477e69a4bab63b66c5dce64f18b0cbfc" + "reference": "ccaa6c2503db867f472a587291e764d6a1e58758" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/26a00b85477e69a4bab63b66c5dce64f18b0cbfc", - "reference": "26a00b85477e69a4bab63b66c5dce64f18b0cbfc", + "url": "https://api.github.com/repos/symfony/mime/zipball/ccaa6c2503db867f472a587291e764d6a1e58758", + "reference": "ccaa6c2503db867f472a587291e764d6a1e58758", "shasum": "" }, "require": { @@ -4040,7 +4775,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.1.2" + "source": "https://github.com/symfony/mime/tree/v7.1.4" }, "funding": [ { @@ -4056,7 +4791,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T10:03:55+00:00" + "time": "2024-08-13T14:28:19+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4830,17 +5565,101 @@ "time": "2024-07-26T12:44:47+00:00" }, { - "name": "symfony/routing", + "name": "symfony/property-info", "version": "v7.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "88a279df2db5b7919cac6f35d6a5d1d7147e6a9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/88a279df2db5b7919cac6f35d6a5d1d7147e6a9b", + "reference": "88a279df2db5b7919cac6f35d6a5d1d7147e6a9b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/string": "^6.4|^7.0", + "symfony/type-info": "^7.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T07:36:36+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8a908a3f22d5a1b5d297578c2ceb41b02fa916d0" + "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8a908a3f22d5a1b5d297578c2ceb41b02fa916d0", - "reference": "8a908a3f22d5a1b5d297578c2ceb41b02fa916d0", + "url": "https://api.github.com/repos/symfony/routing/zipball/1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", + "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", "shasum": "" }, "require": { @@ -4892,7 +5711,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.1.3" + "source": "https://github.com/symfony/routing/tree/v7.1.4" }, "funding": [ { @@ -4908,7 +5727,7 @@ "type": "tidelift" } ], - "time": "2024-07-17T06:10:24+00:00" + "time": "2024-08-29T08:16:25+00:00" }, { "name": "symfony/service-contracts", @@ -4995,16 +5814,16 @@ }, { "name": "symfony/string", - "version": "v7.1.3", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ea272a882be7f20cad58d5d78c215001617b7f07" + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07", - "reference": "ea272a882be7f20cad58d5d78c215001617b7f07", + "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", "shasum": "" }, "require": { @@ -5062,7 +5881,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.3" + "source": "https://github.com/symfony/string/tree/v7.1.4" }, "funding": [ { @@ -5078,7 +5897,7 @@ "type": "tidelift" } ], - "time": "2024-07-22T10:25:37+00:00" + "time": "2024-08-12T09:59:40+00:00" }, { "name": "symfony/translation", @@ -5253,17 +6072,99 @@ "time": "2024-04-18T09:32:20+00:00" }, { - "name": "symfony/uid", + "name": "symfony/type-info", "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "60b28eb733f1453287f1263ed305b96091e0d1dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/60b28eb733f1453287f1263ed305b96091e0d1dc", + "reference": "60b28eb733f1453287f1263ed305b96091e0d1dc", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.0", + "symfony/dependency-injection": "<6.4", + "symfony/property-info": "<6.4" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:59:31+00:00" + }, + { + "name": "symfony/uid", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "bb59febeecc81528ff672fad5dab7f06db8c8277" + "reference": "82177535395109075cdb45a70533aa3d7a521cdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/bb59febeecc81528ff672fad5dab7f06db8c8277", - "reference": "bb59febeecc81528ff672fad5dab7f06db8c8277", + "url": "https://api.github.com/repos/symfony/uid/zipball/82177535395109075cdb45a70533aa3d7a521cdf", + "reference": "82177535395109075cdb45a70533aa3d7a521cdf", "shasum": "" }, "require": { @@ -5308,7 +6209,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.1.1" + "source": "https://github.com/symfony/uid/tree/v7.1.4" }, "funding": [ { @@ -5324,20 +6225,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-08-12T09:59:40+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.1.3", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "86af4617cca75a6e28598f49ae0690f3b9d4591f" + "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/86af4617cca75a6e28598f49ae0690f3b9d4591f", - "reference": "86af4617cca75a6e28598f49ae0690f3b9d4591f", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/a5fa7481b199090964d6fd5dab6294d5a870c7aa", + "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa", "shasum": "" }, "require": { @@ -5391,7 +6292,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.1.3" + "source": "https://github.com/symfony/var-dumper/tree/v7.1.4" }, "funding": [ { @@ -5407,7 +6308,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:41:01+00:00" + "time": "2024-08-30T16:12:47+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -5865,72 +6766,6 @@ }, "time": "2020-07-09T08:09:16+00:00" }, - { - "name": "laravel/pint", - "version": "v1.17.2", - "source": { - "type": "git", - "url": "https://github.com/laravel/pint.git", - "reference": "e8a88130a25e3f9d4d5785e6a1afca98268ab110" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/e8a88130a25e3f9d4d5785e6a1afca98268ab110", - "reference": "e8a88130a25e3f9d4d5785e6a1afca98268ab110", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-mbstring": "*", - "ext-tokenizer": "*", - "ext-xml": "*", - "php": "^8.1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.61.1", - "illuminate/view": "^10.48.18", - "larastan/larastan": "^2.9.8", - "laravel-zero/framework": "^10.4.0", - "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.35.0" - }, - "bin": [ - "builds/pint" - ], - "type": "project", - "autoload": { - "psr-4": { - "App\\": "app/", - "Database\\Seeders\\": "database/seeders/", - "Database\\Factories\\": "database/factories/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" - } - ], - "description": "An opinionated code formatter for PHP.", - "homepage": "https://laravel.com", - "keywords": [ - "format", - "formatter", - "lint", - "linter", - "php" - ], - "support": { - "issues": "https://github.com/laravel/pint/issues", - "source": "https://github.com/laravel/pint" - }, - "time": "2024-08-06T15:11:54+00:00" - }, { "name": "laravel/sail", "version": "v1.31.1", @@ -6137,6 +6972,64 @@ ], "time": "2024-06-12T14:39:25+00:00" }, + { + "name": "nikic/php-parser", + "version": "v5.1.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + }, + "time": "2024-07-01T20:03:41+00:00" + }, { "name": "nunomaduro/collision", "version": "v8.4.0", @@ -7700,16 +8593,16 @@ }, { "name": "symfony/yaml", - "version": "v7.1.1", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "fa34c77015aa6720469db7003567b9f772492bf2" + "reference": "92e080b851c1c655c786a2da77f188f2dccd0f4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/fa34c77015aa6720469db7003567b9f772492bf2", - "reference": "fa34c77015aa6720469db7003567b9f772492bf2", + "url": "https://api.github.com/repos/symfony/yaml/zipball/92e080b851c1c655c786a2da77f188f2dccd0f4b", + "reference": "92e080b851c1c655c786a2da77f188f2dccd0f4b", "shasum": "" }, "require": { @@ -7751,7 +8644,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.1.1" + "source": "https://github.com/symfony/yaml/tree/v7.1.4" }, "funding": [ { @@ -7767,7 +8660,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-08-12T09:59:40+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/agents.php b/config/agents.php new file mode 100644 index 0000000..14efad0 --- /dev/null +++ b/config/agents.php @@ -0,0 +1,53 @@ + [ + CodeReviewAgentFactory::class, + DeliveryAgentFactory::class, + SmartHomeControlAgentFactory::class, + TaskSplitterAgentFactory::class, + SiteStatusChecker\SiteStatusCheckerAgentFactory::class, + ], + 'tools' => [ + \App\Agents\AgentsCaller\AskAgentTool::class, + + // Code Reviewer + \App\Agents\CodeReviewer\ListProjectTool::class, + \App\Agents\CodeReviewer\ReadFileTool::class, + \App\Agents\CodeReviewer\ReviewTool::class, + + // Delivery + \App\Agents\Delivery\GetOrderNumberTool::class, + \App\Agents\Delivery\GetDeliveryDateTool::class, + \App\Agents\Delivery\GetOrderNumberTool::class, + \App\Agents\Delivery\GetProfileTool::class, + + // Dynamic memory + \App\Agents\DynamicMemoryTool\DynamicMemoryTool::class, + + + // Smart Home Control + \App\Agents\SmartHomeControl\ControlDeviceTool::class, + \App\Agents\SmartHomeControl\GetDeviceDetailsTool::class, + \App\Agents\SmartHomeControl\GetRoomListTool::class, + \App\Agents\SmartHomeControl\ListRoomDevicesTool::class, + + // Task splitter + \App\Agents\TaskSplitter\TaskCreateTool::class, + \App\Agents\TaskSplitter\GetProjectDescription::class, + + // Site Status Checker + SiteStatusChecker\CheckSiteAvailabilityTool::class, + SiteStatusChecker\GetDNSInfoTool::class, + SiteStatusChecker\PerformPingTestTool::class, + ], +]; diff --git a/config/openai.php b/config/openai.php new file mode 100644 index 0000000..67806b4 --- /dev/null +++ b/config/openai.php @@ -0,0 +1,28 @@ + env('OPENAI_API_KEY'), + 'organization' => env('OPENAI_ORGANIZATION'), + + /* + |-------------------------------------------------------------------------- + | Request Timeout + |-------------------------------------------------------------------------- + | + | The timeout may be used to specify the maximum number of seconds to wait + | for a response. By default, the client will time out after 30 seconds. + */ + + 'request_timeout' => env('OPENAI_REQUEST_TIMEOUT', 30), +]; diff --git a/database/migrations/2024_09_01_072614_create_session_table.php b/database/migrations/2024_09_01_072614_create_session_table.php new file mode 100644 index 0000000..486f1f7 --- /dev/null +++ b/database/migrations/2024_09_01_072614_create_session_table.php @@ -0,0 +1,33 @@ +uuid('id')->primary(); + $table->uuid('account_uuid'); + $table->string('agent_name'); + $table->string('title')->nullable(); + $table->json('history')->nullable(); + $table->timestamp('finished_at')->nullable(); + $table->softDeletes(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('session'); + } +}; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..644ed39 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,56 @@ +services: + laravel.test: + build: + context: ./vendor/laravel/sail/runtimes/8.3 + dockerfile: Dockerfile + args: + WWWGROUP: '${WWWGROUP}' + image: sail-8.3/app + extra_hosts: + - 'host.docker.internal:host-gateway' + ports: + - '${APP_PORT:-80}:80' + - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' + environment: + WWWUSER: '${WWWUSER}' + LARAVEL_SAIL: 1 + XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}' + XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}' + IGNITION_LOCAL_SITES_PATH: '${PWD}' + volumes: + - '.:/var/www/html' + networks: + - sail + depends_on: + - pgsql + pgsql: + image: 'postgres:15' + ports: + - '${FORWARD_DB_PORT:-5432}:5432' + environment: + PGPASSWORD: '${DB_PASSWORD:-secret}' + POSTGRES_DB: '${DB_DATABASE}' + POSTGRES_USER: '${DB_USERNAME}' + POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}' + volumes: + - 'sail-pgsql:/var/lib/postgresql/data' + - './vendor/laravel/sail/database/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql' + networks: + - sail + healthcheck: + test: + - CMD + - pg_isready + - '-q' + - '-d' + - '${DB_DATABASE}' + - '-U' + - '${DB_USERNAME}' + retries: 3 + timeout: 5s +networks: + sail: + driver: bridge +volumes: + sail-pgsql: + driver: local diff --git a/phpunit.xml b/phpunit.xml index 506b9a3..c09b5bc 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -22,8 +22,7 @@ - - +