diff --git a/nexus/agents/__init__.py b/nexus/agents/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nexus/agents/api/__init__.py b/nexus/agents/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nexus/agents/api/routers.py b/nexus/agents/api/routers.py new file mode 100644 index 00000000..5189b0c7 --- /dev/null +++ b/nexus/agents/api/routers.py @@ -0,0 +1,7 @@ +from django.urls import path +from nexus.agents.api.views import PushAgents + + +urlpatterns = [ + path('agents/push', PushAgents.as_view(), name="push-agents"), +] diff --git a/nexus/agents/api/views.py b/nexus/agents/api/views.py new file mode 100644 index 00000000..f442fe42 --- /dev/null +++ b/nexus/agents/api/views.py @@ -0,0 +1,43 @@ +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated + +from nexus.usecases.projects import get_project_by_uuid +from nexus.usecases.agents import AgentDTO, AgentUsecase +from nexus.agents.models import Agent + +from nexus.projects.api.permissions import ProjectPermission + + +class PushAgents(APIView): + + permission_classes = [IsAuthenticated, ProjectPermission] + + def post(self, request, *args, **kwargs): + def get_agents(agents): + dto_list = [] + for agent_slug in agents: + agent = agents.get(agent_slug) + agent.update({"slug": agent_slug}) + dto_list.append(AgentDTO(**agent)) + return dto_list + + project_uuid = request.data.get("project") + project = get_project_by_uuid(project_uuid) + + agents = request.data.get("agents") + agents_dto = get_agents(agents) + + print(agents_dto) + + agents = [] + + for agent_dto in agents_dto: + agent: Agent = AgentUsecase().create_agent(request.user, agent_dto, project_uuid) + + agents.append({"agent_name": agent.display_name, "agent_external_id": agent.external_id}) + + return Response({ + "project": str(project.uuid), + "agents": agents + }) diff --git a/nexus/agents/apps.py b/nexus/agents/apps.py new file mode 100644 index 00000000..fb85e72d --- /dev/null +++ b/nexus/agents/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AgentsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'nexus.agents' diff --git a/nexus/agents/migrations/0001_initial.py b/nexus/agents/migrations/0001_initial.py new file mode 100644 index 00000000..c7c5d87b --- /dev/null +++ b/nexus/agents/migrations/0001_initial.py @@ -0,0 +1,63 @@ +# Generated by Django 4.2.10 on 2024-12-27 20:15 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('projects', '0008_merge_20241010_2217'), + ] + + operations = [ + migrations.CreateModel( + name='Team', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('external_id', models.CharField(help_text='Supervisor ID', max_length=255)), + ('project', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='projects.project')), + ], + ), + migrations.CreateModel( + name='Agent', + fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('modified_at', models.DateTimeField(blank=True, null=True)), + ('external_id', models.CharField(max_length=255)), + ('slug', models.SlugField(max_length=255)), + ('display_name', models.CharField(max_length=255)), + ('model', models.CharField(max_length=255)), + ('is_official', models.BooleanField(default=False)), + ('metadata', models.JSONField(default=dict)), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='created_%(class)ss', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='modified_%(class)ss', to=settings.AUTH_USER_MODEL)), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='projects.project')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ActiveAgent', + fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('modified_at', models.DateTimeField(blank=True, null=True)), + ('is_official', models.BooleanField(default=False)), + ('agent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='agents.agent')), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='created_%(class)ss', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='modified_%(class)ss', to=settings.AUTH_USER_MODEL)), + ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='agents.team')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/nexus/agents/migrations/__init__.py b/nexus/agents/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nexus/agents/models.py b/nexus/agents/models.py new file mode 100644 index 00000000..68381f7a --- /dev/null +++ b/nexus/agents/models.py @@ -0,0 +1,24 @@ +from django.db import models +from nexus.projects.models import Project +from nexus.db.models import BaseModel + + +class Agent(BaseModel): + external_id = models.CharField(max_length=255) + slug = models.SlugField(max_length=255) + display_name = models.CharField(max_length=255) + model = models.CharField(max_length=255) + is_official = models.BooleanField(default=False) + project = models.ForeignKey(Project, on_delete=models.CASCADE) + metadata = models.JSONField(default=dict) + + +class Team(models.Model): + external_id = models.CharField(max_length=255, help_text="Supervisor ID") + project = models.OneToOneField(Project, on_delete=models.CASCADE) + + +class ActiveAgent(BaseModel): + agent = models.ForeignKey(Agent, on_delete=models.CASCADE) + team = models.ForeignKey(Team, on_delete=models.CASCADE) + is_official = models.BooleanField(default=False) diff --git a/nexus/projects/api/permissions.py b/nexus/projects/api/permissions.py index 5e28111c..c3c245f7 100644 --- a/nexus/projects/api/permissions.py +++ b/nexus/projects/api/permissions.py @@ -9,7 +9,16 @@ class ProjectPermission(permissions.BasePermission): def has_permission(self, request, view): try: - project_uuid = view.kwargs["project_uuid"] + uuids = [ + view.kwargs.get("project_uuid"), + request.data.get("project"), + request.data.get("project_uuid") + ] + + uuids = set(uuids) + uuids.remove(None) + project_uuid = list(uuids)[0] + project = ProjectsUseCase().get_by_uuid(project_uuid) return has_project_permission( user=request.user, diff --git a/nexus/settings.py b/nexus/settings.py index 7ddfa8f7..5e809b3e 100644 --- a/nexus/settings.py +++ b/nexus/settings.py @@ -82,6 +82,7 @@ 'nexus.logs', 'nexus.sentry', 'nexus.zeroshot', + 'nexus.agents', ] MIDDLEWARE = [ diff --git a/nexus/task_managers/file_database/bedrock.py b/nexus/task_managers/file_database/bedrock.py index 35c38f8d..891c21ee 100644 --- a/nexus/task_managers/file_database/bedrock.py +++ b/nexus/task_managers/file_database/bedrock.py @@ -1,12 +1,14 @@ import uuid import json -from typing import Dict, List, Any +import time +from typing import Dict, List, Any, Tuple from os.path import basename import boto3 from django.conf import settings from nexus.task_managers.file_database.file_database import FileDataBase, FileResponseDTO +from nexus.agents.src.utils.bedrock_agent_helper import AgentsForAmazonBedrock class BedrockFileDatabase(FileDataBase): @@ -24,6 +26,53 @@ def __init__(self) -> None: self.bedrock_agent_runtime = self.__get_bedrock_agent_runtime() self.bedrock_runtime = self.__get_bedrock_runtime() + self.agent_for_amazon_bedrock = AgentsForAmazonBedrock() + self.agent_foundation_model = [ + 'anthropic.claude-3-sonnet-2034240229-v1:0', + 'anthropic.claude-3-5-sonnet-20240620-v1:0', + 'anthropic.claude-3-haiku-20240307-v1:0' + ] + + def prepare_agent(self, agent_id: str): + self.bedrock_agent.prepare_agent(agentId=agent_id) + time.sleep(5) + return + + def create_supervisor(self, supervisor_name, supervisor_description, supervisor_instructions): + supervisor_id, supervisor_alias, supervisor_arn = self.agent_for_amazon_bedrock.create_agent( + agent_collaboration='SUPERVISOR_ROUTER', + agent_name=supervisor_name, + agent_description=supervisor_description, + agent_instructions=supervisor_instructions, + model_ids=self.agent_foundation_model, + ) + return supervisor_id + + def create_agent_alias(self, agent_id: str, alias_name: str): + sub_agent_alias_id, sub_agent_alias_arn = self.agent_for_amazon_bedrock.create_agent_alias( + agent_id=agent_id, alias_name=alias_name + ) + return sub_agent_alias_id, sub_agent_alias_arn + + def associate_sub_agents(self, supervisor_id: str, agents_list: str) -> str: + supervisor_agent_alias_id, supervisor_agent_alias_arn = self.agent_for_amazon_bedrock.associate_sub_agents( + supervisor_agent_id=supervisor_id, + sub_agents_list=agents_list, + ) + return supervisor_agent_alias_id + + def create_agent(self, agent_name: str, agent_description: str, agent_instructions: str) -> Tuple[str, str, str]: + agent_id, agent_alias, agent_arn = self.agent_for_amazon_bedrock.create_agent( + agent_name=agent_name, + agent_description=agent_description, + agent_instructions=agent_instructions, + model_ids=self.agent_foundation_model, + agent_collaboration="DISABLED", + code_interpretation=False + ) + time.sleep(5) + return agent_id, agent_alias, agent_arn + def invoke_model(self, prompt: str, config_data: Dict): data = { "top_p": config_data.get("top_p"), diff --git a/nexus/urls.py b/nexus/urls.py index 82959d0b..3d2e4c75 100644 --- a/nexus/urls.py +++ b/nexus/urls.py @@ -11,6 +11,8 @@ from nexus.projects.api.routers import urlpatterns as projects_routes from nexus.logs.api.routers import urlpatterns as logs_routes from nexus.zeroshot.api.routers import urlpatterns as zeroshot_routes +from nexus.agents.api.routers import urlpatterns as agent_routes + schema_view = get_schema_view( openapi.Info( @@ -28,6 +30,7 @@ url_api += actions_routes url_api += projects_routes url_api += logs_routes +url_api += agent_routes urlpatterns = [ path("", lambda _: HttpResponse()), diff --git a/nexus/usecases/agents/__init__.py b/nexus/usecases/agents/__init__.py new file mode 100644 index 00000000..8032cb69 --- /dev/null +++ b/nexus/usecases/agents/__init__.py @@ -0,0 +1 @@ +from .agents import AgentUsecase, AgentDTO diff --git a/nexus/usecases/agents/agents.py b/nexus/usecases/agents/agents.py new file mode 100644 index 00000000..a17368a2 --- /dev/null +++ b/nexus/usecases/agents/agents.py @@ -0,0 +1,105 @@ +from typing import List, Dict, Tuple +from dataclasses import dataclass + +from nexus.agents.models import Agent, Team +from nexus.task_managers.file_database.bedrock import BedrockFileDatabase + + +@dataclass +class AgentDTO: + slug: str + name: str + description: str + instructions: List[str] + guardrails: List[str] + skills: List[Dict] + model: str + + +class AgentUsecase: + def __init__(self, external_agent_client=BedrockFileDatabase): + self.external_agent_client = external_agent_client() + + def prepare_agent(self, agent_id: str): + self.external_agent_client.prepare_agent(agent_id) + return + + def create_external_supervisor( + self, + supervisor_name: str, + supervisor_description: str, + supervisor_instructions: str, + ) -> str: + return self.external_agent_client.create_supervisor( + supervisor_name, + supervisor_description, + supervisor_instructions, + ) + + def create_external_agent(self, agent_name: str, agent_description: str, agent_instructions: str): + return self.external_agent_client.create_agent( + agent_name, + agent_description, + agent_instructions + ) + + def create_external_agent_alias(self, agent_id: str, alias_name: str) -> Tuple[str, str]: + agent_alias_id, agent_alias_arn = self.external_agent_client.create_agent_alias( + agent_id=agent_id, alias_name=alias_name + ) + return agent_alias_id, agent_alias_arn + + def create_agent(self, user, agent_dto: AgentDTO, project_uuid: str, alias_name: str = "v1"): + def format_instructions(instructions: List[str]): + return "\n".join(instructions) + + agent_slug = f"{agent_dto.slug}-{project_uuid}" + external_id, _agent_alias_id, _agent_alias_arn = self.create_external_agent( + agent_name=agent_slug, + agent_description=agent_dto.description, + agent_instructions=format_instructions(agent_dto.instructions), + ) + + self.prepare_agent(external_id) + + self.external_agent_client.agent_for_amazon_bedrock.wait_agent_status_update(external_id) + + sub_agent_alias_id, sub_agent_alias_arn = self.create_external_agent_alias( + agent_id=external_id, alias_name=alias_name + ) + + agent = Agent.objects.create( + created_by=user, + project_id=project_uuid, + external_id=external_id, + slug=agent_slug, + display_name=agent_dto.name, + model=agent_dto.model, + metadata={ + "engine": "BEDROCK", + "_agent_alias_id": _agent_alias_id, + "_agent_alias_arn": _agent_alias_arn, + "agent_alias_id": sub_agent_alias_id, + "agent_alias_arn": sub_agent_alias_arn, + } + ) + return agent + + def create_supervisor( + self, + project_uuid: str, + supervisor_name: str, + supervisor_description: str, + supervisor_instructions: str + ): + external_id = self.create_external_supervisor( + supervisor_name, + supervisor_description, + supervisor_instructions, + ) + + Team.objects.create( + project_id=project_uuid, + external_id=external_id + ) + return diff --git a/nexus/usecases/projects/projects_use_case.py b/nexus/usecases/projects/projects_use_case.py index 84eb78b9..d5494eec 100644 --- a/nexus/usecases/projects/projects_use_case.py +++ b/nexus/usecases/projects/projects_use_case.py @@ -1,3 +1,5 @@ +from django.template.defaultfilters import slugify + from nexus.projects.models import Project from nexus.projects.project_dto import ProjectCreationDTO from nexus.projects.exceptions import ProjectDoesNotExist @@ -10,6 +12,8 @@ create_integrated_intelligence, create_llm ) +from nexus.usecases.agents import AgentUsecase + from .create import ProjectAuthUseCase from nexus.usecases import orgs from nexus.events import event_manager @@ -26,6 +30,7 @@ def __init__( event_manager_notify=event_manager.notify, ) -> None: self.event_manager_notify = event_manager_notify + pass def get_by_uuid(self, project_uuid: str) -> Project: try: @@ -58,7 +63,7 @@ def create_brain_project_base( usecase = CreateContentBaseUseCase( event_manager_notify=self.event_manager_notify, ) - usecase.create_contentbase( + content_base = usecase.create_contentbase( intelligence_uuid=base_intelligence.uuid, user_email=user_email, title=project_dto.name, @@ -77,6 +82,22 @@ def create_brain_project_base( } ) create_llm(llm_dto=llm_dto) + return content_base + + def create_agent_builder_base( + self, + project_uuid: str, + supervisor_name: str, + supervisor_description: str, + supervisor_instructions: str, + ): + agents_usecase = AgentUsecase(BedrockFileDatabase) + agents_usecase.create_supervisor( + project_uuid=project_uuid, + supervisor_name=supervisor_name, + supervisor_description=supervisor_description, + supervisor_instructions=supervisor_instructions, + ) def create_project( self, @@ -117,6 +138,16 @@ def create_project( consumer_msg=auth_consumer_msg ) + supervisor_name = slugify(f"{project.name}-{project.uuid}-supervisor") + supervisor_description = f"Supervisor Agent for {project.name} {project.uuid}" + supervisor_instructions = settings.DEFAULT_AGENT_GOAL + + self.create_agent_builder_base(# TODO: SET ENV VAR TO CHOOSE WHICH PROJECTS CREATE WITH AB 2.0 + str(project.uuid), + supervisor_name=supervisor_name, + supervisor_description=supervisor_description, + supervisor_instructions=supervisor_instructions, + ) return project def get_indexer_database_by_uuid(self, project_uuid: str): diff --git a/poetry.lock b/poetry.lock index bff40e5e..1ab7bb63 100644 --- a/poetry.lock +++ b/poetry.lock @@ -337,17 +337,17 @@ flake8 = ">=3.8,<5.0.0" [[package]] name = "boto3" -version = "1.35.9" +version = "1.35.87" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.35.9-py3-none-any.whl", hash = "sha256:1ee9c52d83e8f4902300e985d62688cf31ca8fc47a80534b4295350ebc418e46"}, - {file = "boto3-1.35.9.tar.gz", hash = "sha256:07e0f335d801765999da67325455ea8219c1a6d7f06bdaad0975ee505276bcbe"}, + {file = "boto3-1.35.87-py3-none-any.whl", hash = "sha256:588ab05e2771c50fca5c242be14e7a25200ffd3dd95c45950ce40993473864c7"}, + {file = "boto3-1.35.87.tar.gz", hash = "sha256:341c58602889078a4a25dc4331b832b5b600a33acd73471d2532c6f01b16fbb4"}, ] [package.dependencies] -botocore = ">=1.35.9,<1.36.0" +botocore = ">=1.35.87,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -356,13 +356,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.35.9" +version = "1.35.88" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.35.9-py3-none-any.whl", hash = "sha256:92962460e4f35d139a23bca28149722030143257ee2916de442243c2464a7434"}, - {file = "botocore-1.35.9.tar.gz", hash = "sha256:9e44572fd2401b89dd58bf8b71ac2c36d5b0437f8cbf40de83302c499965fb54"}, + {file = "botocore-1.35.88-py3-none-any.whl", hash = "sha256:e60cc3fbe8d7a10f70e7e852d76be2b29f23ead418a5899d366ea32b1eacb5a5"}, + {file = "botocore-1.35.88.tar.gz", hash = "sha256:58dcd9a464c354b8c6c25261d8de830d175d9739eae568bf0c52e57116fb03c6"}, ] [package.dependencies] @@ -371,7 +371,7 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.21.2)"] +crt = ["awscrt (==0.22.0)"] [[package]] name = "celery" @@ -2112,6 +2112,30 @@ files = [ docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "marshmallow" version = "3.20.2" @@ -2157,6 +2181,17 @@ files = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "mozilla-django-oidc" version = "4.0.0" @@ -3319,6 +3354,25 @@ files = [ [package.dependencies] requests = ">=2.0.1,<3.0.0" +[[package]] +name = "rich" +version = "13.9.4" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + [[package]] name = "ruff" version = "0.1.15" @@ -3639,6 +3693,20 @@ files = [ [package.extras] doc = ["reno", "sphinx", "tornado (>=4.5)"] +[[package]] +name = "termcolor" +version = "2.5.0" +description = "ANSI color formatting for output in terminal" +optional = false +python-versions = ">=3.9" +files = [ + {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, + {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + [[package]] name = "tomli" version = "2.0.1" @@ -4135,4 +4203,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.12" -content-hash = "07f4719ede627b88cf5e503aa3391386f36f068a2e4e8ee755c9caef761b5467" +content-hash = "aa2ef5dca33b5a6cc92865cfa6f2e9dfdc6f6ac15bc3809b00dfcf6ceda4f367" diff --git a/pyproject.toml b/pyproject.toml index 1b13d4f1..e88762f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ django-celery-results = "2.5.1" redis = "^5.0.1" requests = "^2.31.0" drf-yasg = "^1.21.7" -boto3 = "^1.34.6" +boto3 = "1.35.87" amqp = "^5.2.0" sentry-sdk = "^1.39.2" mozilla-django-oidc = "^4.0.0" @@ -47,6 +47,8 @@ django-redis = "^5.4.0" channels = {extras = ["daphne"], version = "^4.2.0"} channels-redis = "^4.2.1" emoji = "^2.14.0" +termcolor = "^2.5.0" +rich = "^13.9.4"