From b78d64f39ef67702f82dd2e5cf63cc1a1ff4b365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 20 Mar 2024 15:51:07 +0800 Subject: [PATCH 1/2] feat: +learn readme --- metagpt/actions/learn_readme.py | 112 +++++++++++++++++++++ metagpt/utils/graph_repository.py | 4 + tests/metagpt/actions/test_learn_readme.py | 26 +++++ 3 files changed, 142 insertions(+) create mode 100644 metagpt/actions/learn_readme.py create mode 100644 tests/metagpt/actions/test_learn_readme.py diff --git a/metagpt/actions/learn_readme.py b/metagpt/actions/learn_readme.py new file mode 100644 index 000000000..030651f3f --- /dev/null +++ b/metagpt/actions/learn_readme.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Module Description: This script defines the LearnReadMe class, which is an action to learn from the contents of + a README.md file. +Author: mashenquan +Date: 2024-3-20 +""" +from pathlib import Path +from typing import Optional + +from pydantic import Field + +from metagpt.actions import Action +from metagpt.const import GRAPH_REPO_FILE_REPO +from metagpt.schema import Message +from metagpt.utils.common import aread +from metagpt.utils.di_graph_repository import DiGraphRepository +from metagpt.utils.graph_repository import GraphKeyword, GraphRepository + + +class LearnReadMe(Action): + """ + An action to learn from the contents of a README.md file. + + Attributes: + graph_db (Optional[GraphRepository]): A graph database repository. + install_to_path (Optional[str]): The path where the repository to install to. + """ + + graph_db: Optional[GraphRepository] = None + install_to_path: Optional[str] = Field(default="/TO/PATH") + _readme: Optional[str] = None + _filename: Optional[str] = None + + async def run(self, with_messages=None, **kwargs): + """ + Implementation of `Action`'s `run` method. + + Args: + with_messages (Optional[Type]): An optional argument specifying messages to react to. + """ + graph_repo_pathname = self.context.git_repo.workdir / GRAPH_REPO_FILE_REPO / self.context.git_repo.workdir.name + self.graph_db = await DiGraphRepository.load_from(str(graph_repo_pathname.with_suffix(".json"))) + summary = await self.summarize() + await self.graph_db.insert(subject=self._filename, predicate=GraphKeyword.HAS_SUMMARY, object_=summary) + install = await self.install() + await self.graph_db.insert(subject=self._filename, predicate=GraphKeyword.HAS_INSTALL, object_=install) + conf = await self.config() + await self.graph_db.insert(subject=self._filename, predicate=GraphKeyword.HAS_CONFIG, object_=conf) + usage = await self.usage() + await self.graph_db.insert(subject=self._filename, predicate=GraphKeyword.HAS_USAGE, object_=usage) + + await self.graph_db.save() + + return Message(content="", cause_by=self) + + async def summarize(self) -> str: + readme = await self._get() + summary = await self.llm.aask( + readme, + system_msgs=[ + "You are a tool can summarize git repository README.md file.", + "Return the summary about what is the repository.", + ], + ) + return summary + + async def install(self) -> str: + readme = await self._get() + install = await self.llm.aask( + readme, + system_msgs=[ + "You are a tool can install git repository according to README.md file.", + "Return a bash code block of markdown including:\n" + f"1. git clone the repository to the directory `{self.install_to_path}`;\n" + f"2. cd `{self.install_to_path}`;\n" + f"3. install the repository.", + ], + ) + return install + + async def config(self) -> str: + readme = await self._get() + configuration = await self.llm.aask( + readme, + system_msgs=[ + "You are a tool can configure git repository according to README.md file.", + "Return a bash code block of markdown object to configure the repository if necessary, otherwise return" + " a empty bash code block of markdown object", + ], + ) + return configuration + + async def usage(self) -> str: + readme = await self._get() + usage = await self.llm.aask( + readme, + system_msgs=[ + "You are a tool can summarize all usages of git repository according to README.md file.", + "Return a list of code block of markdown objects to demonstrates the usage of the repository.", + ], + ) + return usage + + async def _get(self) -> str: + if self._readme is not None: + return self._readme + filename = Path(self.i_context).resolve() / "README.md" + self._readme = await aread(filename=filename, encoding="utf-8") + self._filename = str(filename) + return self._readme diff --git a/metagpt/utils/graph_repository.py b/metagpt/utils/graph_repository.py index eb1fc5e12..f4219fac3 100644 --- a/metagpt/utils/graph_repository.py +++ b/metagpt/utils/graph_repository.py @@ -49,6 +49,10 @@ class GraphKeyword: IS_COMPOSITE_OF = "is_composite_of" IS_AGGREGATE_OF = "is_aggregate_of" HAS_PARTICIPANT = "has_participant" + HAS_SUMMARY = "has_summary" + HAS_INSTALL = "has_install" + HAS_CONFIG = "has_config" + HAS_USAGE = "has_usage" class SPO(BaseModel): diff --git a/tests/metagpt/actions/test_learn_readme.py b/tests/metagpt/actions/test_learn_readme.py new file mode 100644 index 000000000..4e623cf2c --- /dev/null +++ b/tests/metagpt/actions/test_learn_readme.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from pathlib import Path + +import pytest + +from metagpt.actions.learn_readme import LearnReadMe +from metagpt.llm import LLM + + +@pytest.mark.asyncio +async def test_learn_readme(context): + action = LearnReadMe( + name="RedBean", + i_context=str(Path(__file__).parent.parent.parent.parent), + llm=LLM(), + context=context, + ) + await action.run() + rows = await action.graph_db.select() + assert rows + assert context.repo.docs.graph_repo.changed_files + + +if __name__ == "__main__": + pytest.main([__file__, "-s"]) From 13f5fa73e0adc6d442c3a5cb8a0ffcc7e153cf81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 20 Mar 2024 16:33:23 +0800 Subject: [PATCH 2/2] feat: rename --- .../{learn_readme.py => extract_readme.py} | 32 +++++++++---------- ...learn_readme.py => test_extract_readme.py} | 4 +-- 2 files changed, 18 insertions(+), 18 deletions(-) rename metagpt/actions/{learn_readme.py => extract_readme.py} (85%) rename tests/metagpt/actions/{test_learn_readme.py => test_extract_readme.py} (85%) diff --git a/metagpt/actions/learn_readme.py b/metagpt/actions/extract_readme.py similarity index 85% rename from metagpt/actions/learn_readme.py rename to metagpt/actions/extract_readme.py index 030651f3f..aeb3608a0 100644 --- a/metagpt/actions/learn_readme.py +++ b/metagpt/actions/extract_readme.py @@ -19,9 +19,9 @@ from metagpt.utils.di_graph_repository import DiGraphRepository from metagpt.utils.graph_repository import GraphKeyword, GraphRepository -class LearnReadMe(Action): +class ExtractReadMe(Action): """ - An action to learn from the contents of a README.md file. + An action to extract summary, installation, configuration, usages from the contents of a README.md file. Attributes: graph_db (Optional[GraphRepository]): A graph database repository. @@ -42,20 +42,20 @@ class LearnReadMe(Action): """ graph_repo_pathname = self.context.git_repo.workdir / GRAPH_REPO_FILE_REPO / self.context.git_repo.workdir.name self.graph_db = await DiGraphRepository.load_from(str(graph_repo_pathname.with_suffix(".json"))) - summary = await self.summarize() + summary = await self._summarize() await self.graph_db.insert(subject=self._filename, predicate=GraphKeyword.HAS_SUMMARY, object_=summary) - install = await self.install() + install = await self._extract_install() await self.graph_db.insert(subject=self._filename, predicate=GraphKeyword.HAS_INSTALL, object_=install) - conf = await self.config() + conf = await self._extract_configuration() await self.graph_db.insert(subject=self._filename, predicate=GraphKeyword.HAS_CONFIG, object_=conf) - usage = await self.usage() + usage = await self._extract_usage() await self.graph_db.insert(subject=self._filename, predicate=GraphKeyword.HAS_USAGE, object_=usage) await self.graph_db.save() return Message(content="", cause_by=self) - async def summarize(self) -> str: + async def _summarize(self) -> str: readme = await self._get() summary = await self.llm.aask( readme, @@ -66,10 +66,10 @@ class LearnReadMe(Action): ) return summary - async def install(self) -> str: - readme = await self._get() + async def _extract_install(self) -> str: + await self._get() install = await self.llm.aask( - readme, + self._readme, system_msgs=[ "You are a tool can install git repository according to README.md file.", "Return a bash code block of markdown including:\n" @@ -80,10 +80,10 @@ class LearnReadMe(Action): ) return install - async def config(self) -> str: - readme = await self._get() + async def _extract_configuration(self) -> str: + await self._get() configuration = await self.llm.aask( - readme, + self._readme, system_msgs=[ "You are a tool can configure git repository according to README.md file.", "Return a bash code block of markdown object to configure the repository if necessary, otherwise return" @@ -92,10 +92,10 @@ class LearnReadMe(Action): ) return configuration - async def usage(self) -> str: - readme = await self._get() + async def _extract_usage(self) -> str: + await self._get() usage = await self.llm.aask( - readme, + self._readme, system_msgs=[ "You are a tool can summarize all usages of git repository according to README.md file.", "Return a list of code block of markdown objects to demonstrates the usage of the repository.", diff --git a/tests/metagpt/actions/test_learn_readme.py b/tests/metagpt/actions/test_extract_readme.py similarity index 85% rename from tests/metagpt/actions/test_learn_readme.py rename to tests/metagpt/actions/test_extract_readme.py index 4e623cf2c..a3428d4d5 100644 --- a/tests/metagpt/actions/test_learn_readme.py +++ b/tests/metagpt/actions/test_extract_readme.py @@ -4,13 +4,13 @@ from pathlib import Path import pytest -from metagpt.actions.learn_readme import LearnReadMe +from metagpt.actions.extract_readme import ExtractReadMe from metagpt.llm import LLM @pytest.mark.asyncio async def test_learn_readme(context): - action = LearnReadMe( + action = ExtractReadMe( name="RedBean", i_context=str(Path(__file__).parent.parent.parent.parent), llm=LLM(),