mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-05-04 13:22:39 +02:00
Merge pull request #481 from garylin2099/human_roleplay
allow human to play any roles
This commit is contained in:
commit
820fee5df6
6 changed files with 218 additions and 19 deletions
|
|
@ -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()
|
||||
|
|
|
|||
158
examples/build_customized_multi_agents.py
Normal file
158
examples/build_customized_multi_agents.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
35
metagpt/provider/human_provider.py
Normal file
35
metagpt/provider/human_provider.py
Normal file
|
|
@ -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 []
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue