Skip to content

Commit

Permalink
BossRequirement -> UserRequirement
Browse files Browse the repository at this point in the history
team.start_project(idea) -> team.run_project(idea)
fire.Fire(main) -> app() # typer
_publish -> publish_message
cause_by=type(todo) -> cause_by=todo
  • Loading branch information
geekan committed Dec 11, 2023
1 parent 1287098 commit b538841
Show file tree
Hide file tree
Showing 14 changed files with 93 additions and 65 deletions.
2 changes: 1 addition & 1 deletion src/blog/werewolf/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ One of the essential elements of implementing the Werewolf game lies in facilita
2. A private message from a werewolf, signaling the werewolf partner and the Moderator about a chosen kill (one-to-multiple).
3. A public message from the Moderator instructing all players to awaken (one-to-all).

MetaGPT supports all three communication thanks to the key abstractions `Environment` and `Message`, as well as the agent's (`Role`'s) method of message handling via the `\_publish` and `\_observe` functions. Every time an agent sends a `Message`, it `\_publish` the `Message` to the `Environment`. In turn, receiving agents `\_observe` the `Message` from the `Environment`. All we need to do is to populate the `Message` attributes, such as `send_to` and `restricted_to`, with the intended recipients. MetaGPT handles everything else.
MetaGPT supports all three communication thanks to the key abstractions `Environment` and `Message`, as well as the agent's (`Role`'s) method of message handling via the `\publish_message` and `\_observe` functions. Every time an agent sends a `Message`, it `\publish_message` the `Message` to the `Environment`. In turn, receiving agents `\_observe` the `Message` from the `Environment`. All we need to do is to populate the `Message` attributes, such as `send_to` and `restricted_to`, with the intended recipients. MetaGPT handles everything else.

All combined, we vitalize a sophisticated communication topology between agents. For detailed implementation, please feel free to check our code. We are actively working on refining this mechanism and will release a comprehensive guide soon.

Expand Down
2 changes: 1 addition & 1 deletion src/guide/get_started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ async def startup(idea: str):
]
)
company.invest(investment=3.0)
company.start_project(idea=idea)
company.run_project(idea=idea)

await company.run(n_round=5)
```
Expand Down
4 changes: 2 additions & 2 deletions src/guide/tutorials/agent_101.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class SimpleCoder(Role):
msg = self.get_memories(k=1)[0] # find the most recent k messages

code_text = await todo.run(msg.content)
msg = Message(content=code_text, role=self.profile, cause_by=type(todo))
msg = Message(content=code_text, role=self.profile, cause_by=todo)

return msg
```
Expand Down Expand Up @@ -184,7 +184,7 @@ class RunnableCoder(Role):
msg = self.get_memories(k=1)[0] # find the most k recent messages
result = await todo.run(msg.content)

msg = Message(content=result, role=self.profile, cause_by=type(todo))
msg = Message(content=result, role=self.profile, cause_by=todo)
self._rc.memory.add(msg)
return msg
```
Expand Down
44 changes: 21 additions & 23 deletions src/guide/tutorials/multi_agent_101.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ By giving the outline above, we actually make our SOP clear. We will talk about
We list the three `Action`s.

```python
from metagpt.actions import Action

class SimpleWriteCode(Action):

