From c5e16330a21231abbf2f326889e941ce3a890995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sat, 2 Sep 2023 18:51:46 +0800 Subject: [PATCH 01/10] feat: +path --- metagpt/utils/s3.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/metagpt/utils/s3.py b/metagpt/utils/s3.py index 85837fedb..d13030292 100644 --- a/metagpt/utils/s3.py +++ b/metagpt/utils/s3.py @@ -1,4 +1,5 @@ import base64 +import os.path import traceback import uuid from typing import Optional @@ -138,8 +139,11 @@ class S3: await file.write(data) bucket = CONFIG.S3.get("bucket") - await self.upload_file(bucket=bucket, local_path=pathname, object_name=object_name) - return await self.get_object_url(bucket=bucket, object_name=object_name) + object_pathname = CONFIG.S3.get("path") or "system" + object_pathname += f"/{object_name}" + object_pathname = os.path.normpath(object_pathname) + await self.upload_file(bucket=bucket, local_path=pathname, object_name=object_pathname) + return await self.get_object_url(bucket=bucket, object_name=object_pathname) except Exception as e: logger.exception(f"{e}, stack:{traceback.format_exc()}") return None From 2148e4e4f47edc8e108daf261fb1166b31012f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sat, 2 Sep 2023 19:17:35 +0800 Subject: [PATCH 02/10] feat: +skill config --- metagpt/learn/skill_loader.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/metagpt/learn/skill_loader.py b/metagpt/learn/skill_loader.py index 1cd83240d..83200bca6 100644 --- a/metagpt/learn/skill_loader.py +++ b/metagpt/learn/skill_loader.py @@ -7,11 +7,13 @@ @Desc : Skill YAML Configuration Loader. """ from pathlib import Path -from typing import List, Dict, Optional +from typing import Dict, List, Optional import yaml from pydantic import BaseModel, Field +from metagpt.config import CONFIG + class Example(BaseModel): ask: str @@ -52,7 +54,7 @@ class SkillLoader: def __init__(self, skill_yaml_file_name: Path = None): if not skill_yaml_file_name: skill_yaml_file_name = Path(__file__).parent.parent.parent / ".well-known/skills.yaml" - with open(str(skill_yaml_file_name), 'r') as file: + with open(str(skill_yaml_file_name), "r") as file: skills = yaml.safe_load(file) self._skills = SkillsDeclaration(**skills) @@ -62,8 +64,18 @@ class SkillLoader: if not entity_skills: return {} + agent_skills = CONFIG.agent_skills + if not agent_skills: + return {} + + class AgentSkill(BaseModel): + name: str + + names = [AgentSkill(**i).name for i in agent_skills] description_to_name_mappings = {} for s in entity_skills.skills: + if s.name not in names: + continue description_to_name_mappings[s.description] = s.name return description_to_name_mappings From 610dd8b4ba2771bb7f1d38b101be7fb2cb425fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sat, 2 Sep 2023 19:25:06 +0800 Subject: [PATCH 03/10] feat: +skill config --- metagpt/utils/s3.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/metagpt/utils/s3.py b/metagpt/utils/s3.py index d13030292..531142737 100644 --- a/metagpt/utils/s3.py +++ b/metagpt/utils/s3.py @@ -131,9 +131,11 @@ class S3: async def cache(self, data: str, format: str = "") -> str: """Save data to remote S3 and return url""" object_name = str(uuid.uuid4()).replace("-", "") - pathname = WORKSPACE_ROOT / "s3_tmp" / object_name + path = WORKSPACE_ROOT / "s3_tmp" + path.mkdir(exist_ok=True) + pathname = path / object_name try: - async with aiofiles.open(pathname, mode="w") as file: + async with aiofiles.open(str(pathname), mode="w") as file: if format == BASE64_FORMAT: data = base64.b64decode(data) await file.write(data) @@ -142,7 +144,7 @@ class S3: object_pathname = CONFIG.S3.get("path") or "system" object_pathname += f"/{object_name}" object_pathname = os.path.normpath(object_pathname) - await self.upload_file(bucket=bucket, local_path=pathname, object_name=object_pathname) + await self.upload_file(bucket=bucket, local_path=str(pathname), object_name=object_pathname) return await self.get_object_url(bucket=bucket, object_name=object_pathname) except Exception as e: logger.exception(f"{e}, stack:{traceback.format_exc()}") From 86e3ca0ba99c7522cdbca9df35e3b8fc965fa384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sat, 2 Sep 2023 19:44:26 +0800 Subject: [PATCH 04/10] feat: +skill config --- metagpt/utils/s3.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/metagpt/utils/s3.py b/metagpt/utils/s3.py index 531142737..6df244197 100644 --- a/metagpt/utils/s3.py +++ b/metagpt/utils/s3.py @@ -2,13 +2,14 @@ import base64 import os.path import traceback import uuid +from pathlib import Path from typing import Optional import aioboto3 import aiofiles from metagpt.config import CONFIG -from metagpt.const import BASE64_FORMAT, WORKSPACE_ROOT +from metagpt.const import BASE64_FORMAT from metagpt.logs import logger @@ -131,8 +132,7 @@ class S3: async def cache(self, data: str, format: str = "") -> str: """Save data to remote S3 and return url""" object_name = str(uuid.uuid4()).replace("-", "") - path = WORKSPACE_ROOT / "s3_tmp" - path.mkdir(exist_ok=True) + path = Path(__file__).parent pathname = path / object_name try: async with aiofiles.open(str(pathname), mode="w") as file: @@ -145,7 +145,10 @@ class S3: object_pathname += f"/{object_name}" object_pathname = os.path.normpath(object_pathname) await self.upload_file(bucket=bucket, local_path=str(pathname), object_name=object_pathname) + pathname.unlink(missing_ok=True) + return await self.get_object_url(bucket=bucket, object_name=object_pathname) except Exception as e: logger.exception(f"{e}, stack:{traceback.format_exc()}") + pathname.unlink(missing_ok=True) return None From 7881937e8fb3c5a4ef183d6460fc1d741c0d6b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sat, 2 Sep 2023 20:47:14 +0800 Subject: [PATCH 05/10] feat: test s3 --- metagpt/utils/s3.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/metagpt/utils/s3.py b/metagpt/utils/s3.py index 6df244197..74c3f1654 100644 --- a/metagpt/utils/s3.py +++ b/metagpt/utils/s3.py @@ -129,13 +129,13 @@ class S3: logger.error(f"Failed to download the file from S3: {e}") raise e - async def cache(self, data: str, format: str = "") -> str: + async def cache(self, data: str, file_ext: str, format: str = "") -> str: """Save data to remote S3 and return url""" - object_name = str(uuid.uuid4()).replace("-", "") + object_name = str(uuid.uuid4()).replace("-", "") + file_ext path = Path(__file__).parent pathname = path / object_name try: - async with aiofiles.open(str(pathname), mode="w") as file: + async with aiofiles.open(str(pathname), mode="wb") as file: if format == BASE64_FORMAT: data = base64.b64decode(data) await file.write(data) From 9d74e8e157029ec1e49d307adc121772e1dc048f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sat, 2 Sep 2023 20:51:02 +0800 Subject: [PATCH 06/10] feat: test s3 --- metagpt/learn/text_to_image.py | 4 ++-- metagpt/learn/text_to_speech.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/metagpt/learn/text_to_image.py b/metagpt/learn/text_to_image.py index c5f554ef3..dd85cf617 100644 --- a/metagpt/learn/text_to_image.py +++ b/metagpt/learn/text_to_image.py @@ -33,7 +33,7 @@ async def text_to_image(text, size_type: str = "512x512", openai_api_key="", mod raise openai.error.InvalidRequestError("缺少必要的参数") s3 = S3() - url = await s3.cache(base64_data, BASE64_FORMAT) + url = await s3.cache(data=base64_data, file_ext=".png", format=BASE64_FORMAT) if url: - return url + return f"[{text}]({url})" return image_declaration + base64_data if base64_data else "" diff --git a/metagpt/learn/text_to_speech.py b/metagpt/learn/text_to_speech.py index 7883ae9f3..819da2364 100644 --- a/metagpt/learn/text_to_speech.py +++ b/metagpt/learn/text_to_speech.py @@ -22,7 +22,7 @@ async def text_to_speech( role="Girl", subscription_key="", region="", - **kwargs + **kwargs, ): """Text to speech For more details, check out:`https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=tts` @@ -41,9 +41,9 @@ async def text_to_speech( if (CONFIG.AZURE_TTS_SUBSCRIPTION_KEY and CONFIG.AZURE_TTS_REGION) or (subscription_key and region): base64_data = await oas3_azsure_tts(text, lang, voice, style, role, subscription_key, region) s3 = S3() - url = await s3.cache(base64_data, BASE64_FORMAT) + url = await s3.cache(data=base64_data, file_ext=".wav", format=BASE64_FORMAT) if url: - return url + return f"[{text}]({url})" return audio_declaration + base64_data if base64_data else base64_data raise openai.error.InvalidRequestError("缺少必要的参数") From 842aac82fcda09a6879edfdcf40adfc12b053790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sat, 2 Sep 2023 21:11:44 +0800 Subject: [PATCH 07/10] fixbug: summary too long --- metagpt/provider/openai_api.py | 45 ++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index c08a34f7e..4764b6aad 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -226,38 +226,45 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): return CONFIG.max_tokens_rsp return get_max_completion_tokens(messages, self.model, CONFIG.max_tokens_rsp) - async def get_summary(self, text: str, max_words=20): + async def get_summary(self, text: str, max_words=200): + max_token_count = DEFAULT_MAX_TOKENS + max_count = 100 + while max_count > 0: + if len(text) < max_token_count: + return await self._get_summary(text, max_words=max_words) + + text_windows = self.split_texts(text, window_size=max_token_count - max_words) + summaries = [] + for ws in text_windows: + response = await self._get_summary(ws, max_words=max_words) + summaries.append(response) + if len(summaries) == 1: + return summaries[0] + + # Merged and retry + text = "\n".join(summaries) + + max_count -= 1 # safeguard + raise openai.error.InvalidRequestError("text too long") + + async def _get_summary(self, text: str, max_words=20): """Generate text summary""" if len(text) < max_words: return text - language = CONFIG.language or DEFAULT_LANGUAGE - command = f"Translate the above content into a {language} summary of less than {max_words} words." + command = f"Translate the above content into a summary of less than {max_words} words." msg = text + "\n\n" + command logger.info(f"summary ask:{msg}") response = await self.aask(msg=msg, system_msgs=[]) logger.info(f"summary rsp: {response}") return response - async def get_context_title(self, text: str, max_token_count_per_ask=None, max_words=5) -> str: + async def get_context_title(self, text: str, max_words=5) -> str: """Generate text title""" - max_response_token_count = 50 - max_token_count = max_token_count_per_ask or CONFIG.MAX_TOKENS or DEFAULT_MAX_TOKENS - while True: - text_windows = self.split_texts(text, window_size=max_token_count - max_response_token_count) - - summaries = [] - for ws in text_windows: - response = await self.get_summary(ws, max_words=max_response_token_count) - summaries.append(response) - if len(summaries) == 1: - return summaries[0] - text = "\n".join(summaries) - if len(text) <= max_words * 2 and len(text) <= max_token_count: - break + summary = await self.get_summary(text, max_words) language = CONFIG.language or DEFAULT_LANGUAGE command = f"Translate the above summary into a {language} title of less than {max_words} words." - summaries.append(command) + summaries = [summary, command] msg = "\n".join(summaries) logger.info(f"title ask:{msg}") response = await self.aask(msg=msg, system_msgs=[]) From 3112680324a2ba42ecf39b31796d14c605509848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sat, 2 Sep 2023 21:30:19 +0800 Subject: [PATCH 08/10] fixbug: summary too long --- metagpt/provider/openai_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index 4764b6aad..b1d8aaa4a 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -260,7 +260,7 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): async def get_context_title(self, text: str, max_words=5) -> str: """Generate text title""" - summary = await self.get_summary(text, max_words) + summary = await self.get_summary(text, max_words=500) language = CONFIG.language or DEFAULT_LANGUAGE command = f"Translate the above summary into a {language} title of less than {max_words} words." From 264799541155c6ff59727a15e55b7b2ec5d4582c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sat, 2 Sep 2023 21:38:49 +0800 Subject: [PATCH 09/10] fixbug: summary too long --- metagpt/provider/openai_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index b1d8aaa4a..b2a0faca5 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -233,7 +233,8 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): if len(text) < max_token_count: return await self._get_summary(text, max_words=max_words) - text_windows = self.split_texts(text, window_size=max_token_count - max_words) + padding_size = 20 if max_token_count > 20 else 0 + text_windows = self.split_texts(text, window_size=max_token_count - padding_size) summaries = [] for ws in text_windows: response = await self._get_summary(ws, max_words=max_words) From 5980b08c80451740ad5c3c3e057a146dcffb8694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sat, 2 Sep 2023 21:48:23 +0800 Subject: [PATCH 10/10] fixbug: summary too long --- metagpt/roles/assistant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metagpt/roles/assistant.py b/metagpt/roles/assistant.py index fdd697b59..c707cb6f1 100644 --- a/metagpt/roles/assistant.py +++ b/metagpt/roles/assistant.py @@ -123,7 +123,7 @@ class Assistant(Role): return None if history_text == "": return last_talk - history_summary = await self._llm.get_context_title(history_text, max_token_count_per_ask=1000, max_words=500) + history_summary = await self._llm.get_summary(history_text, max_words=500) if last_talk and await self._llm.is_related(last_talk, history_summary): # Merge relevant content. last_talk = await self._llm.rewrite(sentence=last_talk, context=history_text) return last_talk