diff --git a/README.md b/README.md index d2ca26cdd..1307fbb53 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ # MetaGPT: The Multi-Agent Framework
-
diff --git a/docs/README_CN.md b/docs/README_CN.md
index 3aa0cb05d..a49ca9b77 100644
--- a/docs/README_CN.md
+++ b/docs/README_CN.md
@@ -19,7 +19,6 @@ # MetaGPT: 多智能体框架
-
diff --git a/docs/README_JA.md b/docs/README_JA.md
index 1ced60b83..cba84df80 100644
--- a/docs/README_JA.md
+++ b/docs/README_JA.md
@@ -19,7 +19,6 @@ # MetaGPT: マルチエージェントフレームワーク
-
diff --git a/examples/build_customized_agent.py b/examples/build_customized_agent.py
index c7069b768..be34e5e5e 100644
--- a/examples/build_customized_agent.py
+++ b/examples/build_customized_agent.py
@@ -9,6 +9,7 @@ import asyncio
import fire
+from metagpt.llm import LLM
from metagpt.actions import Action
from metagpt.roles import Role
from metagpt.schema import Message
@@ -19,19 +20,10 @@ class SimpleWriteCode(Action):
PROMPT_TEMPLATE = """
Write a python function that can {instruction} and provide two runnnable test cases.
Return ```python your_code_here ``` with NO other texts,
- example:
- ```python
- # function
- def add(a, b):
- return a + b
- # test cases
- print(add(1, 2))
- print(add(3, 4))
- ```
your code:
"""
- def __init__(self, name="SimpleWriteCode", context=None, llm=None):
+ def __init__(self, name: str = "SimpleWriteCode", context=None, llm: LLM = None):
super().__init__(name, context, llm)
async def run(self, instruction: str):
@@ -51,8 +43,9 @@ class SimpleWriteCode(Action):
code_text = match.group(1) if match else rsp
return code_text
+
class SimpleRunCode(Action):
- def __init__(self, name="SimpleRunCode", context=None, llm=None):
+ def __init__(self, name: str = "SimpleRunCode", context=None, llm: LLM = None):
super().__init__(name, context, llm)
async def run(self, code_text: str):
@@ -61,6 +54,7 @@ class SimpleRunCode(Action):
logger.info(f"{code_result=}")
return code_result
+
class SimpleCoder(Role):
def __init__(
self,
@@ -73,15 +67,16 @@ class SimpleCoder(Role):
async def _act(self) -> Message:
logger.info(f"{self._setting}: ready to {self._rc.todo}")
- todo = self._rc.todo
+ todo = self._rc.todo # todo will be SimpleWriteCode()
msg = self.get_memories(k=1)[0] # find the most recent messages
- code_text = await SimpleWriteCode().run(msg.content)
- msg = Message(content=code_text, role=self.profile, cause_by=todo)
+ code_text = await todo.run(msg.content)
+ msg = Message(content=code_text, role=self.profile, cause_by=type(todo))
return msg
+
class RunnableCoder(Role):
def __init__(
self,
@@ -95,6 +90,8 @@ class RunnableCoder(Role):
async def _act(self) -> Message:
logger.info(f"{self._setting}: ready to {self._rc.todo}")
+ # By choosing the Action by order under the hood
+ # todo will be first SimpleWriteCode() then SimpleRunCode()
todo = self._rc.todo
msg = self.get_memories(k=1)[0] # find the most k recent messages
@@ -104,6 +101,7 @@ class RunnableCoder(Role):
self._rc.memory.add(msg)
return msg
+
def main(msg="write a function that calculates the product of a list and run it"):
# role = SimpleCoder()
role = RunnableCoder()
diff --git a/examples/build_customized_multi_agents.py b/examples/build_customized_multi_agents.py
new file mode 100644
index 000000000..0df927e32
--- /dev/null
+++ b/examples/build_customized_multi_agents.py
@@ -0,0 +1,158 @@
+'''
+Filename: MetaGPT/examples/build_customized_multi_agents.py
+Created Date: Wednesday, November 15th 2023, 7:12:39 pm
+Author: garylin2099
+'''
+import re
+import asyncio
+import fire
+
+from metagpt.llm import LLM
+from metagpt.actions import Action, BossRequirement
+from metagpt.roles import Role
+from metagpt.team import Team
+from metagpt.schema import Message
+from metagpt.logs import logger
+
+def parse_code(rsp):
+ pattern = r'```python(.*)```'
+ match = re.search(pattern, rsp, re.DOTALL)
+ code_text = match.group(1) if match else rsp
+ return code_text
+
+class SimpleWriteCode(Action):
+
+ PROMPT_TEMPLATE = """
+ Write a python function that can {instruction}.
+ Return ```python your_code_here ``` with NO other texts,
+ your code:
+ """
+
+ def __init__(self, name: str = "SimpleWriteCode", context=None, llm: LLM = None):
+ super().__init__(name, context, llm)
+
+ async def run(self, instruction: str):
+
+ prompt = self.PROMPT_TEMPLATE.format(instruction=instruction)
+
+ rsp = await self._aask(prompt)
+
+ code_text = parse_code(rsp)
+
+ return code_text
+
+
+class SimpleCoder(Role):
+ def __init__(
+ self,
+ name: str = "Alice",
+ profile: str = "SimpleCoder",
+ **kwargs,
+ ):
+ super().__init__(name, profile, **kwargs)
+ self._watch([BossRequirement])
+ self._init_actions([SimpleWriteCode])
+
+
+class SimpleWriteTest(Action):
+
+ PROMPT_TEMPLATE = """
+ Context: {context}
+ Write {k} unit tests using pytest for the given function, assuming you have imported it.
+ Return ```python your_code_here ``` with NO other texts,
+ your code:
+ """
+
+ def __init__(self, name: str = "SimpleWriteTest", context=None, llm: LLM = None):
+ super().__init__(name, context, llm)
+
+ async def run(self, context: str, k: int = 3):
+
+ prompt = self.PROMPT_TEMPLATE.format(context=context, k=k)
+
+ rsp = await self._aask(prompt)
+
+ code_text = parse_code(rsp)
+
+ return code_text
+
+
+class SimpleTester(Role):
+ def __init__(
+ self,
+ name: str = "Bob",
+ profile: str = "SimpleTester",
+ **kwargs,
+ ):
+ super().__init__(name, profile, **kwargs)
+ self._init_actions([SimpleWriteTest])
+ # self._watch([SimpleWriteCode])
+ self._watch([SimpleWriteCode, SimpleWriteReview]) # feel free to try this too
+
+ async def _act(self) -> Message:
+ logger.info(f"{self._setting}: ready to {self._rc.todo}")
+ todo = self._rc.todo
+
+ # context = self.get_memories(k=1)[0].content # use the most recent memory as context
+ context = self.get_memories() # use all memories as context
+
+ code_text = await todo.run(context, k=5) # specify arguments
+ msg = Message(content=code_text, role=self.profile, cause_by=type(todo))
+
+ return msg
+
+
+class SimpleWriteReview(Action):
+
+ PROMPT_TEMPLATE = """
+ Context: {context}
+ Review the test cases and provide one critical comments:
+ """
+
+ def __init__(self, name: str = "SimpleWriteReview", context=None, llm: LLM = None):
+ super().__init__(name, context, llm)
+
+ async def run(self, context: str):
+
+ prompt = self.PROMPT_TEMPLATE.format(context=context)
+
+ rsp = await self._aask(prompt)
+
+ return rsp
+
+
+class SimpleReviewer(Role):
+ def __init__(
+ self,
+ name: str = "Charlie",
+ profile: str = "SimpleReviewer",
+ **kwargs,
+ ):
+ super().__init__(name, profile, **kwargs)
+ self._init_actions([SimpleWriteReview])
+ self._watch([SimpleWriteTest])
+
+
+async def main(
+ idea: str = "write a function that calculates the product of a list",
+ investment: float = 3.0,
+ n_round: int = 5,
+ add_human: bool = False,
+):
+ logger.info(idea)
+
+ team = Team()
+ team.hire(
+ [
+ SimpleCoder(),
+ SimpleTester(),
+ SimpleReviewer(is_human=add_human),
+ ]
+ )
+
+ team.invest(investment=investment)
+ team.start_project(idea)
+ await team.run(n_round=n_round)
+
+if __name__ == '__main__':
+ fire.Fire(main)
diff --git a/examples/debate.py b/examples/debate.py
index 4db7567f0..a37e60848 100644
--- a/examples/debate.py
+++ b/examples/debate.py
@@ -77,6 +77,8 @@ class Debator(Role):
send_to=self.opponent_name,
)
+ self._rc.memory.add(msg)
+
return msg
async def debate(idea: str, investment: float = 3.0, n_round: int = 5):
diff --git a/metagpt/llm.py b/metagpt/llm.py
index e6f815950..9324da126 100644
--- a/metagpt/llm.py
+++ b/metagpt/llm.py
@@ -8,6 +8,7 @@
from metagpt.provider.anthropic_api import Claude2 as Claude
from metagpt.provider.openai_api import OpenAIGPTAPI as LLM
+from metagpt.provider.human_provider import HumanProvider
DEFAULT_LLM = LLM()
CLAUDE_LLM = Claude()
diff --git a/metagpt/provider/human_provider.py b/metagpt/provider/human_provider.py
new file mode 100644
index 000000000..1d12f972f
--- /dev/null
+++ b/metagpt/provider/human_provider.py
@@ -0,0 +1,35 @@
+'''
+Filename: MetaGPT/metagpt/provider/human_provider.py
+Created Date: Wednesday, November 8th 2023, 11:55:46 pm
+Author: garylin2099
+'''
+from typing import Optional
+from metagpt.provider.base_gpt_api import BaseGPTAPI
+from metagpt.logs import logger
+
+class HumanProvider(BaseGPTAPI):
+ """Humans provide themselves as a 'model', which actually takes in human input as its response.
+ This enables replacing LLM anywhere in the framework with a human, thus introducing human interaction
+ """
+
+ def ask(self, msg: str) -> str:
+ logger.info("It's your turn, please type in your response. You may also refer to the context below")
+ rsp = input(msg)
+ if rsp in ["exit", "quit"]:
+ exit()
+ return rsp
+
+ async def aask(self, msg: str, system_msgs: Optional[list[str]] = None) -> str:
+ return self.ask(msg)
+
+ def completion(self, messages: list[dict]):
+ """dummy implementation of abstract method in base"""
+ return []
+
+ async def acompletion(self, messages: list[dict]):
+ """dummy implementation of abstract method in base"""
+ return []
+
+ async def acompletion_text(self, messages: list[dict], stream=False) -> str:
+ """dummy implementation of abstract method in base"""
+ return []
diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py
index 0251176f7..b96c361c0 100644
--- a/metagpt/roles/role.py
+++ b/metagpt/roles/role.py
@@ -15,7 +15,7 @@ from pydantic import BaseModel, Field
# from metagpt.environment import Environment
from metagpt.config import CONFIG
from metagpt.actions import Action, ActionOutput
-from metagpt.llm import LLM
+from metagpt.llm import LLM, HumanProvider
from metagpt.logs import logger
from metagpt.memory import Memory, LongTermMemory
from metagpt.schema import Message
@@ -65,6 +65,7 @@ class RoleSetting(BaseModel):
goal: str
constraints: str
desc: str
+ is_human: bool
def __str__(self):
return f"{self.name}({self.profile})"
@@ -106,9 +107,10 @@ class RoleContext(BaseModel):
class Role:
"""Role/Agent"""
- def __init__(self, name="", profile="", goal="", constraints="", desc=""):
- self._llm = LLM()
- self._setting = RoleSetting(name=name, profile=profile, goal=goal, constraints=constraints, desc=desc)
+ def __init__(self, name="", profile="", goal="", constraints="", desc="", is_human=False):
+ self._llm = LLM() if not is_human else HumanProvider()
+ self._setting = RoleSetting(name=name, profile=profile, goal=goal,
+ constraints=constraints, desc=desc, is_human=is_human)
self._states = []
self._actions = []
self._role_id = str(self._setting)
@@ -122,8 +124,11 @@ class Role:
self._reset()
for idx, action in enumerate(actions):
if not isinstance(action, Action):
- i = action("")
+ i = action("", llm=self._llm)
else:
+ if self._setting.is_human and not isinstance(action.llm, HumanProvider):
+ logger.warning(f"is_human attribute does not take effect,"
+ f"as Role's {str(action)} was initialized using LLM, try passing in Action classes instead of initialized instances")
i = action
i.set_prefix(self._get_prefix(), self.profile)
self._actions.append(i)