PROMPT_TEMPLATE = """
Expand All @@ -44,13 +46,9 @@ class SimpleWriteCode(Action):
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
```

Expand All @@ -68,13 +66,9 @@ class SimpleWriteTest(Action):
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
```
```python
Expand All @@ -89,17 +83,14 @@ class SimpleWriteReview(Action):
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
```
#### Define Role
In many multi-agent scenarios, defining a `Role` can be as simple as 10 lines of codes. For `SimpleCoder`, we do two things:
1. Equip the `Role` with the appropriate `Action`s with `_init_actions`, this is identical to setting up a single agent
2. A multi-agent operation: we make the `Role` `_watch` important upstream messages from users or other agents. Recall our SOP, `SimpleCoder` takes user instruction, which is a `Message` caused by `BossRequirement` in MetaGPT. Therefore, we add `self._watch([BossRequirement])`.
2. A multi-agent operation: we make the `Role` `_watch` important upstream messages from users or other agents. Recall our SOP, `SimpleCoder` takes user instruction, which is a `Message` caused by `UserRequirement` in MetaGPT. Therefore, we add `self._watch([UserRequirement])`.

That's all users have to do. For those who are interested in the mechanism under the hood, see [Mechanism Explained](#mechanism-explained) of this chapter.

Expand All @@ -112,7 +103,7 @@ class SimpleCoder(Role):
**kwargs,
):
super().__init__(name, profile, **kwargs)
self._watch([BossRequirement])
self._watch([UserRequirement])
self._init_actions([SimpleWriteCode])
```

Expand Down Expand Up @@ -148,7 +139,7 @@ class SimpleTester(Role):

code_text = await todo.run(context, k=5) # specify arguments

msg = Message(content=code_text, role=self.profile, cause_by=type(todo))
msg = Message(content=code_text, role=self.profile, cause_by=todo)

return msg
```
Expand All @@ -172,10 +163,17 @@ Now that we have defined our three `Role`s, it's time to put them together. We i

Run the `Team`, we should see the collaboration between them!
```python
async def main(
idea: str = "write a function that calculates the product of a list",
investment: float = 3.0,
n_round: int = 5,
import asyncio
import typer
from metagpt.logs import logger
from metagpt.team import Team
app = typer.Typer()

@app.command()
def main(
idea: str = typer.Argument(..., help="write a function that calculates the product of a list"),
investment: float = typer.Option(default=3.0, help="Dollar amount to invest in the AI company."),
n_round: int = typer.Option(default=5, help="Number of rounds for the simulation."),
):
logger.info(idea)

Expand All @@ -189,11 +187,11 @@ async def main(
)

team.invest(investment=investment)
team.start_project(idea)
await team.run(n_round=n_round)
team.run_project(idea)
asyncio.run(team.run(n_round=n_round))

if __name__ == '__main__':
fire.Fire(main)
app()
```
## Complete script of this tutorial

Expand All @@ -213,10 +211,10 @@ While users can write a few lines of code to set up a running `Role`, it's benef

![img](/image/guide/tutorials/multi_agents_flowchart.png)

Internally, as shown in the right part of the diagram, the `Role` will `_observe` `Message` from the `Environment`. If there is a `Message` caused by the particular `Action`s the `Role` `_watch`, then it is a valid observation, triggering the `Role`'s subsequent thoughts and actions. In `_think`, the `Role` will choose one of its capable `Action`s and set it as todo. During `_act`, `Role` executes the todo, i.e., runs the `Action` and obtains the output. The output is encapsulated in a `Message` to be finally `_publish` to the `Environment`, finishing a complete agent run.
Internally, as shown in the right part of the diagram, the `Role` will `_observe` `Message` from the `Environment`. If there is a `Message` caused by the particular `Action`s the `Role` `_watch`, then it is a valid observation, triggering the `Role`'s subsequent thoughts and actions. In `_think`, the `Role` will choose one of its capable `Action`s and set it as todo. During `_act`, `Role` executes the todo, i.e., runs the `Action` and obtains the output. The output is encapsulated in a `Message` to be finally `publish_message` to the `Environment`, finishing a complete agent run.

In each step, either `_observe`, `_think`, or `_act`, the `Role` will interact with its `Memory`, through adding or retrieval. Moreover, MetaGPT provides different modes of the `react` process. For these parts, please see [Use Memories](use_memories) and [Think and act](agent_think_act)

When each `Role` is set up appropriately, we may see the corresponding SOP to the example earlier in this tutorial, demonstrated by the left half of the diagram. The dotted box suggests the SOP can be extended if we make `SimpleTester` `_watch` both `SimpleWriteCode` and `SimpleWriteReview`.

We encourage developers with interest to see the [code](https://github.com/geekan/MetaGPT/blob/main/metagpt/roles/role.py) of `Role`, as we believe it is quite readable. Checking out `run`, `_observe`, `react`, `_think`, `_act`, `_publish` should provide one with a decent understanding.
We encourage developers with interest to see the [code](https://github.com/geekan/MetaGPT/blob/main/metagpt/roles/role.py) of `Role`, as we believe it is quite readable. Checking out `run`, `_observe`, `react`, `_think`, `_act`, `publish_message` should provide one with a decent understanding.
2 changes: 1 addition & 1 deletion src/guide/tutorials/use_memories.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async def _act(self) -> Message:

code_text = await todo.run(context, k=5) # specify arguments

msg = Message(content=code_text, role=self.profile, cause_by=type(todo))
msg = Message(content=code_text, role=self.profile, cause_by=todo)

return msg
```
Expand Down
4 changes: 2 additions & 2 deletions src/guide/use_cases/agent/researcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,14 +246,14 @@ class Researcher(Role):
# Search the internet and retrieve URL information
if isinstance(todo, CollectLinks):
links = await todo.run(topic, 4, 4)
ret = Message("", Report(topic=topic, links=links), role=self.profile, cause_by=type(todo))
ret = Message("", Report(topic=topic, links=links), role=self.profile, cause_by=todo)
# Browse web pages and summarize their content
elif isinstance(todo, WebBrowseAndSummarize):
links = instruct_content.links
todos = (todo.run(*url, query=query, system_text=research_system_text) for (query, url) in links.items())
summaries = await asyncio.gather(*todos)
summaries = list((url, summary) for i in summaries for (url, summary) in i.items() if summary)
ret = Message("", Report(topic=topic, summaries=summaries), role=self.profile, cause_by=type(todo))
ret = Message("", Report(topic=topic, summaries=summaries), role=self.profile, cause_by=todo)
# Generate a research report
else:
summaries = instruct_content.summaries
Expand Down
29 changes: 20 additions & 9 deletions src/guide/use_cases/multi_agent/debate.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class SpeakAloud(Action):
### Define Role
We will define a common `Role` called `Debator`.

Here `_init_actions` make our `Role` possess the `SpeakAloud` action we just define. We also `_watch` both `SpeakAloud` and `BossRequirement`, because we want each debator to pay attention to messages of `SpeakAloud` from his opponent, as well as `BossRequirement` (human instruction) from users.
Here `_init_actions` make our `Role` possess the `SpeakAloud` action we just define. We also `_watch` both `SpeakAloud` and `UserRequirement`, because we want each debator to pay attention to messages of `SpeakAloud` from his opponent, as well as `UserRequirement` (human instruction) from users.
```python
class Debator(Role):
def __init__(
Expand All @@ -54,7 +54,7 @@ class Debator(Role):
):
super().__init__(name, profile, **kwargs)
self._init_actions([SpeakAloud])
self._watch([BossRequirement, SpeakAloud])
self._watch([UserRequirement, SpeakAloud])
self.name = name
self.opponent_name = opponent_name
```
Expand All @@ -81,7 +81,7 @@ async def _act(self) -> Message:
msg = Message(
content=rsp,
role=self.profile,
cause_by=type(todo),
cause_by=todo,
sent_from=self.name,
send_to=self.opponent_name,
)
Expand All @@ -102,7 +102,7 @@ class Debator(Role):
):
super().__init__(name, profile, **kwargs)
self._init_actions([SpeakAloud])
self._watch([BossRequirement, SpeakAloud])
self._watch([UserRequirement, SpeakAloud])
self.name = name
self.opponent_name = opponent_name

Expand All @@ -124,7 +124,7 @@ class Debator(Role):
msg = Message(
content=rsp,
role=self.profile,
cause_by=type(todo),
cause_by=todo,
sent_from=self.name,
send_to=self.opponent_name,
)
Expand All @@ -134,7 +134,7 @@ class Debator(Role):
return msg
```
### Create a team and add roles
Now that we have defined our `Debator`s, let's put them together to see what will come up. We set up a `Team` and "hire" Biden and Trump. In this example, we will send our instruction (as a `BossRequirement` under the hood) to Biden to have him start first. If you want Trump to speak first, set send_to as "Trump".
Now that we have defined our `Debator`s, let's put them together to see what will come up. We set up a `Team` and "hire" Biden and Trump. In this example, we will send our instruction (as a `UserRequirement` under the hood) to Biden to have him start first. If you want Trump to speak first, set send_to as "Trump".

Run the `Team`, we should see the friendly conversation between them!
```python
Expand All @@ -145,10 +145,21 @@ async def debate(idea: str, investment: float = 3.0, n_round: int = 5):
team = Team()
team.hire([Biden, Trump])
team.invest(investment)
team.start_project(idea, send_to="Biden") # send debate topic to Biden and let him speak first
team.run_project(idea, send_to="Biden") # send debate topic to Biden and let him speak first
await team.run(n_round=n_round)

def main(idea: str, investment: float = 3.0, n_round: int = 10):
import asyncio
import platform
import typer
from metagpt.team import Team
app = typer.Typer()

@app.command()
def main(
idea: str = typer.Argument(..., help="Economic Policy: Discuss strategies and plans related to taxation, employment, fiscal budgeting, and economic growth."),
investment: float = typer.Option(default=3.0, help="Dollar amount to invest in the AI company."),
n_round: int = typer.Option(default=5, help="Number of rounds for the simulation."),
):
"""
:param idea: Debate topic, such as "Topic: The U.S. should commit more in climate change fighting"
or "Trump: Climate change is a hoax"
Expand All @@ -161,7 +172,7 @@ def main(idea: str, investment: float = 3.0, n_round: int = 10):
asyncio.run(debate(idea, investment, n_round))

