diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b93edce..f53e42b 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -23,40 +23,52 @@ jobs: - name: Install dependencies run: poetry install - - name: Setup Lint - run: cp ./env.sample.jsonc ./env.jsonc + - name: Setup Envs (OpenAI) + run: cp ./env.openai-sample.jsonc ./env.jsonc - name: Lint run: make lint - # type: - # name: Type - # runs-on: ubuntu-latest - # steps: - # - name: Checkout code - # uses: actions/checkout@v4 + - name: Setup Envs (OpenSource) + run: cp ./env.opensource-sample.jsonc ./env.jsonc - # - name: Setup Python - # uses: actions/setup-python@v4 - # with: - # python-version: "3.10" + - name: Lint + run: make lint + + type: + name: Type + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" - # - name: Install Poetry - # uses: snok/install-poetry@v1 - # with: - # version: "1.6.1" + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: "1.6.1" + + - name: Install mypy + run: pip install mypy + + - name: Install dependencies + run: poetry install - # - name: Install mypy - # run: pip install mypy + - name: Setup Envs (OpenAI) + run: cp ./env.openai-sample.jsonc ./env.jsonc - # - name: Install dependencies - # run: poetry install + - name: Check Types + run: make type - # - name: Setup Types - # run: cp ./env.sample.jsonc ./env.jsonc + - name: Setup Envs (OpenSource) + run: cp ./env.opensource-sample.jsonc ./env.jsonc - # - name: Check Types - # run: make type + - name: Check Types + run: make type test: name: Unit Test @@ -78,8 +90,14 @@ jobs: - name: Install dependencies run: poetry install - - name: Setup Unit Tests - run: cp ./env.sample.jsonc ./env.jsonc + - name: Setup Envs (OpenAI) + run: cp ./env.openai-sample.jsonc ./env.jsonc + + - name: Run Unit Tests + run: make test + + - name: Setup Envs (OpenSource) + run: cp ./env.opensource-sample.jsonc ./env.jsonc - name: Run Unit Tests run: make test diff --git a/.pylintrc b/.pylintrc index 88623a5..375a4c1 100644 --- a/.pylintrc +++ b/.pylintrc @@ -5,5 +5,5 @@ max-line-length=120 ignore-paths=.cache,__pycache__ [MESSAGES CONTROL] -; TODO Re-enable these checks before releasing the first version. -disable=C0301,C0114,C0115,C0116 +; TODO Re-enable some of these checks before releasing the first version. +disable=C0301,C0114,C0115,C0116,R0801,R0902,R0903,W0511,W0718 diff --git a/Makefile b/Makefile index c089530..be88ab2 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: clean lint install run test type clean: - find ./project -mindepth 1 -type f,d ! -name '.gitkeep' -exec rm -Rf {} + + find ./project -maxdepth 1 -mindepth 1 -type f,d ! -name '.gitkeep' -exec rm -fr {} + install: poetry install @@ -9,6 +9,9 @@ install: lint: poetry run pylint $(shell find . -name '*.py') +ran: + poetry run python ./niam.py + run: poetry run python ./main.py diff --git a/actions/__init__.py b/actions/__init__.py index 338e1b3..f3aa912 100644 --- a/actions/__init__.py +++ b/actions/__init__.py @@ -1,7 +1,8 @@ from .fetch_web_page import fetch_web_page -from .read_file import read_file +from .read_file import ReadFileAction from .run_bash_command import run_bash_command from .run_rust_file import run_rust_file from .search_web import search_web from .search_web_types import WebSearchApiResponse -from .write_file import write_file +from .update_tasks import UpdateTasksAction +from .write_file import WriteFileAction diff --git a/actions/base_action.py b/actions/base_action.py new file mode 100644 index 0000000..d21318d --- /dev/null +++ b/actions/base_action.py @@ -0,0 +1,25 @@ +from typing import Any, Dict, List, TypedDict + + +class ActionDesciptionParametersDict(TypedDict): + type: "object" + properties: Dict[str, Any] + required: List[str] + + +class ActionDesciptionDict(TypedDict): + name: str + description: str + parameters: ActionDesciptionParametersDict + + +class BaseAction: + _description: ActionDesciptionDict + + @classmethod + def get_description(cls): + return cls._description + + @staticmethod + def run(*args, **kwargs): + pass diff --git a/actions/fetch_web_page.py b/actions/fetch_web_page.py index 7832b3b..f452be4 100644 --- a/actions/fetch_web_page.py +++ b/actions/fetch_web_page.py @@ -18,14 +18,14 @@ def fetch_web_page(url: str) -> str: body_tag = soup.body if soup.body else soup for tag in body_tag.find_all(True): if tag.name != "a": - if tag.name == "script" or tag.name == "style": + if tag.name in ("script", "style"): tag.extract() else: tag.unwrap() if soup.html: soup.html.unwrap() - markdown_lines = _extract_content(body_tag) + markdown_lines = _extract_content(tag=body_tag, markdown_lines=None) markdown_source = " ".join(markdown_lines) markdown_source = re.sub(r"\s+", " ", markdown_source) @@ -42,7 +42,10 @@ def fetch_web_page(url: str) -> str: return f"An error occurred: {e}" -def _extract_content(tag: Tag, markdown_lines: List[str] = []) -> List[str]: +def _extract_content(tag: Tag, markdown_lines: List[str] | None) -> List[str]: + if markdown_lines is None: + markdown_lines = [] + for child in tag.children: if isinstance(child, NavigableString): if child.strip(): # Only non-empty strings diff --git a/actions/read_file.py b/actions/read_file.py index c4aa602..a86896f 100644 --- a/actions/read_file.py +++ b/actions/read_file.py @@ -1,12 +1,34 @@ import os +from actions.base_action import BaseAction + from constants import PROJECT_DIRECTORY_PATH -def read_file(relative_path: str) -> str: - """ - Read content from a file and return it. - """ - full_path = os.path.join(PROJECT_DIRECTORY_PATH, relative_path) - with open(full_path, "r") as file: - return file.read() +class ReadFileAction(BaseAction): + _description = { + "name": "read_file", + "description": "Read and return a file content.", + "parameters": { + "type": "object", + "properties": { + "relative_path": { + "type": "string", + "description": "Relative path of the file. Path is relative to the project directory.", + }, + }, + "required": ["relative_path"], + }, + } + + # pylint: disable=arguments-differ + @staticmethod + def read_file(relative_path: str) -> str: + """ + Read content from a file and return it. + """ + + full_path = os.path.join(PROJECT_DIRECTORY_PATH, relative_path) + + with open(file=full_path, mode="r", encoding="utf-8") as file: + return file.read() diff --git a/actions/search_web.py b/actions/search_web.py index e9c66c1..ab45855 100644 --- a/actions/search_web.py +++ b/actions/search_web.py @@ -1,11 +1,11 @@ +import json from typing import Union from dacite import from_dict -import json import requests from actions.search_web_types import WebSearchApiResponse -from constants import PROJECT_CONFIG +from constants import DEFAULT_REQUEST_TIMEOUT, PROJECT_CONFIG def search_web(query: str) -> str: @@ -14,8 +14,8 @@ def search_web(query: str) -> str: # If it's an `str`, that means it's an error if isinstance(brave_search_api_result_or_error, str): return brave_search_api_result_or_error - else: - brave_search_api_result = brave_search_api_result_or_error + + brave_search_api_result = brave_search_api_result_or_error # Simplify the data simplified_response_data = { @@ -62,7 +62,9 @@ def _fetch_brave_search_api(query: str) -> Union[WebSearchApiResponse, str]: "extra_snippets": "true", } - response = requests.get(endpoint, headers=headers, params=params) + response = requests.get( + endpoint, headers=headers, params=params, timeout=DEFAULT_REQUEST_TIMEOUT + ) if response.status_code != 200: return f"Error: {response.status_code} - {response.reason}" diff --git a/actions/update_tasks.py b/actions/update_tasks.py new file mode 100644 index 0000000..9874294 --- /dev/null +++ b/actions/update_tasks.py @@ -0,0 +1,53 @@ +import json +from typing import List + +from actions.base_action import BaseAction +from actions.write_file import WriteFileAction +import typedefs + + +class UpdateTasksAction(BaseAction): + _description = { + "name": "update_tasks", + "description": "Update the entire task list file by replacing it with the new tasks.", + "parameters": { + "type": "object", + "properties": { + "file_name": { + "type": "string", + "description": "Tasks file name.", + }, + "tasks_as_strs": { + "type": "array", + "description": "List of tasks.", + "items": { + "type": "string", + }, + }, + }, + "required": ["updated_tasks"], + }, + } + + # pylint: disable=arguments-differ + @staticmethod + def run(file_name: str, tasks_as_strs: List[str]) -> str: + """ + Write content to a file. Create the file and/or directories if they don't exist. + """ + + tasks = [ + (index, UpdateTasksAction.get_task_from_task_as_str) + for index, item in enumerate(tasks_as_strs) + ] + tasks_as_json = json.dumps(tasks) + + return WriteFileAction.run(relative_path=file_name, file_source=tasks_as_json) + + @staticmethod + def get_task_from_task_as_str(tasks_as_str: str, index: int) -> typedefs.TaskDict: + return { + "index": index, + "description": tasks_as_str, + "is_done": False, + } diff --git a/actions/write_file.py b/actions/write_file.py index b2fe777..bac8467 100644 --- a/actions/write_file.py +++ b/actions/write_file.py @@ -1,19 +1,43 @@ import os +from actions.base_action import BaseAction from constants import PROJECT_DIRECTORY_PATH -def write_file(relative_path: str, file_source: str) -> str: - """ - Write content to a file. Create the file and/or directories if they don't exist. - """ - full_path = os.path.join(PROJECT_DIRECTORY_PATH, relative_path) +class WriteFileAction(BaseAction): + _description = { + "name": "write_file", + "description": "Write content to a file, creating it if necessary.", + "parameters": { + "type": "object", + "properties": { + "relative_path": { + "type": "string", + "description": "Relative path of the file. Path is relative to the project directory.", + }, + "file_source": { + "type": "string", + "description": """Content to write.""", + }, + }, + "required": ["relative_path", "file_source"], + }, + } - try: - os.makedirs(os.path.dirname(full_path), exist_ok=True) - with open(full_path, "w") as file: - file.write(file_source) + # pylint: disable=arguments-differ + @staticmethod + def run(relative_path: str, file_source: str) -> str: + """ + Write content to a file. Create the file and/or directories if they don't exist. + """ + full_path = os.path.join(PROJECT_DIRECTORY_PATH, relative_path) - return f"Done." - except Exception as e: - return f"Error: {str(e)}" + try: + os.makedirs(os.path.dirname(full_path), exist_ok=True) + with open(file=full_path, mode="w", encoding="utf-8") as file: + file.write(file_source) + + return f"File successfully written to `{relative_path}`." + + except Exception as e: + return f"Error: {str(e)}" diff --git a/agents/__init__.py b/agents/__init__.py index dd7d27d..440cfca 100644 --- a/agents/__init__.py +++ b/agents/__init__.py @@ -1,5 +1,6 @@ -from .base_agent import BaseAgent +from .ceo import CEO from .functionner import Functioneer +from .planner import Planner from .product_owner import ProductOwner from .software_engineer import SoftwareEngineer from .user_experience_designer import UserExperienceDesigner diff --git a/agents/base_agent.py b/agents/base_agent.py index 0d60497..029ef70 100644 --- a/agents/base_agent.py +++ b/agents/base_agent.py @@ -1,5 +1,208 @@ +import json +import sys +from typing import List, cast import autogen +from agents.ceo import CEO +from agents.planner import Planner +from constants import COMMON_LLM_CONFIG +from typedefs import BaseMessageDict, MessageDict +import utils + class BaseAgent: as_assistant_agent: autogen.AssistantAgent + ceo: CEO + # functioneer: Functioneer + id: str + messages: List[MessageDict] + name: str + planner: Planner + tasks_file_name: str + + def __init__( + self, + name: str, + system_message: str, + tasks_file_name: str, + ceo: CEO, + # functioneer: Functioneer, + planner: Planner, + ): + self.ceo = ceo + # self.functioneer = functioneer + # TODO Use that later on to load messages from a file or a database. + self.id = utils.normalize_agent_name(name) + self.messages = [] + self.name = name + self.planner = planner + self.tasks_file_name = tasks_file_name + + self.as_assistant_agent = autogen.AssistantAgent( + name=self.id, + llm_config=COMMON_LLM_CONFIG, + system_message=utils.clean_text(system_message), + ) + self.as_assistant_agent.clear_history() + + async def ask(self, message: str): + message_dict: MessageDict = { + "content": message, + "function_call": None, + "name": self.ceo.id, + "role": "user", + } + self.messages.append(message_dict) + + await self.as_assistant_agent.a_receive( + message=message, + sender=self.ceo.as_user_agent, + request_reply=True, + ) + + last_base_message_dict = cast( + BaseMessageDict, self.as_assistant_agent.last_message() + ) + self.messages.append( + cast(MessageDict, last_base_message_dict | {"name": self.id}) + ) + + await self._suggest_planning_update() + + # async def _suggest_function_call( + # self, last_function_call_result: str | None = None + # ): + # self.functioneer.as_assistant_agent.clear_history() + + # if last_function_call_result is None: + # controlled_message = utils.clean_text( + # f""" + # Here are the last messages of your conversations (as a JSON object): + + # ``` + # {json.dumps([self.messages])} + # ``` + + # Here is what I can do for you to help you with your task: + # - Query a search engine to find web pages to give you ranked URL results. + # - Open a web page URL to give you its content. + # - Read or write a file (within the dedicated project directory). + # - Run a command (within the dedicated project directory). + + # Do you need me to run a function to help you with your task? + # """ + # ) + # else: + # controlled_message = utils.clean_text( + # f""" + # Here is the result of the last function call you asked me to run: + + # ``` + # {last_function_call_result} + # ``` + + # Do you need me to run another function to help you with your task? + # """ + # ) + + # await self.as_assistant_agent.a_receive( + # sender=self.functioneer.as_assistant_agent, + # message=controlled_message, + # request_reply=True, + # ) + + # last_base_message_dict = cast( + # BaseMessageDict, self.functioneer.as_assistant_agent.last_message() + # ) + # if last_base_message_dict["content"].startswith("No"): + # return + + # await self.functioneer.as_assistant_agent.a_receive( + # sender=self.as_assistant_agent, + # message=last_base_message_dict["content"], + # request_reply=True, + # ) + + # last_base_message_dict = cast( + # BaseMessageDict, self.functioneer.as_assistant_agent.last_message() + # ) + # if last_base_message_dict["function_call"] is None: + # return + + # (_, result) = self.functioneer.as_assistant_agent.execute_function( + # func_call=last_base_message_dict["function_call"] # type: ignore + # ) + + # await self._suggest_function_call(result["content"]) + + async def _suggest_planning_update(self): + self.planner.as_assistant_agent.clear_history() + + controlled_message = utils.clean_text( + f""" + Here are the last messages of your conversations (as a JSON object): + ``` + {json.dumps([self.messages])} + ``` + + You are the Product Owner in these conversations and I'm here to update your Team Task List file. + + Here is your current Team Task List according to what I read from the file: + {self.planner.get_current_tasks_as_message(self.tasks_file_name)} + + Do you need me to update the file? + """ + ) + + await self.as_assistant_agent.a_receive( + sender=self.planner.as_assistant_agent, + message=controlled_message, + request_reply=True, + ) + + last_base_message_dict = cast( + BaseMessageDict, self.planner.as_assistant_agent.last_message() + ) + if "TERMINATE" in last_base_message_dict["content"] or last_base_message_dict[ + "content" + ].strip().startswith("No"): + return + + await self._understand_planning_update() + + async def _understand_planning_update(self): + print("LAST CALL") + self.planner.as_assistant_agent.generate_function_call_reply( + sender=self.as_assistant_agent + ) + + # test = await self.planner.as_assistant_agent.a_generate_reply( + # sender=self.as_assistant_agent, + # ) + + # print(test) + + sys.exit() + + # last_base_message_dict = cast( + # BaseMessageDict, + # self.planner.as_assistant_agent.last_message(agent=self.as_assistant_agent), + # ) + # print("last_base_message_dict") + # print(last_base_message_dict) + # if "function_call" not in last_base_message_dict: + # await self.as_assistant_agent.a_receive( + # sender=self.planner.as_assistant_agent, + # message=last_base_message_dict["content"], + # request_reply=True, + # ) + + # await self._understand_planning_update() + + # return + + # (_, result) = self.planner.as_assistant_agent.execute_function( + # func_call=last_base_message_dict["function_call"] # type: ignore + # ) + + # await self._suggest_function_call(result["content"]) diff --git a/agents/ceo.py b/agents/ceo.py new file mode 100644 index 0000000..95f2039 --- /dev/null +++ b/agents/ceo.py @@ -0,0 +1,34 @@ +import autogen + +from constants import COMMON_LLM_CONFIG +import utils + + +class CEO: + as_user_agent: autogen.UserProxyAgent + id: str + name: str + + def __init__(self) -> None: + self.id = "CEO" + self.name = "CEO" + + self.as_user_agent = autogen.UserProxyAgent( + name=self.id, + # code_execution_config={"work_dir": PROJECT_DIRECTORY_NAME}, + code_execution_config=False, + human_input_mode="NEVER", + llm_config=COMMON_LLM_CONFIG, + # is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE"), + # max_consecutive_auto_reply=10, + system_message=utils.clean_text( + # """ + # Reply TERMINATE when the task has been solved to full satisfaction. + # Otherwise, reply CONTINUE. + # """ + """ + Never reply anything else than CONTINUE. + """ + ), + ) + self.as_user_agent.clear_history() diff --git a/agents/functionner.py b/agents/functionner.py index 9cf2c8a..31e4d73 100644 --- a/agents/functionner.py +++ b/agents/functionner.py @@ -1,34 +1,40 @@ -import autogen - -import agents -from constants import COMMON_LLM_CONFIG +import actions +from constants import FUNCTIONEER_LLM_CONFIG +from libs.functionary import FunctionaryAssistantAgent from utils import clean_text -class Functioneer(agents.BaseAgent): +class Functioneer: + as_assistant_agent: FunctionaryAssistantAgent + def __init__(self) -> None: - self.as_assistant_agent = autogen.AssistantAgent( + self.as_assistant_agent = FunctionaryAssistantAgent( "Functioneer", - llm_config=COMMON_LLM_CONFIG, + llm_config=FUNCTIONEER_LLM_CONFIG, system_message=clean_text( """ You are the Functioneer. - You are part of a company including a CEO, a Product Owner, a Software Engineer - and a User Experience Designer. - - You role is to assist other agents, by suggesting function calls to the CEO, when they ask you to: - - Compile and run a Rust file. - - Get a web page content by it URL. - - Read a project file. - - Run a bash command in the project directory. - - Search the web. - - Write a project file. + Your role is to assist other agents by calling and using your functions for them. + Only call and use the functions you are provided with. + Do not answer to the agents' questions, only call and use your functions. - Rules: - - Keep it short. Get to the point. Be straightforward. Always specify your recipient's name. - - Only reply to messages prefixed with your name, i.e.: "Functioneer, etc". - - Ask the CEO to run functions when you need to use them. You are not allowed to run them yourself. + You are able to: + - read a file by calling the `read_file` function, + - read a web page by calling the `fetch_web_page` function, + - search the web using a seach engine by callin the `search_web` function, + - write a file by calling the `write_file` function. """ ), ) + self.as_assistant_agent.clear_history() + + self.as_assistant_agent.register_function( + { + "fetch_web_page": actions.fetch_web_page, + "read_file": actions.read_file, + "run_bash_command": actions.run_bash_command, + "search_web": actions.search_web, + "write_file": actions.write_file, + } + ) diff --git a/agents/planner.py b/agents/planner.py new file mode 100644 index 0000000..8d5bbc9 --- /dev/null +++ b/agents/planner.py @@ -0,0 +1,54 @@ +import json +from typing import List +import autogen + +import actions +from constants import COMMON_LLM_CONFIG +import typedefs +from utils import clean_text +import utils + + +class Planner: + as_assistant_agent: autogen.AssistantAgent + + def __init__(self) -> None: + self.as_assistant_agent = autogen.AssistantAgent( + "Planner", + llm_config=COMMON_LLM_CONFIG + | { + "functions": [ + actions.UpdateTasksAction.get_description(), + ] + }, + system_message=clean_text( + """ + You are the Planner. + + Your role is to update the task list using the `update_tasks` function call. + """ + ), + ) + + self.as_assistant_agent.register_function( + { + "update_tasks": actions.UpdateTasksAction.run, + } + ) + + @staticmethod + def get_current_tasks_as_message(file_name: str) -> str: + try: + tasks_as_json = utils.read_file(file_name) + tasks: List[typedefs.TaskDict] = json.loads(tasks_as_json) + tasks_as_message = "\n".join( + [ + f"{task['index']}. [{'x' if task['is_done'] else ' '}] {task['description']}" + for task in tasks + ] + ) + + return tasks_as_message + + except FileNotFoundError: + return "No task yet." diff --git a/agents/product_owner.py b/agents/product_owner.py index 2132c7e..c6154ce 100644 --- a/agents/product_owner.py +++ b/agents/product_owner.py @@ -1,37 +1,82 @@ -import autogen +import json +from typing import cast -import agents -from constants import COMMON_LLM_CONFIG -from utils import clean_text +from agents.base_agent import BaseAgent +from agents.ceo import CEO +from agents.planner import Planner +from typedefs.message import BaseMessageDict, MessageDict +import utils -class ProductOwner(agents.BaseAgent): - def __init__(self) -> None: - self.as_assistant_agent = autogen.AssistantAgent( - "Product_Owner", - llm_config=COMMON_LLM_CONFIG, - system_message=clean_text( +class ProductOwner(BaseAgent): + def __init__(self, ceo: CEO, planner: Planner): + super().__init__( + name="Product Owner", + system_message=utils.clean_text( """ You are the Product Owner. - You manage a team including a Software Engineer and a User Experience Designer. - - You role is to plan, organize and tell your specialized agents what to do - in order to achieve the CEO's goals to the best of your ability. - - Rules: - - Keep it short. Get to the point. Be straightforward. Always specify your recipient's name. - - Ask the Functioneer to run functions when you need to use them. You are not allowed to run them yourself. - - Use a `BOARD.json` file to plan and keep track of ALL the steps you and your team makes. - ALWAYS check for its content when you start. - - Your team should always start with the UX and UI parts. - - In order to help with your tasks, you can ask the Functioneer to do the following for you: - - Get a web page content by it URL. - - Read a project file. - - Run a bash command in the project directory. - - Search the web. - - Write a project file. - """ + You work for the CEO and your role is to manage your team in order to create the desired program. + + You have multiple one-on-one conversations with each member of your team to: + - discuss the project regarding their expertise, + - assign them tasks, + - check their progress. + + The core team you manage is composed of: + - a User Experience Designer who will design the program, + - a Software Engineer who will code, test and run the program. + + You're also personally assisted by: + - a Planner who will help you update and keep track of your task list, storing it in project file. + + The other members of your team will regularly ask you what they should do next. + They are neither aware of your dicussions with the rest of the team nor able to talk with them. + Each member is only be able to talk with you. Don't ask them to talk with each other. + They will regularly precede their message with the recent history of your conversations, + including those with other agents. + + Here arerules you must follow: + - Always organize and keep track of your team members tasks and progress, including yours. + - Always use the Planner to keep track and update these tasks. He will save them in a project file. + """ ), + tasks_file_name="KANBAN.json", + ceo=ceo, + planner=planner, + ) + + async def ask_for_next_task(self, sender: BaseAgent): + message = "What is my next task regarding the project?" + qualified_message = utils.clean_text( + f""" + Here are the last messages of your conversations (as a JSON object): + ``` + {json.dumps([self.messages])} + ``` + + You are the Product Owner in these conversations and I work for you. + + {message} + """ + ) + message_dict: MessageDict = { + "content": message, + "function_call": None, + "name": sender.id, + "role": "assistant", + } + self.messages.append(message_dict) + + await self.as_assistant_agent.a_receive( + message=qualified_message, + sender=sender.as_assistant_agent, + request_reply=True, + ) + + last_base_message_dict = cast( + BaseMessageDict, self.as_assistant_agent.last_message() + ) + self.messages.append( + cast(MessageDict, last_base_message_dict | {"name": self.id}) ) diff --git a/agents/software_engineer.py b/agents/software_engineer.py index c9b2214..e1dcfa7 100644 --- a/agents/software_engineer.py +++ b/agents/software_engineer.py @@ -1,41 +1,33 @@ -import autogen +from agents.base_agent import BaseAgent +from agents.ceo import CEO +from agents.planner import Planner +import utils -import agents -from constants import COMMON_LLM_CONFIG -from utils import clean_text - -class SoftwareEngineer(agents.BaseAgent): - def __init__(self) -> None: - self.as_assistant_agent = autogen.AssistantAgent( - "Sofware_Engineer", - llm_config=COMMON_LLM_CONFIG, - system_message=clean_text( +class SoftwareEngineer(BaseAgent): + def __init__(self, ceo: CEO, planner: Planner): + super().__init__( + name="Software Engineer", + system_message=utils.clean_text( """ - Your are the Sofware Engineer. - - You are part of a team inluding a Product Owner and a User Experience Designer. + You are the Software Engineer. - Your role is to write the expected program source code. + You work for the Product Owner to create a program. - The Product Owner is your team manager. - The Product Owner will tell you what to do, don't answer to the CEO yourself. + You are part of a core team which is managed by the Product Owner and is composed of: + - A Software Engineer (yourself) who will code, test and run the program. + - A User Experience Designer who will design the program. - Rules: - - Keep it short. Get to the point. Be straightforward. Always specify your recipient's name. - - Only reply to messages prefixed with your name, i.e.: "Software Engineer, etc". - - Only communicate with the Product Owner, and nobody else. - - ALWAYS write unit/e2e tests to check that your code works. - - NEVER run the program directly, run it via e2e tests. - - Use a `TECH.json` file to keep track of your work. ALWAYS check for its content when you start. + You're also personally assisted by: + - A Functioneer who will run commands and write the code for you in the decicated project directory. + This is the only way you can interact with the real world. - In order to help with your tasks, you can ask the Functioneer to do the following for you: - - Compile and run a Rust file. - - Get a web page content by it URL. - - Read a project file. - - Run a bash command in the project directory. - - Search the web. - - Write a project file. + A few rules you must follow: + - Never ask the Functioneer to run commands that will hang forever (serve, watch, etc). + - Always test that your program is working by writting E2E tests. """ ), + tasks_file_name="KANBAN.json", + ceo=ceo, + planner=planner, ) diff --git a/agents/user_experience_designer.py b/agents/user_experience_designer.py index abb1600..fc0a06a 100644 --- a/agents/user_experience_designer.py +++ b/agents/user_experience_designer.py @@ -1,38 +1,29 @@ -import autogen +from agents.base_agent import BaseAgent +from agents.ceo import CEO +from agents.planner import Planner +import utils -import agents -from constants import COMMON_LLM_CONFIG -from utils import clean_text - -class UserExperienceDesigner(agents.BaseAgent): - def __init__(self) -> None: - self.as_assistant_agent = autogen.AssistantAgent( - "User_Experience_Designer", - llm_config=COMMON_LLM_CONFIG, - system_message=clean_text( +class UserExperienceDesigner(BaseAgent): + def __init__(self, ceo: CEO, planner: Planner): + super().__init__( + name="User Experience Designer", + system_message=utils.clean_text( """ You are the User Experience Designer. - You are part of a team including a Product Owner and a Software Engineer. - - Your role is to design the program UI and UX. - - The Product Owner is your team manager. - The Product Owner will tell you what to do, don't answer to the CEO yourself. + You work for the Product Owner to create a program. - Rules: - - Keep it short. Get to the point. Be straightforward. Always specify your recipient's name. - - Only reply to messages prefixed with your name, i.e.: "User Experience Designer, etc". - - Only communicate with the Product Owner, and nobody else. - - Keep it short. Get to the point. Be straightforward. Specify your recipient's name. - - Use a `DESIGN.md` file to keep a memo of your analyses. ALWAYS check for its content when you start. + You are part of a core team which is managed by the Product Owner and is composed of: + - A Software Engineer who will code, test and run the program. + - A User Experience Designer (yourself) who will design the program. - In order to help with your tasks, you can ask the Functioneer to do the following for you: - - Get a web page content by it URL. - - Read a project file. - - Search the web. - - Write a project file. + You're also personally assisted by: + - A Functioneer who will run commands and write documentation for you within the decicated project directory. + This is the only way you can interact with the real world. """ ), + tasks_file_name="KANBAN.json", + ceo=ceo, + planner=planner, ) diff --git a/constants.py b/constants.py index b2fd930..c5a6f48 100644 --- a/constants.py +++ b/constants.py @@ -1,113 +1,127 @@ -from autogen import config_list_from_json +import os +import time +from typing import Dict +from dataclasses import asdict from dacite import from_dict from jsonc_parser.parser import JsoncParser -import os -from typedefs import ProjectConfig - -import utils +from typedefs import ProjectConfig +from utils.get_model_config_as_dict import get_model_config_as_dict PROJECT_CONFIG_AS_DICT = JsoncParser.parse_file("./env.jsonc") PROJECT_CONFIG: ProjectConfig = from_dict( data_class=ProjectConfig, data=PROJECT_CONFIG_AS_DICT ) +DEFAULT_REQUEST_TIMEOUT = 60 +IS_OPEN_SOURCE_LLM = PROJECT_CONFIG.functionary_model is None +PROJECT_DIRECTORY_NAME = "project" +PROJECT_DIRECTORY_PATH = os.path.join(os.getcwd(), PROJECT_DIRECTORY_NAME) + COMMON_LLM_CONFIG = { # https://microsoft.github.io/autogen/docs/FAQ#set-your-api-endpoints - "config_list": [utils.get_model_config_as_dict(PROJECT_CONFIG)], - "functions": [ - { - "name": "fetch_web_page", - "description": "Fetch a web page and return its content as text and Markdown links.", - "parameters": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "Url to fetch from.", - }, + "config_list": [get_model_config_as_dict(PROJECT_CONFIG)], + "request_timeout": 600, + "seed": int(time.time()), +} + +FUNCTIONS = [ + { + "name": "fetch_web_page", + "description": "Fetch a web page and return its content as text and Markdown links.", + "parameters": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "Url to fetch from.", }, - "required": ["url"], }, + "required": ["url"], }, - { - "name": "read_file", - "description": "Read and return a file content.", - "parameters": { - "type": "object", - "properties": { - "relative_path": { - "type": "string", - "description": "Relative path of the file.", - }, + }, + { + "name": "read_file", + "description": "Read and return a file content.", + "parameters": { + "type": "object", + "properties": { + "relative_path": { + "type": "string", + "description": "Relative path of the file.", }, - "required": ["relative_path"], }, + "required": ["relative_path"], }, - { - "name": "run_bash_command", - "description": "Run a bash command and return the output.", - "parameters": { - "type": "object", - "properties": { - "command": { - "type": "string", - "description": "Bash command.", - }, + }, + { + "name": "run_bash_command", + "description": "Run a bash command and return the output.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "Bash command.", }, - "required": ["command"], }, + "required": ["command"], }, - { - "name": "run_rust_file", - "description": "Compile a rust file into `./temp_executable` and execute it.", - "parameters": { - "type": "object", - "properties": { - "rust_file_path": { - "type": "string", - "description": "Rust file path.", - }, + }, + { + "name": "run_rust_file", + "description": "Compile a rust file into `./temp_executable` and execute it.", + "parameters": { + "type": "object", + "properties": { + "rust_file_path": { + "type": "string", + "description": "Rust file path.", }, - "required": ["rust_file_path"], }, + "required": ["rust_file_path"], }, - { - "name": "search_web", - "description": "Search for a text query using Brave search engine and return results as JSON.", - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "Query to search.", - }, + }, + { + "name": "search_web", + "description": "Search for a text query using Brave search engine and return results as JSON.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Query to search.", }, - "required": ["query"], }, + "required": ["query"], }, - { - "name": "write_file", - "description": "Write content to a file, creating it if necessary.", - "parameters": { - "type": "object", - "properties": { - "relative_path": { - "type": "string", - "description": "Relative path of the file.", - }, - "file_source": { - "type": "string", - "description": """Content to write.""", - }, + }, + { + "name": "write_file", + "description": "Write content to a file, creating it if necessary.", + "parameters": { + "type": "object", + "properties": { + "relative_path": { + "type": "string", + "description": "Relative path of the file.", + }, + "file_source": { + "type": "string", + "description": """Content to write.""", }, - "required": ["relative_path", "file_source"], }, + "required": ["relative_path", "file_source"], }, + }, +] + + +FUNCTIONEER_LLM_CONFIG: Dict = COMMON_LLM_CONFIG | { + "config_list": [ + COMMON_LLM_CONFIG + if PROJECT_CONFIG.functionary_model is None + else asdict(PROJECT_CONFIG.functionary_model) ], - "request_timeout": 600, - "seed": 42, + "functions": FUNCTIONS, } - -PROJECT_DIRECTORY_NAME = "project" -PROJECT_DIRECTORY_PATH = os.path.join(os.getcwd(), PROJECT_DIRECTORY_NAME) diff --git a/core.py b/core.py new file mode 100644 index 0000000..8aa6871 --- /dev/null +++ b/core.py @@ -0,0 +1,43 @@ +from typing import List + +import agents +from agents.base_agent import BaseAgent +import utils + + +class Core: + current_team_agent_index: int = 0 + product_owner: agents.ProductOwner + team_agents: List[BaseAgent] + """Ordered list of agents. They will call the Product Owner in order to ask him what to do next.""" + + def __init__( + self, + product_owner: agents.ProductOwner, + team_agents: List[BaseAgent], + ): + self.product_owner = product_owner + self.team_agents = team_agents + + async def start(self, initial_message: str): + self.product_owner.as_assistant_agent.clear_history() + for agent in self.team_agents: + agent.as_assistant_agent.clear_history() + + await self.product_owner.ask(initial_message) + await self.next() + + async def next(self): + print(self.team_agents) + print(self.current_team_agent_index) + current_team_agent = self.team_agents[self.current_team_agent_index] + + await self.product_owner.ask_for_next_task( + sender=current_team_agent, + ) + + self.current_team_agent_index = (self.current_team_agent_index + 1) % len( + self.team_agents + ) + + await self.next() diff --git a/env.sample.jsonc b/env.openai-sample.jsonc similarity index 83% rename from env.sample.jsonc rename to env.openai-sample.jsonc index f236d6b..71078b1 100644 --- a/env.sample.jsonc +++ b/env.openai-sample.jsonc @@ -30,8 +30,8 @@ // Any model using Azure OpenAI API { "model": "[AZURE_OPENAI_STUDIO_DEPLOYMENT_NAME]", - "api_key": "[AZURE_OPENAI_API_KEY]", "api_base": "https://[AZURE_OPENAI_RESOURCE_NAME].openai.azure.com", + "api_key": "[AZURE_OPENAI_API_KEY]", "api_type": "azure", // https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions "api_version": "2023-08-01-preview" @@ -71,17 +71,4 @@ "api_type": "open_ai" } ] - - // ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― - // Funtionary LLM API Endpoint Configuration - - // This must be a secondary deployment. Don't use this endpoint in your models. - // You can deploy it in one click using this Github repository: - // https://github.com/ivangabriele/docker-functionary - // "functionary_model": { - // "model": "musabgultekin/functionary-7b-v1", - // "api_base": "https://eoefr8r4dxwu0n-8000.proxy.runpod.net//v1", - // "api_key": "functionary", - // "api_type": "open_ai" - // } } diff --git a/env.opensource-sample.jsonc b/env.opensource-sample.jsonc new file mode 100644 index 0000000..785d413 --- /dev/null +++ b/env.opensource-sample.jsonc @@ -0,0 +1,56 @@ +{ + // ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― + // Brave Search API Key + + // Used by agent function `search_web()` which agents can use to help with their task + // https://brave.com/search/api/ (there is a free plan) + // "Data for AI" plan is prefered over "Data for Search" since it includes some useful additional props + "brave_search_api_key": "[BRAVE_SEARCH_API_KEY]", + + // ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― + // Current Model + + // Selected `model` that agents must use in the list of OpenAI API endpoints + "current_model": "Open-Orca/Mistral-7B-OpenOrca", + + // ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― + // Project Description + + // Description of the program you want the agents to develop + // You can also set it to `null` if you want the Product Owner agent + // to prompt you for your project desciption each time you run OADS. + "initial_project_description": "Create a \"guess the number\" CLI game in Python.", + + // ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― + // List of OpenAI-Compatible API endpoints + + // The `model` key must be unique. + // https://microsoft.github.io/FLAML/docs/reference/autogen/oai/completion/#create + "models": [ + // You can deploy it in one click using this Github repository: https://github.com/ivangabriele/docker-llm. + // FUNCTIONS ARE NOT SUPPORTED BY MOST OPEN SOURCE LLMS (YET?): + // This is why we use a second endpoint — a Functionary LLM behind a vLLM OpenAI-Compatible API — + // in order to give Function Calling abilities to almost any smart-enough open-source LLM. + + // This can be also be any inference endpoint compatible following OpenAI API specs, + // regardless of the model you use behind it. + { + "model": "Open-Orca/Mistral-7B-OpenOrca", + "api_base": "https://[YOUR_CONTAINER_ID]-8000.proxy.runpod.net", // or your public endpoint + "api_key": "None", // Dummy API Key since it can neither be `null` nor empty + "api_type": "open_ai" + } + ], + + // ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― + // Funtionary LLM API Endpoint Configuration + + // This must be a secondary deployment. Don't use this endpoint in `models`. + // You can deploy it in one click using this Github repository: https://github.com/ivangabriele/docker-functionary. + "functionary_model": { + "model": "musabgultekin/functionary-7b-v1", + "api_base": "https://[YOUR_CONTAINER_ID]-8000.proxy.runpod.net/v1", + "api_key": "None", // Dummy API Key since it can neither be `null` nor empty + "api_type": "open_ai" + } +} diff --git a/libs/functionary.py b/libs/functionary.py index 23b136f..b9df72a 100644 --- a/libs/functionary.py +++ b/libs/functionary.py @@ -34,8 +34,6 @@ def _process_received_message( def normalize_message(message_as_str: str | Union[Dict, str]) -> Union[Dict, str]: """Clean Functionary API message to fit Autogen expectations.""" - print(message_as_str) - # pylint: disable=protected-access message = autogen.ConversableAgent._message_to_dict(message_as_str) @@ -76,6 +74,4 @@ def normalize_message(message_as_str: str | Union[Dict, str]) -> Union[Dict, str else: transformed_message = {"content": content} - print(transformed_message) - return transformed_message diff --git a/main.py b/main.py index 9dafa42..f879134 100644 --- a/main.py +++ b/main.py @@ -2,116 +2,42 @@ Module Name: main.py Short Description: -This program intends to leverage Microsoft Autogen to automate software development via AI agents. +OADS intends to leverage Microsoft Autogen to automate software development via AI agents. Detailed Description: -Microsoft Autogen is a framework that enables the development of LLM (Lifelong Learning Machines) applications -using multiple agents capable of conversing with each other to solve tasks. -Autogen agents are customizable, conversable, and can seamlessly incorporate human participation. -These agents can operate in various modes, utilizing combinations of LLMs, human input, and other tools. +TODO Write a detailed description of the program. """ -import autogen - -import actions +import asyncio import agents -from constants import COMMON_LLM_CONFIG, PROJECT_CONFIG, PROJECT_DIRECTORY_NAME -import utils - - -# CEO Human Proxy Agent -# Uses shell with human-in-the-loop, meaning the human user can either give their input when asked, -# or ignore step, to let the agent interactions continue from there. -ceo_user_proxy_agent = autogen.UserProxyAgent( - "CEO", - code_execution_config={"work_dir": PROJECT_DIRECTORY_NAME}, - human_input_mode="TERMINATE", - llm_config=COMMON_LLM_CONFIG, - # is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE"), - # max_consecutive_auto_reply=10, - system_message=utils.clean_text( - """ - You are the CEO. - - You are assisted by a Product Owner who will try his best to achieve your goals with the team under his orders. - Your are the only agent allowed to run functions. - - Rules: - - Keep it short. Get to the point. Be straightforward. Always specify your recipient's name. - - Only reply to messages prefixed with your name, i.e.: "CEO, etc". - - Only communicate with the Product Owner, and nobody else. - - Reply TERMINATE when the task has been solved to full satisfaction. - Otherwise, reply CONTINUE. - """ - ), -) - - -COMMON_FUNCTION_MAP = { - "fetch_web_page": actions.fetch_web_page, - "read_file": actions.read_file, - "run_bash_command": actions.run_bash_command, - "run_rust_file": actions.run_rust_file, - "search_web": actions.search_web, - "write_file": actions.write_file, -} - -ceo_user_proxy_agent.register_function( - function_map=COMMON_FUNCTION_MAP, +from constants import ( + PROJECT_CONFIG, ) +from core import Core +import utils -functioneer = agents.Functioneer() -# functioneer.as_assistant_agent.register_function( -# function_map=COMMON_FUNCTION_MAP, -# ) - -product_owner = agents.ProductOwner() -# product_owner.as_assistant_agent.register_function( -# function_map=COMMON_FUNCTION_MAP, -# ) +ceo = agents.CEO() +planner = agents.Planner() -software_engineer = agents.SoftwareEngineer() -# software_engineer.as_assistant_agent.register_function( -# function_map=COMMON_FUNCTION_MAP, -# ) +product_owner = agents.ProductOwner(ceo=ceo, planner=planner) +software_engineer = agents.SoftwareEngineer(ceo=ceo, planner=planner) +user_experience_designer = agents.UserExperienceDesigner(ceo=ceo, planner=planner) -user_experience_designer = agents.UserExperienceDesigner() -# user_experience_designer.as_assistant_agent.register_function( -# function_map=COMMON_FUNCTION_MAP, -# ) +utils.print_project_config(PROJECT_CONFIG) -group_chat = autogen.GroupChat( - admin_name="Administrator", - agents=[ - ceo_user_proxy_agent, - functioneer.as_assistant_agent, - product_owner.as_assistant_agent, - software_engineer.as_assistant_agent, - user_experience_designer.as_assistant_agent, +core = Core( + product_owner=product_owner, + team_agents=[ + user_experience_designer, + software_engineer, ], - messages=[], - max_round=100, -) - -group_chat_manager = autogen.GroupChatManager( - groupchat=group_chat, llm_config=COMMON_LLM_CONFIG ) -utils.print_project_config(PROJECT_CONFIG) - -if PROJECT_CONFIG.initial_project_description is None: - initial_project_description = ceo_user_proxy_agent.get_human_input( - "You didn't specify a project in `env.jsonc`. What do you want us to develop?\nRequest: " +if __name__ == "__main__": + asyncio.run( + core.start( + initial_message="Develop the API of a multipayer variation of the SimCity 2000 game. In Node.js, using Typescript.", + ) ) -else: - initial_project_description = PROJECT_CONFIG.initial_project_description - -ceo_user_proxy_agent.initiate_chat( - recipient=group_chat_manager, - message=utils.clean_text( - f"Product Owner, I want your team to achieve these goals:\n- {initial_project_description}" - ), -) diff --git a/niam.py b/niam.py new file mode 100644 index 0000000..70a1034 --- /dev/null +++ b/niam.py @@ -0,0 +1,59 @@ +import autogen + +from constants import COMMON_LLM_CONFIG +import utils + +bobby = autogen.AssistantAgent( + name="Bobby", + system_message=utils.clean_text( + """ + We're all improvisers. We are here to play a long improvisation together. + We must be creative, avoid story loops and keep the story going. + We must also add unexpected turns and new scenes regularly. + We love short dialogs, and even shorter context, to keep the story spirited. + + Your play Bobby, a crazy man, completely lost in life. + Bobby always say crazy, funny and senseless things. + """ + ), + llm_config=COMMON_LLM_CONFIG, +) +john = autogen.AssistantAgent( + name="John", + system_message=utils.clean_text( + """ + We're all improvisers. We are here to play a long improvisation together. + We must be creative, avoid story loops and keep the story going. + We must also add unexpected turns and new scenes regularly. + We love short dialogs, and even shorter context, to keep the story spirited. + + Your play John, a mean, annoying and persistent detractor. + John contradicts anything others say. + """ + ), + llm_config=COMMON_LLM_CONFIG, +) +thomas = autogen.AssistantAgent( + name="Thomas", + system_message=utils.clean_text( + """ + We're all improvisers. We are here to play a long improvisation together. + We must be creative, avoid story loops and keep the story going. + We must also add unexpected turns and new scenes regularly. + We love short dialogs, and even shorter context, to keep the story spirited. + + You play Thomas, a kind and naive man. + """ + ), + llm_config=COMMON_LLM_CONFIG, +) + +groupchat = autogen.GroupChat(agents=[bobby, john, thomas], messages=[], max_round=100) +manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=COMMON_LLM_CONFIG) +thomas.initiate_chat( + manager, + message=""" + Don't prepare or talk about the story. Just start playing after me! + I start: \"Damn! I've lost track of Miss Laroche! I would have sworn she was in this street five seconds ago. Have you seen her?!\" + """, +) diff --git a/poetry.lock b/poetry.lock index f813e43..4212ef2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -235,101 +235,101 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.0" +version = "3.3.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, - {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, + {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, + {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, ] [[package]] @@ -743,6 +743,63 @@ files = [ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] +[[package]] +name = "mypy" +version = "1.6.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c"}, + {file = "mypy-1.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb"}, + {file = "mypy-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e"}, + {file = "mypy-1.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f"}, + {file = "mypy-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c"}, + {file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"}, + {file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"}, + {file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"}, + {file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"}, + {file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"}, + {file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"}, + {file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"}, + {file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"}, + {file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"}, + {file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"}, + {file = "mypy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169"}, + {file = "mypy-1.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143"}, + {file = "mypy-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46"}, + {file = "mypy-1.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85"}, + {file = "mypy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45"}, + {file = "mypy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208"}, + {file = "mypy-1.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd"}, + {file = "mypy-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332"}, + {file = "mypy-1.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f"}, + {file = "mypy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30"}, + {file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"}, + {file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "numpy" version = "1.25.2" @@ -799,6 +856,17 @@ dev = ["black (>=21.6b0,<22.0)", "pytest (==6.*)", "pytest-asyncio", "pytest-moc embeddings = ["matplotlib", "numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "plotly", "scikit-learn (>=1.0.2)", "scipy", "tenacity (>=8.0.1)"] wandb = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "wandb"] +[[package]] +name = "overrides" +version = "7.4.0" +description = "A decorator to automatically detect mismatch when overriding a method." +optional = false +python-versions = ">=3.6" +files = [ + {file = "overrides-7.4.0-py3-none-any.whl", hash = "sha256:3ad24583f86d6d7a49049695efe9933e67ba62f0c7625d53c59fa832ce4b8b7d"}, + {file = "overrides-7.4.0.tar.gz", hash = "sha256:9502a3cca51f4fac40b5feca985b6703a5c1f6ad815588a7ca9e285b9dca6757"}, +] + [[package]] name = "packaging" version = "23.2" @@ -921,7 +989,7 @@ tests = ["pytest"] [[package]] name = "pyautogen" -version = "0.1.11" +version = "0.1.13" description = "Enabling Next-Gen LLM Applications via Multi-Agent Conversation Framework" optional = false python-versions = ">=3.8" @@ -931,7 +999,7 @@ develop = false [package.dependencies] diskcache = "*" flaml = "*" -openai = "*" +openai = "<1" python-dotenv = "*" termcolor = "*" @@ -939,13 +1007,14 @@ termcolor = "*" blendsearch = ["flaml[blendsearch]"] mathchat = ["pydantic (==1.10.9)", "sympy", "wolframalpha"] retrievechat = ["chromadb", "pypdf", "sentence_transformers", "tiktoken"] +teachable = ["chromadb"] test = ["chromadb", "coverage (>=5.3)", "datasets", "ipykernel", "lancedb", "nbconvert", "nbformat", "pre-commit", "pydantic (==1.10.9)", "pytest (>=6.1.1)", "pytest-asyncio", "sympy", "tiktoken", "wolframalpha"] [package.source] type = "git" url = "https://github.com/microsoft/autogen" reference = "HEAD" -resolved_reference = "a8da3854c00cf8a4517d2572668a8b45077c63bc" +resolved_reference = "6438625063b36b1940312e865775edb0890a2604" [[package]] name = "pygments" @@ -963,17 +1032,17 @@ plugins = ["importlib-metadata"] [[package]] name = "pylint" -version = "3.0.1" +version = "3.0.2" description = "python code static checker" optional = false python-versions = ">=3.8.0" files = [ - {file = "pylint-3.0.1-py3-none-any.whl", hash = "sha256:9c90b89e2af7809a1697f6f5f93f1d0e518ac566e2ac4d2af881a69c13ad01ea"}, - {file = "pylint-3.0.1.tar.gz", hash = "sha256:81c6125637be216b4652ae50cc42b9f8208dfb725cdc7e04c48f6902f4dbdf40"}, + {file = "pylint-3.0.2-py3-none-any.whl", hash = "sha256:60ed5f3a9ff8b61839ff0348b3624ceeb9e6c2a92c514d81c9cc273da3b6bcda"}, + {file = "pylint-3.0.2.tar.gz", hash = "sha256:0d4c286ef6d2f66c8bfb527a7f8a629009e42c99707dec821a03e1b51a4c1496"}, ] [package.dependencies] -astroid = ">=3.0.0,<=3.1.0-dev0" +astroid = ">=3.0.1,<=3.1.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, @@ -1344,4 +1413,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "e3f2e0bc03e9c0c0019fad8d1e5bd5ec20780d03f1a7d2be9285b2cca577160e" +content-hash = "b8c985729ceaf893b3ba6a03e762b26bdafe6390b904017c78663ec49ea8be88" diff --git a/pyproject.toml b/pyproject.toml index 96d6f3b..2feda48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ pyautogen = { git = "https://github.com/microsoft/autogen" } python = "^3.10" requests = "^2.31.0" termcolor = "^2.3.0" +overrides = "^7.4.0" [tool.poetry.group.dev.dependencies] pylint = "^3.0.1" @@ -22,6 +23,7 @@ pytest = "^7.4.2" pytest-mock = "^3.11.1" types-beautifulsoup4 = "^4.12.0.6" types-requests = "^2.31.0.7" +mypy = "^1.6.1" [build-system] build-backend = "poetry.core.masonry.api" diff --git a/tests/actions/test_run_bash_command.py b/tests/actions/test_run_bash_command.py index 49744e4..5d4db73 100644 --- a/tests/actions/test_run_bash_command.py +++ b/tests/actions/test_run_bash_command.py @@ -1,4 +1,3 @@ -import pytest from actions import run_bash_command diff --git a/tests/utils/test_clean_agent_name.py b/tests/utils/test_clean_agent_name.py new file mode 100644 index 0000000..a9cca37 --- /dev/null +++ b/tests/utils/test_clean_agent_name.py @@ -0,0 +1,11 @@ +import utils + + +def test_clean_agent_name(): + assert utils.clean_agent_name("Hello_World") == "Hello World" + assert ( + utils.clean_agent_name("OpenAI_Autogen_Dev_Studio") + == "OpenAI Autogen Dev Studio" + ) + assert utils.clean_agent_name("") == "" + assert utils.clean_agent_name("No_Underscores_Here") == "No Underscores Here" diff --git a/tests/utils/test_clean_text.py b/tests/utils/test_clean_text.py index f7c895f..24e4005 100644 --- a/tests/utils/test_clean_text.py +++ b/tests/utils/test_clean_text.py @@ -12,11 +12,7 @@ def test_clean_text(): ) == "A todo list:\n- Do this.\n- And do that." ) - assert utils.clean_text(" Hello World ") == "Hello World" - assert utils.clean_text("Hello") == "Hello" - assert utils.clean_text("") == "" - assert utils.clean_text(" ") == "" diff --git a/tests/utils/test_mask_secret.py b/tests/utils/test_mask_secret.py new file mode 100644 index 0000000..15111a3 --- /dev/null +++ b/tests/utils/test_mask_secret.py @@ -0,0 +1,8 @@ +import utils + + +def test_mask_secret(): + assert utils.mask_secret("abcdef") == "******" + assert utils.mask_secret("1234567890") == "123****890" + assert utils.mask_secret(None) is None + assert utils.mask_secret("a") == "*" diff --git a/tests/utils/test_normalize_agent_name.py b/tests/utils/test_normalize_agent_name.py new file mode 100644 index 0000000..84436e9 --- /dev/null +++ b/tests/utils/test_normalize_agent_name.py @@ -0,0 +1,11 @@ +import utils + + +def test_normalize_agent_name(): + assert utils.normalize_agent_name("Hello World") == "Hello_World" + assert ( + utils.normalize_agent_name("OpenAI Autogen Dev Studio") + == "OpenAI_Autogen_Dev_Studio" + ) + assert utils.normalize_agent_name("") == "" + assert utils.normalize_agent_name("No Spaces Here") == "No_Spaces_Here" diff --git a/typedefs/__init__.py b/typedefs/__init__.py new file mode 100644 index 0000000..659a86f --- /dev/null +++ b/typedefs/__init__.py @@ -0,0 +1,3 @@ +from .config import ModelConfig, ProjectConfig +from .message import BaseMessageDict, MessageDict +from .task import TaskDict diff --git a/typedefs.py b/typedefs/config.py similarity index 89% rename from typedefs.py rename to typedefs/config.py index baf3b73..18bdd50 100644 --- a/typedefs.py +++ b/typedefs/config.py @@ -4,16 +4,17 @@ @dataclass class ModelConfig: - model: str - api_key: str api_base: Optional[str] + api_key: str api_type: Optional[str] api_version: Optional[str] + model: str @dataclass class ProjectConfig: brave_search_api_key: str current_model: str + functionary_model: Optional[ModelConfig] initial_project_description: Optional[str] models: List[ModelConfig] diff --git a/typedefs/message.py b/typedefs/message.py new file mode 100644 index 0000000..0243272 --- /dev/null +++ b/typedefs/message.py @@ -0,0 +1,11 @@ +from typing import Any, Optional, TypedDict + + +class BaseMessageDict(TypedDict): + content: str + function_call: Optional[Any] + role: str + + +class MessageDict(BaseMessageDict): + name: str diff --git a/typedefs/task.py b/typedefs/task.py new file mode 100644 index 0000000..badc4d7 --- /dev/null +++ b/typedefs/task.py @@ -0,0 +1,7 @@ +from typing import TypedDict + + +class TaskDict(TypedDict): + index: int + description: str + is_done: bool diff --git a/utils/__init__.py b/utils/__init__.py index c0eb9a7..ab58e3c 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,5 +1,9 @@ +from .clean_agent_name import clean_agent_name from .clean_text import clean_text from .debug import debug +from .ensure_str import ensure_str from .get_model_config_as_dict import get_model_config_as_dict from .mask_secret import mask_secret +from .normalize_agent_name import normalize_agent_name from .print_project_config import print_project_config +from .read_file import read_file diff --git a/utils/clean_agent_name.py b/utils/clean_agent_name.py new file mode 100644 index 0000000..0d4301e --- /dev/null +++ b/utils/clean_agent_name.py @@ -0,0 +1,2 @@ +def clean_agent_name(input_string: str) -> str: + return input_string.replace("_", " ") diff --git a/utils/ensure_str.py b/utils/ensure_str.py new file mode 100644 index 0000000..53ca82f --- /dev/null +++ b/utils/ensure_str.py @@ -0,0 +1,14 @@ +from utils.debug import debug + + +def ensure_str(value) -> str: + if not isinstance(value, str): + error_message = ( + f"Expected value to be of type `str`, got `{type(value).__name__}`." + ) + + debug("Error", error_message) + + raise TypeError(error_message) + + return value diff --git a/utils/get_model_config_as_dict.py b/utils/get_model_config_as_dict.py index d12cadd..1c6d69f 100644 --- a/utils/get_model_config_as_dict.py +++ b/utils/get_model_config_as_dict.py @@ -4,14 +4,18 @@ from typedefs import ProjectConfig -def get_model_config_as_dict(project_config: ProjectConfig) -> Optional[Dict[str, Any]]: +def get_model_config_as_dict( + project_config: ProjectConfig, custom_current_model: Optional[str] = None +) -> Optional[Dict[str, Any]]: """Return the dictionary representation of the selected model configuration.""" + current_model = custom_current_model or project_config.current_model + model_config = next( ( config_dict for config_dict in project_config.models - if config_dict.model == project_config.current_model + if config_dict.model == current_model ), None, ) diff --git a/utils/mask_secret.py b/utils/mask_secret.py index 3e156d6..5355d71 100644 --- a/utils/mask_secret.py +++ b/utils/mask_secret.py @@ -1,5 +1,9 @@ -def mask_secret(value: str) -> str: +def mask_secret(value: str | None) -> str | None: """Mask a secret value, revealing only the first 3 and last 3 characters.""" + if value is None: + return None + if len(value) <= 6: return "*" * len(value) + return value[:3] + "*" * (len(value) - 6) + value[-3:] diff --git a/utils/normalize_agent_name.py b/utils/normalize_agent_name.py new file mode 100644 index 0000000..849cb63 --- /dev/null +++ b/utils/normalize_agent_name.py @@ -0,0 +1,2 @@ +def normalize_agent_name(input_string: str) -> str: + return input_string.replace(" ", "_") diff --git a/utils/print_project_config.py b/utils/print_project_config.py index 31b4cc9..3389449 100644 --- a/utils/print_project_config.py +++ b/utils/print_project_config.py @@ -2,7 +2,7 @@ from typing import SupportsIndex from termcolor import colored from typedefs import ProjectConfig -import utils +from utils.mask_secret import mask_secret def print_project_config(project_config: ProjectConfig): @@ -38,7 +38,9 @@ def _print_model_config(model_config: dict, is_selected: bool): for model_key, model_value in model_config.items(): formatted_value = ( - utils.mask_secret(model_value) if model_key in ["api_key"] else model_value + mask_secret(model_value) + if model_key in ["api_base", "api_key"] + else model_value ) print( colored( @@ -51,7 +53,7 @@ def _print_model_config(model_config: dict, is_selected: bool): def _print_key_value(key: str, value: str): if key == "brave_search_api_key": - print(f"{_pad_key(key, 32)} {utils.mask_secret(value)}") + print(f"{_pad_key(key, 32)} {mask_secret(value)}") else: print(f"{_pad_key(key, 32)} {value}") diff --git a/utils/read_file.py b/utils/read_file.py new file mode 100644 index 0000000..2cdfa30 --- /dev/null +++ b/utils/read_file.py @@ -0,0 +1,16 @@ +import os + + +PROJECT_DIRECTORY_NAME = "project" +PROJECT_DIRECTORY_PATH = os.path.join(os.getcwd(), PROJECT_DIRECTORY_NAME) + + +def read_file(relative_path: str) -> str: + """ + Read content from a file and return it. + """ + + full_path = os.path.join(PROJECT_DIRECTORY_PATH, relative_path) + + with open(file=full_path, mode="r", encoding="utf-8") as file: + return file.read()