if __name__ == '__main__':
fire.Fire(main)
app()
```
## Complete script of this section

Expand Down
2 changes: 1 addition & 1 deletion src/rfcs/RFC-116-MetaGPT优化方案.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
await super()._observe()
# accept the very first human instruction (the debate topic) or messages sent (from opponent) to self,
# disregard own messages from the last round
self._rc.news = [msg for msg in self._rc.news if msg.cause_by == BossRequirement or msg.send_to == self.name]
self._rc.news = [msg for msg in self._rc.news if msg.cause_by == UserRequirement or msg.send_to == self.name]
return len(self._rc.news)
```
5. `sent_from`被用作展示时显示的发言者信息。本质是meta信息的一部分。
Expand Down
2 changes: 1 addition & 1 deletion src/zhcn/guide/get_started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ async def startup(idea: str):
]
)
company.invest(investment=3.0)
company.start_project(idea=idea)
company.run_project(idea=idea)

await company.run(n_round=5)
```
Expand Down
2 changes: 1 addition & 1 deletion src/zhcn/guide/tutorials/agent_101.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ class RunnableCoder(Role):
msg = self.get_memories(k=1)[0] # 得到最相似的 k 条消息
result = await todo.run(msg.content)

msg = Message(content=result, role=self.profile, cause_by=type(todo))
msg = Message(content=result, role=self.profile, cause_by=todo)
self._rc.memory.add(msg)
return msg
```
Expand Down
Loading

0 comments on commit b538841

Please sign in to comment.