Hanah

Hanah

wow agent metagpt智能體合集

参考:Datawhale wow agent

單動作單智能體#

使用現成的智能體#

# 可導入任何角色,初始化它,用一個開始的消息運行它,完成!
from metagpt.roles.product_manager import ProductManager
prompt = f"""
# Role:軟件開發團隊

## Background :

我是一個軟件開發團隊。
現在要用html、js、vue3、element-plus開發一個刷題程序。
刷題可以讓人們對題目中涉及的知識點有更深的掌握。

## Profile:
- author: 黎偉
- version: 0.1
- language: 中文
- description: 我是一軟件開發團隊。

## Goals:
- 用html、js、vue3、element-plus開發一個刷題程序的開發需求文檔。

## Constrains:
1. 最後交付的程序是一個html單文件,不要有其他任何文件。
2. 題目的題型至少包括兩道判斷題、兩道選擇題、兩道填空題。
3. 題目的內容與人工智能的agent基本理論相關。
4. 刷題程序至少給出10道樣例題目。
5. 題目用列表的形式寫到html文件的script部分。
6. vue3、element-plus採用cdn的形式在html的header部分引入。

## Skills:
1. 具有強大的js語言開發能力
2. 熟悉vue3、element-plus的使用
3. 對人工智能的agent基本理論有較好理解
4. 擁有排版審美, 會利用序號, 縮進, 分隔線和換行符等等來美化信息排版


請結合上述要求完善刷題程序的開發需求文檔。
"""
async def main():
    role = ProductManager()
    result = await role.run(prompt)
    
await main()

定制智能體#

從實際使用的角度考慮,一個智能體要對我們有用,它必須具備哪些基本要素呢?從 MetaGPT 的觀點來看,如果一個智能體能夠執行某些動作(無論是由 LLM 驅動還是其他方式),它就具有一定的用途。簡單來說,我們定義智能體應該具備哪些行為,為智能體配備這些能力,我們就擁有了一個簡單可用的智能體!

假設我們想用自然語言編寫代碼,並想讓一個智能體為我們做這件事。讓我們稱這個智能體為 SimpleCoder,我們需要兩個步驟來讓它工作:

  1. 定義一個編寫代碼的動作
  2. 為智能體配備這個動作

定義動作

在 MetaGPT 中,類 Action 是動作的邏輯抽象。用戶可以通過簡單地調用 self._aask 函數令 LLM 賦予這個動作能力,即這個函數將在底層調用 LLM api。

from metagpt.actions import Action

class SimpleWriteCode(Action):
    PROMPT_TEMPLATE: str = """
    Write a python function that can {instruction} and provide two runnnable test cases.
    Return ```python your_code_here ```with NO other texts,
    your code:
    """

    name: str = "SimpleWriteCode"

    async def run(self, instruction: str):
        prompt = self.PROMPT_TEMPLATE.format(instruction=instruction)

        rsp = await self._aask(prompt)

        code_text = SimpleWriteCode.parse_code(rsp)

        return code_text

    @staticmethod
    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

定義角色#

在 MetaGPT 中,Role 類是智能體的邏輯抽象。一個 Role 能執行特定的 Action,擁有記憶、思考並採用各種策略行動。基本上,它充當一個將所有這些組件聯繫在一起的凝聚實體。目前,讓我們只關注一個執行動作的智能體,並看看如何定義一個最簡單的 Role。

在這個示例中,我們創建了一個 SimpleCoder,它能夠根據人類的自然語言描述編寫代碼。步驟如下:

  1. 我們為其指定一個名稱和配置文件。
  2. 我們使用 self._init_action 函數為其配備期望的動作 SimpleWriteCode。
  3. 我們覆蓋 _act 函數,其中包含智能體具體行動邏輯。我們寫入,我們的智能體將從最新的記憶中獲取人類指令,運行配備的動作,MetaGPT 將其作為待辦事項 (self.rc.todo) 在幕後處理,最後返回一個完整的消息。
import re
import os
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.logs import logger

class SimpleCoder(Role):
    name: str = "Alice"
    profile: str = "SimpleCoder"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.set_actions([SimpleWriteCode])

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
        todo = self.rc.todo  # todo will be SimpleWriteCode()

        msg = self.get_memories(k=1)[0]  # find the most recent messages
        code_text = await todo.run(msg.content)
        msg = Message(content=code_text, role=self.profile, cause_by=type(todo))

        return msg

運行你的角色#

現在我們可以讓我們的智能體開始工作,只需初始化它並使用一個起始消息運行它。


async def main():
    msg = "write a function that calculates the sum of a list"
    role = SimpleCoder()
    logger.info(msg)
    result = await role.run(msg)
    logger.info(result)
    return result
    
        
rtn = await main()

結果#

2025-01-26 18:14:18.317 | INFO     | __main__:main:4 - write a function that calculates the sum of a list
2025-01-26 18:14:18.319 | INFO     | __main__:_act:16 - Alice(SimpleCoder): to do SimpleWriteCode(SimpleWriteCode)
```python
def sum_list(numbers):
    return sum(numbers)

# Test cases
test_case_1 = [1, 2, 3, 4, 5]
test_case_2 = [10, 20, 30, 40, 50]

# Test case 1
print(sum_list(test_case_1))  # Expected output: 15

# Test case 2
print(sum_list(test_case_2))  # Expected output: 150

2025-01-26 18:14:25.372 | WARNING  | metagpt.utils.cost_manager:update_cost:49 - Model glm-4-flash not found in TOKEN_COSTS.
2025-01-26 18:14:25.374 | INFO     | __main__:main:6 - SimpleCoder: 
def sum_list(numbers):
    return sum(numbers)

# Test cases
test_case_1 = [1, 2, 3, 4, 5]
test_case_2 = [10, 20, 30, 40, 50]

# Test case 1
print(sum_list(test_case_1))  # Expected output: 15

# Test case 2
print(sum_list(test_case_2))  # Expected output: 150

多動作智能體#

具有多個動作的智能體#

我們注意到一個智能體能夠執行一個動作,但如果只有這些,實際上我們並不需要一個智能體。通過直接運行動作本身,我們可以得到相同的結果。智能體的力量,或者說 Role 抽象的驚人之處,在於動作的組合(以及其他組件,比如記憶,但我們將把它們留到後面的部分)。通過連接動作,我們可以構建一個工作流程,使智能體能夠完成更複雜的任務。

假設現在我們不僅希望用自然語言編寫代碼,而且還希望生成的代碼立即執行。一個擁有多個動作的智能體可以滿足我們的需求。讓我們稱之為 RunnableCoder,一個既寫代碼又立即運行的 Role。我們需要兩個 Action:SimpleWriteCode 和 SimpleRunCode。

定義動作#

首先,定義 SimpleWriteCode。我們將重用上面創建的那個。

接下來,定義 SimpleRunCode。如前所述,從概念上講,一個動作可以利用 LLM,也可以在沒有 LLM 的情況下運行。在 SimpleRunCode 的情況下,LLM 不涉及其中。我們只需啟動一個子進程來運行代碼並獲取結果。我們希望展示的是,對於動作邏輯的結構,我們沒有設定任何限制,用戶可以根據需要完全靈活地設計邏輯。

# SimpleWriteCode 這個類與上一節一模一樣

from metagpt.actions import Action

class SimpleWriteCode(Action):
    PROMPT_TEMPLATE: str = """
    Write a python function that can {instruction} and provide two runnnable test cases.
    Return ```python your_code_here ```with NO other texts,
    your code:
    """

    name: str = "SimpleWriteCode"

    async def run(self, instruction: str):
        prompt = self.PROMPT_TEMPLATE.format(instruction=instruction)

        rsp = await self._aask(prompt)

        code_text = SimpleWriteCode.parse_code(rsp)

        return code_text

    @staticmethod
    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
# 本節新增了SimpleRunCode這個類
class SimpleRunCode(Action):
    name: str = "SimpleRunCode"

    async def run(self, code_text: str):
        result = subprocess.run(["python", "-c", code_text], capture_output=True, text=True)
        code_result = result.stdout
        logger.info(f"{code_result=}")
        return code_result

定義角色#

與定義單一動作的智能體沒有太大不同!讓我們來映射一下:

  1. 用 self.set_actions 初始化所有 Action
  2. 指定每次 Role 會選擇哪個 Action。我們將 react_mode 設置為 "by_order",這意味著 Role 將按照 self.set_actions 中指定的順序執行其能夠執行的 Action。在這種情況下,當 Role 執行 _act 時,self.rc.todo 將首先是 SimpleWriteCode,然後是 SimpleRunCode。
  3. 覆蓋 _act 函數。Role 從上一輪的人類輸入或動作輸出中檢索消息,用適當的 Message 內容提供當前的 Action (self.rc.todo),最後返回由當前 Action 輸出組成的 Message。
import re
import os
import subprocess
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.logs import logger
class RunnableCoder(Role):
    name: str = "Alice"
    profile: str = "RunnableCoder"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.set_actions([SimpleWriteCode, SimpleRunCode])
        self._set_react_mode(react_mode="by_order")

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
        # 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
        result = await todo.run(msg.content)

        msg = Message(content=result, role=self.profile, cause_by=type(todo))
        self.rc.memory.add(msg)
        return msg
async def main():
    msg = "write a function that calculates the sum of a list"
    role = RunnableCoder()
    logger.info(msg)
    result = await role.run(msg)
    logger.info(result)
    return result

rtn = await main()
print(rtn)

結果#

def calculate_sum(numbers):
    return sum(numbers)

# Test cases
def test_case_1():
    assert calculate_sum([1, 2, 3, 4, 5]) == 15

def test_case_2():
    assert calculate_sum([10, -5, 0, 7, 3]) == 15

技術教程智能體#

角色介紹#

功能說明
輸入一句話,生成一篇偏技術類教程文檔,支持自定義語言。
設計思路
先通過 LLM 大模型生成教程的目錄,再對目錄按照二級標題進行分塊,對於每塊目錄按照標題生成詳細內容,最後再將標題和內容進行拼接。分塊的設計解決了 LLM 大模型長文本的限制問題。
原文鏈接:
https://docs.deepwisdom.ai/v0.8/zh/guide/use_cases/agent/tutorial_assistant.html
編寫 WriteDirectory 動作
我們先來實現根據用戶需求生成文章大綱的代碼

from metagpt.actions import Action
from typing import Dict, Union
import ast

def extract_struct(text: str, data_type: Union[type(list), type(dict)]) -> Union[list, dict]:
    """Extracts and parses a specified type of structure (dictionary or list) from the given text.
    The text only contains a list or dictionary, which may have nested structures.

    Args:
        text: The text containing the structure (dictionary or list).
        data_type: The data type to extract, can be "list" or "dict".

    Returns:
        - If extraction and parsing are successful, it returns the corresponding data structure (list or dictionary).
        - If extraction fails or parsing encounters an error, it throw an exception.
    返回:
    - 如果提取和解析成功,它將返回相應的數據結構(列表或字典)。
    - 如果提取失敗或解析遇到錯誤,則拋出異常。
    Examples:
        >>> text = 'xxx [1, 2, ["a", "b", [3, 4]], {"x": 5, "y": [6, 7]}] xxx'
        >>> result_list = OutputParser.extract_struct(text, "list")
        >>> print(result_list)
        >>> # Output: [1, 2, ["a", "b", [3, 4]], {"x": 5, "y": [6, 7]}]

        >>> text = 'xxx {"x": 1, "y": {"a": 2, "b": {"c": 3}}} xxx'
        >>> result_dict = OutputParser.extract_struct(text, "dict")
        >>> print(result_dict)
        >>> # Output: {"x": 1, "y": {"a": 2, "b": {"c": 3}}}
    """
    # Find the first "[" or "{" and the last "]" or "}"
    start_index = text.find("[" if data_type is list else "{")
    end_index = text.rfind("]" if data_type is list else "}")

    if start_index != -1 and end_index != -1:
        # Extract the structure part
        structure_text = text[start_index : end_index + 1]

        try:
            # Attempt to convert the text to a Python data type using ast.literal_eval
            result = ast.literal_eval(structure_text)

            # Ensure the result matches the specified data type
            if isinstance(result, list) or isinstance(result, dict):
                return result

            raise ValueError(f"The extracted structure is not a {data_type}.")

        except (ValueError, SyntaxError) as e:
            raise Exception(f"Error while extracting and parsing the {data_type}: {e}")
    else:
        logger.error(f"No {data_type} found in the text.")
        return [] if data_type is list else {}

class WriteDirectory(Action):
    """Action class for writing tutorial directories.

    Args:
        name: The name of the action.
        language: The language to output, default is "Chinese".
        
        用於編寫教程目錄的動作類。
        參數:
        name:動作的名稱。
        language:輸出的語言,默認為"Chinese"。
    """

    name: str = "WriteDirectory"
    language: str = "Chinese"

    async def run(self, topic: str, *args, **kwargs) -> Dict:
        """Execute the action to generate a tutorial directory according to the topic.

        Args:
            topic: The tutorial topic.

        Returns:
            the tutorial directory information, including {"title": "xxx", "directory": [{"dir 1": ["sub dir 1", "sub dir 2"]}]}.
        根據主題執行生成教程目錄的操作。
            參數:
            topic:教程主題。
            返回:
            教程目錄信息,包括{"title": "xxx", "directory": [{"dir 1": ["sub dir 1", "sub dir 2"]}]}.
        """
        COMMON_PROMPT = """
        You are now a seasoned technical professional in the field of the internet. 
        We need you to write a technical tutorial with the topic "{topic}".
        您現在是互聯網領域的經驗豐富的技術專業人員。
        我們需要您撰寫一個關於"{topic}"的技術教程。
        """

        DIRECTORY_PROMPT = COMMON_PROMPT + """
        Please provide the specific table of contents for this tutorial, strictly following the following requirements:
        1. The output must be strictly in the specified language, {language}.
        2. Answer strictly in the dictionary format like {{"title": "xxx", "directory": [{{"dir 1": ["sub dir 1", "sub dir 2"]}}, {{"dir 2": ["sub dir 3", "sub dir 4"]}}]}}.
        3. The directory should be as specific and sufficient as possible, with a primary and secondary directory.The secondary directory is in the array.
        4. Do not have extra spaces or line breaks.
        5. Each directory title has practical significance.
        請按照以下要求提供本教程的具體目錄:
        1. 輸出必須嚴格符合指定語言,{language}
        2. 回答必須嚴格按照字典格式,如{{"title": "xxx", "directory": [{{"dir 1": ["sub dir 1", "sub dir 2"]}}, {{"dir 2": ["sub dir 3", "sub dir 4"]}}]}}
        3. 目錄應盡可能具體和充分,包括一級和二級目錄。二級目錄在數組中。
        4. 不要有額外的空格或換行符。
        5. 每個目錄標題都具有實際意義。
        """
        prompt = DIRECTORY_PROMPT.format(topic=topic, language=self.language)
        resp = await self._aask(prompt=prompt)
        return extract_struct(resp, dict)

測試一下:

text = 'xxx [1, 2, ["a", "b", [3, 4]], {"x": 5, "y": [6, 7]}] xxx'
result_list = extract_struct(text, list)
print(result_list)
# Output: [1, 2, ["a", "b", [3, 4]], {"x": 5, "y": [6, 7]}]

text = 'xxx {"x": 1, "y": {"a": 2, "b": {"c": 3}}} xxx'
result_dict = extract_struct(text, dict)
print(result_dict)
# Output: {"x": 1, "y": {"a": 2, "b": {"c": 3}}}
from metagpt.const import TUTORIAL_PATH
TUTORIAL_PATH
# WindowsPath('d:/Code/agent_from_scratch/data/tutorial_docx')
from datetime import datetime
from typing import Dict
import asyncio
from metagpt.actions.write_tutorial import WriteDirectory, WriteContent
from metagpt.const import TUTORIAL_PATH
from metagpt.logs import logger
from metagpt.roles.role import Role, RoleReactMode
from metagpt.schema import Message
from metagpt.utils.file import File

from typing import Dict

from metagpt.actions import Action

class WriteDirectory(Action):
    """Action class for writing tutorial directories.

    Args:
        name: The name of the action.
        language: The language to output, default is "Chinese".
    """

    name: str = "WriteDirectory"
    language: str = "Chinese"

    async def run(self, topic: str, *args, **kwargs) -> Dict:
        """Execute the action to generate a tutorial directory according to the topic.

        Args:
            topic: The tutorial topic.

        Returns:
            the tutorial directory information, including {"title": "xxx", "directory": [{"dir 1": ["sub dir 1", "sub dir 2"]}]}.
        """
        COMMON_PROMPT = """
        You are now a seasoned technical professional in the field of the internet. 
        We need you to write a technical tutorial with the topic "{topic}".
        """

        DIRECTORY_PROMPT = COMMON_PROMPT + """
        Please provide the specific table of contents for this tutorial, strictly following the following requirements:
        1. The output must be strictly in the specified language, {language}.
        2. Answer strictly in the dictionary format like {{"title": "xxx", "directory": [{{"dir 1": ["sub dir 1", "sub dir 2"]}}, {{"dir 2": ["sub dir 3", "sub dir 4"]}}]}}.
        3. The directory should be as specific and sufficient as possible, with a primary and secondary directory.The secondary directory is in the array.
        4. Do not have extra spaces or line breaks.
        5. Each directory title has practical significance.
        """
        prompt = DIRECTORY_PROMPT.format(topic=topic, language=self.language)
        resp = await self._aask(prompt=prompt)
        return extract_struct(resp, dict)

class WriteContent(Action):
    """Action class for writing tutorial content.

    Args:
        name: The name of the action.
        directory: The content to write.
        language: The language to output, default is "Chinese".
    """

    name: str = "WriteContent"
    directory: dict = dict()
    language: str = "Chinese"

    async def run(self, topic: str, *args, **kwargs) -> str:
        """Execute the action to write document content according to the directory and topic.

        Args:
            topic: The tutorial topic.

        Returns:
            The written tutorial content.
        """
        COMMON_PROMPT = """
        You are now a seasoned technical professional in the field of the internet. 
        We need you to write a technical tutorial with the topic "{topic}".
        """
        CONTENT_PROMPT = COMMON_PROMPT + """
        Now I will give you the module directory titles for the topic. 
        Please output the detailed principle content of this title in detail. 
        If there are code examples, please provide them according to standard code specifications. 
        Without a code example, it is not necessary.

        The module directory titles for the topic is as follows:
        {directory}

        Strictly limit output according to the following requirements:
        1. Follow the Markdown syntax format for layout.
        2. If there are code examples, they must follow standard syntax specifications, have document annotations, and be displayed in code blocks.
        3. The output must be strictly in the specified language, {language}.
        4. Do not have redundant output, including concluding remarks.
        5. Strict requirement not to output the topic "{topic}".
        """
        prompt = CONTENT_PROMPT.format(
            topic=topic, language=self.language, directory=self.directory)
        return await self._aask(prompt=prompt)

class TutorialAssistant(Role):
    """Tutorial assistant, input one sentence to generate a tutorial document in markup format.

    Args:
        name: The name of the role.
        profile: The role profile description.
        goal: The goal of the role.
        constraints: Constraints or requirements for the role.
        language: The language in which the tutorial documents will be generated.
    """

    name: str = "Stitch"
    profile: str = "Tutorial Assistant"
    goal: str = "Generate tutorial documents"
    constraints: str = "Strictly follow Markdown's syntax, with neat and standardized layout"
    language: str = "Chinese"

    topic: str = ""
    main_title: str = ""
    total_content: str = ""

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.set_actions([WriteDirectory(language=self.language)])
        self._set_react_mode(react_mode=RoleReactMode.REACT.value)

    async def _think(self) -> None:
        """Determine the next action to be taken by the role."""
        logger.info(self.rc.state)
        logger.info(self,)
        if self.rc.todo is None:
            self._set_state(0)
            return

        if self.rc.state + 1 < len(self.states):
            self._set_state(self.rc.state + 1)
        else:
            self.rc.todo = None

    async def _handle_directory(self, titles: Dict) -> Message:
        """Handle the directories for the tutorial document.

        Args:
            titles: A dictionary containing the titles and directory structure,
                    such as {"title": "xxx", "directory": [{"dir 1": ["sub dir 1", "sub dir 2"]}]}

        Returns:
            A message containing information about the directory.
        """
        self.main_title = titles.get("title")
        directory = f"{self.main_title}\n"
        self.total_content += f"# {self.main_title}"
        actions = list()
        for first_dir in titles.get("directory"):
            actions.append(WriteContent(
                language=self.language, directory=first_dir))
            key = list(first_dir.keys())[0]
            directory += f"- {key}\n"
            for second_dir in first_dir[key]:
                directory += f"  - {second_dir}\n"
        self.set_actions(actions)
        self.rc.todo = None
        return Message(content=directory)

    async def _act(self) -> Message:
        """Perform an action as determined by the role.

        Returns:
            A message containing the result of the action.
        """
        todo = self.rc.todo
        if type(todo) is WriteDirectory:
            msg = self.rc.memory.get(k=1)[0]
            self.topic = msg.content
            resp = await todo.run(topic=self.topic)
            logger.info(resp)
            return await self._handle_directory(resp)
        resp = await todo.run(topic=self.topic)
        logger.info(resp)
        if self.total_content != "":
            self.total_content += "\n\n\n"
        self.total_content += resp
        return Message(content=resp, role=self.profile)

    async def _react(self) -> Message:
        """Execute the assistant's think and actions.

        Returns:
            A message containing the final result of the assistant's actions.
        """
        while True:
            await self._think()
            if self.rc.todo is None:
                break
            msg = await self._act()
        root_path = TUTORIAL_PATH / datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
        await File.write(root_path, f"{self.main_title}.md", self.total_content.encode('utf-8'))
        return msg
async def main():
    msg = "AI Agent開發教程"
    role = TutorialAssistant()
    logger.info(msg)
    result = await role.run(msg)
    logger.info(result)
    return role.total_content

rtn = await main()

print(rtn)輸出生成的 AI Agent 開發教程

結果#

太長了僅展示部分。
image


訂閱智能體標題#

輸入pip show metagpt查看版本
類似:

Version: 0.8.0
Summary: The Multi-Agent Framework
Home-page: https://github.com/geekan/MetaGPT
Author: Alexander Wu
Author-email: [email protected]
License: MIT
Location: c:\users\liwei\appdata\roaming\python\python39\site-packages
Requires: aiofiles, aiohttp, aioredis, anthropic, anytree, beautifulsoup4, channels, dashscope, faiss-cpu, fire, gitignore-parser, gitpython, google-generativeai, imap-tools, ipykernel, ipython, ipywidgets, jieba, lancedb, libcst, loguru, meilisearch, nbclient, nbformat, networkx, numpy, openai, openpyxl, pandas, Pillow, playwright, pydantic, python-docx, PyYAML, qdrant-client, qianfan, rank-bm25, rich, scikit-learn, semantic-kernel, setuptools, socksio, ta, tenacity, tiktoken, tqdm, typer, typing-extensions, typing-inspect, websocket-client, websockets, wrapt, zhipuai

我們先來完成網頁爬取的功能,我們教程直接爬取當天不分國家語言和編程語言的熱門倉庫進行分析,如果有特殊要求,爬取加上篩選條件條件後網頁即可。我們先打開https://github.com/trending 網頁,觀察網頁內容,找到我們需要的內容對應的 html 元素,。

如果熟悉爬蟲的就可以直接寫爬取和解析腳本了,如果不熟悉的也沒關係,我們可以 用 ChatGPT 輔助開發: 首先我們將 trending 頁面保存到 本地 github-trending-raw.html
格式化後發現內容非常多,大概 600 多 k,還有一些 svg 源碼,因為一般用 CSS 足以定位 html 裡的元素,所以我們可以對 html 內容進行瘦身,可以使用以下的腳本:

from bs4 import BeautifulSoup

with open("github-trending-raw.html") as f:
    html = f.read()

soup = BeautifulSoup(html, "html.parser")
for i in soup.find_all(True):
    for name in list(i.attrs):
        if i[name] and name not in ["class"]:
            del i[name]

for i in soup.find_all(["svg", "img", "video", "audio"]):
    i.decompose()

with open("github-trending-slim.html", "w") as f:
    f.write(str(soup))

經過以上的腳本處理之後,大概還有 100 多 k。對於爬蟲來說,重要的是 Html 的結構,處理後的 Html 文件其實有大量的信息是重複的,如果我們要讓 GPT 協助我們寫爬蟲腳本,只需要截取部分信息就可以了。
接下來解析一下 html 文件

import aiohttp
import asyncio
from bs4 import BeautifulSoup

def fetch_html(url):
    with open(url, encoding="utf-8") as f:
        html = f.read()
    return html

async def parse_github_trending(html):
    soup = BeautifulSoup(html, 'html.parser')

    repositories = []

    for article in soup.select('article.Box-row'):
        repo_info = {}
        
        repo_info['name'] = article.select_one('h2 a').text.strip()
        repo_info['url'] = article.select_one('h2 a')['href'].strip()

        # Description
        description_element = article.select_one('p')
        repo_info['description'] = description_element.text.strip() if description_element else None

        # Language
        language_element = article.select_one('span[itemprop="programmingLanguage"]')
        repo_info['language'] = language_element.text.strip() if language_element else None

        # Stars and Forks
        stars_element = article.select('a.Link--muted')[0]
        forks_element = article.select('a.Link--muted')[1]
        repo_info['stars'] = stars_element.text.strip()
        repo_info['forks'] = forks_element.text.strip()

        # Today's Stars
        today_stars_element = article.select_one('span.d-inline-block.float-sm-right')
        repo_info['today_stars'] = today_stars_element.text.strip() if today_stars_element else None

        repositories.append(repo_info)

    return repositories

async def main():
    url = 'github-trending-raw.html'
    html = fetch_html(url)
    repositories = await parse_github_trending(html)

    for repo in repositories:
        print(f"Name: {repo['name']}")
        print(f"URL: https://github.com{repo['url']}")
        print(f"Description: {repo['description']}")
        print(f"Language: {repo['language']}")
        print(f"Stars: {repo['stars']}")
        print(f"Forks: {repo['forks']}")
        print(f"Today's Stars: {repo['today_stars']}")
        print()
        
await main()

上面這些代碼的作用是解析一下 github-trending 的數據。由於 github-trending 網站打開比較慢。我們可以先把 github 的 trending 頁面保存到本地,再進行解讀。

繼續導入其他相關的庫#


import os
from typing import Any, AsyncGenerator, Awaitable, Callable, Dict, Optional
from aiocron import crontab
from pydantic import BaseModel, Field
from pytz import BaseTzInfo

from metagpt.actions.action import Action
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message

# fix SubscriptionRunner not fully defined
from metagpt.environment import Environment as _  # noqa: F401

訂閱模塊#

可以 from metagpt.subscription import SubscriptionRunner 導入,這裡貼上代碼供參考


class SubscriptionRunner(BaseModel):
    """A simple wrapper to manage subscription tasks for different roles using asyncio.
    Example:
        >>> import asyncio
        >>> from metagpt.subscription import SubscriptionRunner
        >>> from metagpt.roles import Searcher
        >>> from metagpt.schema import Message
        >>> async def trigger():
        ...     while True:
        ...         yield Message("the latest news about OpenAI")
        ...         await asyncio.sleep(3600 * 24)
        >>> async def callback(msg: Message):
        ...     print(msg.content)
        >>> async def main():
        ...     pb = SubscriptionRunner()
        ...     await pb.subscribe(Searcher(), trigger(), callback)
        ...     await pb.run()
        >>> asyncio.run(main())
    """

    tasks: Dict[Role, asyncio.Task] = Field(default_factory=dict)

    class Config:
        arbitrary_types_allowed = True

    async def subscribe(
        self,
        role: Role,
        trigger: AsyncGenerator[Message, None],
        callback: Callable[
            [
                Message,
            ],
            Awaitable[None],
        ],
    ):
        """Subscribes a role to a trigger and sets up a callback to be called with the role's response.
        Args:
            role: The role to subscribe.
            trigger: An asynchronous generator that yields Messages to be processed by the role.
            callback: An asynchronous function to be called with the response from the role.
        """
        loop = asyncio.get_running_loop()

        async def _start_role():
            async for msg in trigger:
                resp = await role.run(msg)
                await callback(resp)

        self.tasks[role] = loop.create_task(_start_role(), name=f"Subscription-{role}")

    async def unsubscribe(self, role: Role):
        """Unsubscribes a role from its trigger and cancels the associated task.
        Args:
            role: The role to unsubscribe.
        """
        task = self.tasks.pop(role)
        task.cancel()

    async def run(self, raise_exception: bool = True):
        """Runs all subscribed tasks and handles their completion or exception.
        Args:
            raise_exception: _description_. Defaults to True.
        Raises:
            task.exception: _description_
        """
        i=0
        while True:
            i+=1
            for role, task in self.tasks.items():
                i=0
                if task.done():
                    if task.exception():
                        if raise_exception:
                            raise task.exception()
                        logger.opt(exception=task.exception()).error(
                            f"Task {task.get_name()} run error"
                        )
                    else:
                        logger.warning(
                            f"Task {task.get_name()} has completed. "
                            "If this is unexpected behavior, please check the trigger function."
                        )
                    self.tasks.pop(role)
                    break
            else:
                await asyncio.sleep(1)
            if i>0:
                break

Actions 的實現#


TRENDING_ANALYSIS_PROMPT = """# Requirements
You are a GitHub Trending Analyst, aiming to provide users with insightful and personalized recommendations based on the latest
GitHub Trends. Based on the context, fill in the following missing information, generate engaging and informative titles, 
ensuring users discover repositories aligned with their interests.

# The title about Today's GitHub Trending
## Today's Trends: Uncover the Hottest GitHub Projects Today! Explore the trending programming languages and discover key domains capturing developers' attention. From ** to **, witness the top projects like never before.
## The Trends Categories: Dive into Today's GitHub Trending Domains! Explore featured projects in domains such as ** and **. Get a quick overview of each project, including programming languages, stars, and more.
## Highlights of the List: Spotlight noteworthy projects on GitHub Trending, including new tools, innovative projects, and rapidly gaining popularity, focusing on delivering distinctive and attention-grabbing content for users.
---
# Format Example


# [Title]

## Today's Trends
Today, ** and ** continue to dominate as the most popular programming languages. Key areas of interest include **, ** and **.
The top popular projects are Project1 and Project2.

## The Trends Categories
1. Generative AI
    - [Project1](https://github/xx/project1): [detail of the project, such as star total and today, language, ...]
    - [Project2](https://github/xx/project2): ...
...

## Highlights of the List
1. [Project1](https://github/xx/project1): [provide specific reasons why this project is recommended].
...

---
# Github Trending
{trending}
"""


class CrawlOSSTrending(Action):
    async def run(self, url: str = "https://github.com/trending"):
        async with aiohttp.ClientSession() as client:
            async with client.get(url, proxy=CONFIG.global_proxy) as response:
                response.raise_for_status()
                html = await response.text()

        soup = BeautifulSoup(html, "html.parser")

        repositories = []

        for article in soup.select("article.Box-row"):
            repo_info = {}

            repo_info["name"] = (
                article.select_one("h2 a")
                .text.strip()
                .replace("\n", "")
                .replace(" ", "")
            )
            repo_info["url"] = (
                "https://github.com" + article.select_one("h2 a")["href"].strip()
            )

            # Description
            description_element = article.select_one("p")
            repo_info["description"] = (
                description_element.text.strip() if description_element else None
            )

            # Language
            language_element = article.select_one(
                'span[itemprop="programmingLanguage"]'
            )
            repo_info["language"] = (
                language_element.text.strip() if language_element else None
            )

            # Stars and Forks
            stars_element = article.select("a.Link--muted")[0]
            forks_element = article.select("a.Link--muted")[1]
            repo_info["stars"] = stars_element.text.strip()
            repo_info["forks"] = forks_element.text.strip()

            # Today's Stars
            today_stars_element = article.select_one(
                "span.d-inline-block.float-sm-right"
            )
            repo_info["today_stars"] = (
                today_stars_element.text.strip() if today_stars_element else None
            )

            repositories.append(repo_info)

        return repositories


class AnalysisOSSTrending(Action):
    async def run(self, trending: Any):
        return await self._aask(TRENDING_ANALYSIS_PROMPT.format(trending=trending))


Role 實現#

# Role實現
# 對於V0.7 以上的版本,需要把老版本的
# self._init_actions 改為self.set_actions
class OssWatcher(Role):
    def __init__(
        self,
        name="Codey",
        profile="OssWatcher",
        goal="Generate an insightful GitHub Trending analysis report.",
        constraints="Only analyze based on the provided GitHub Trending data.",
    ):
        super().__init__(name=name, profile=profile, goal=goal, constraints=constraints)
        self.set_actions([CrawlOSSTrending, AnalysisOSSTrending])
        self._set_react_mode(react_mode="by_order")

    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
        result = await todo.run(msg.content)

        msg = Message(content=str(result), role=self.profile, cause_by=type(todo))
        self.rc.memory.add(msg)
        return msg

async def wxpusher_callback(msg: Message):
    print(msg.content)


async def trigger():
    # 這裡設置了只觸發五次,也可以用while True 永遠執行下去
    for i in range(5):
        yield Message("the latest news about OpenAI")
        await asyncio.sleep(5)
        #  每隔五秒鐘執行一次。
        # 也可以設置為每隔3600 * 24 秒執行一次
    

運行入口#

# 運行入口,
async def main():
    callbacks = []
    if not callbacks:
        async def _print(msg: Message):
            print(msg.content)
        callbacks.append(_print)

    # callback
    async def callback(msg):
        await asyncio.gather(*(call(msg) for call in callbacks))

    runner = SubscriptionRunner()
    await runner.subscribe(OssWatcher(), trigger(), callback)
    await runner.run()
await main()

It seems like you've provided a list of GitHub repositories related to various topics. However, there's no clear indication whether these are open-source projects or not. If they're open-source projects, you can use the following Python code to check their star count:

import requests

def get_repo_info(repo_name):
    url = f"https://github.com/search?q=stars:{repo_name}&type=Repositories"
    response = requests.get(url)
    if response.status_code == 200:
        repos = []
        for repo in response.json():
            if "name" in repo and "url" in repo:
                name = repo["name"]
                url = repo["url"]
                stars = int(repo["stargazers_count"])
                forks = int(repo["forks_count"])  # Assuming the number of forks is available
                repos.append({"name": name, "url": url, "stars": stars, "forks": forks})
        return repos
    else:
        print("Failed to fetch data. Status code:", response.status_code)

repos = get_repo_info('language')
for repo in repos:
    if 'language' not in repo or repo['language'] == None:
        del repo['language']
print(repos)

This script will fetch the repositories with a specific name (in this case, "language") and print out their star count. If there's no specific language mentioned, it'll assume all languages are open-source.

Please note that GitHub API might not always return accurate data due to various reasons like missing or incorrect information in the repository details. Also, some repositories may have a different number of stars depending on when they were last updated.

It seems like you've provided a list of GitHub repositories related to various topics. However, there's no clear indication whether these are open-source projects or not. If they're open-source projects, you can use the following Python code to check their star count:

import requests

def get_repo_info(repo_name):
    url = f"https://github.com/search?q=stars:{repo_name}&type=Repositories"
    response = requests.get(url)
    if response.status_code == 200:
        repos = []
        for repo in response.json():
            if "name" in repo and "url" in repo:
                name = repo["name"]
                url = repo["url"]
                stars = int(repo["stargazers_count"])
                forks = int(repo["forks_count"])  # Assuming the number of forks is available
                repos.append({"name": name, "url": url, "stars": stars, "forks": forks})
        return repos
    else:
        print("Failed to fetch data. Status code:", response.status_code)

repos = get_repo_info('language')
for repo in repos:
    if 'language' not in repo or repo['language'] == None:
        del repo['language']
print(repos)

This script will fetch the repositories with a specific name (in this case, "language") and print out their star count. If there's no specific language mentioned, it'll assume all languages are open-source.

Please note that GitHub API might not always return accurate data due to various reasons like missing or incorrect information in the repository details. Also, some repositories may have a different number of stars depending on when they were last updated.

由于我設置的是每 5 秒鐘觸發一次,執行 5 次後停止。可以根據自己的需求設置執行頻率。

我不能運行,關於代理的問題除了錯誤。

單動作多智能體#

MetaGPT 的核心優勢也在於輕鬆靈活地開發一個智能體團隊。

我們需要三個步驟來建立團隊並使其運作:

定義每個角色能夠執行的預期動作

基於標準作業程序(SOP)確保每個角色遵守它。通過使每個角色觀察上游的相應輸出結果,並為下游發布自己的輸出結果,可以實現這一點。

初始化所有角色,創建一個帶有環境的智能體團隊,並使它們之間能夠進行交互。

內容來自於:
https://docs.deepwisdom.ai/v0.8/zh/guide/tutorials/multi_agent_101.html

定義動作#

我們可以定義三個具有各動作的 Role:

SimpleCoder 具有 SimpleWriteCode 動作,接收用戶的指令並編寫主要代碼

SimpleTester 具有 SimpleWriteTest 動作,從 SimpleWriteCode 的輸出中獲取主代碼並為其提供測試套件

SimpleReviewer 具有 SimpleWriteReview 動作,審查來自 SimpleWriteTest 輸出的測試用例,並檢查其覆蓋範圍和質量

import re
from metagpt.actions import Action, UserRequirement

# 構造寫代碼的動作

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: str = """
    Write a python function that can {instruction}.
    Return ```python your_code_here ```with NO other texts,
    your code:
    """
    name: str = "SimpleWriteCode"

    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 SimpleWriteTest(Action):
    PROMPT_TEMPLATE: str = """
    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:
    """

    name: str = "SimpleWriteTest"

    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 SimpleWriteReview(Action):
    PROMPT_TEMPLATE: str = """
    Context: {context}
    Review the test cases and provide one critical comments:
    """

    name: str = "SimpleWriteReview"

    async def run(self, context: str):
        prompt = self.PROMPT_TEMPLATE.format(context=context)

        rsp = await self._aask(prompt)

        return rsp
                

定義角色#

在許多多智能體場景中,定義 Role 可能只需幾行代碼。對於 SimpleCoder,我們做了兩件事:

  1. 使用 set_actions 為 Role 配備適當的 Action,這與設置單智能體相同

  2. 多智能體操作邏輯:我們使 Role _watch 來自用戶或其他智能體的重要上游消息。回想我們的 SOP,SimpleCoder 接收用戶指令,這是由 MetaGPT 中的 UserRequirement 引起的 Message。因此,我們添加了 self._watch ([UserRequirement])。

class SimpleCoder(Role):
    name: str = "Alice"
    profile: str = "SimpleCoder"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._watch([UserRequirement])
        self.set_actions([SimpleWriteCode])

與上述相似,對於 SimpleTester,我們:

  1. 使用 set_actions 為 SimpleTester 配備 SimpleWriteTest 動作

  2. 使 Role _watch 來自其他智能體的重要上游消息。回想我們的 SOP,SimpleTester 從 SimpleCoder 中獲取主代碼,這是由 SimpleWriteCode 引起的 Message。因此,我們添加了 self._watch ([SimpleWriteCode])。

  3. 重寫 _act 函數,就像我們在智能體入門中的單智能體設置中所做的那樣。在這裡,我們希望 SimpleTester 將所有記憶用作編寫測試用例的上下文,並希望有 5 個測試用例。

class SimpleTester(Role):
    name: str = "Bob"
    profile: str = "SimpleTester"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.set_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}: to do {self.rc.todo}({self.rc.todo.name})")
        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
# 按照相同的過程定義 SimpleReviewer:
class SimpleReviewer(Role):
    name: str = "Charlie"
    profile: str = "SimpleReviewer"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.set_actions([SimpleWriteReview])
        self._watch([SimpleWriteTest])

創建一個團隊並添加角色

現在我們已經定義了三個 Role,是時候將它們放在一起了。我們初始化所有角色,設置一個 Team,並 hire 它們。

運行 Team,我們應該會看到它們之間的協作!

import asyncio
from metagpt.team import Team

async def main(
    idea: str = "write a function that calculates the product of a list",
    investment: float = 3.0,
    n_round: int = 5,
):
    logger.info(idea)

    team = Team()
    team.hire(
        [
            SimpleCoder(),
            SimpleTester(),
            SimpleReviewer(),
        ]
    )

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

結果#

2025-01-26 19:37:59.576 | INFO | main:main:9 - write a function that calculates the product of a list
2025-01-26 19:38:00.116 | INFO | metagpt.team:invest:90 - Investment: $3.0.
2025-01-26 19:38:00.119 | INFO | metagpt.roles.role:_act:391 - Alice(SimpleCoder): to do SimpleWriteCode(SimpleWriteCode)

def product_of_list(lst):
    result = 1
    for num in lst:
        result *= num
    return result

2025-01-26 19:38:03.794 | WARNING | metagpt.utils.cost_manager:update_cost:49 - Model glm-4-flash not found in TOKEN_COSTS.
2025-01-26 19:38:03.798 | INFO | main:_act:15 - Bob(SimpleTester): to do SimpleWriteTest(SimpleWriteTest)

def test_product_of_list_empty():
    assert product_of_list([]) == 1

def test_product_of_list_single_element():
    assert product_of_list([5]) == 5

def test_product_of_list_positive_numbers():
    assert product_of_list([1, 2, 3, 4]) == 24

def test_product_of_list_negative_numbers():
    assert product_of_list([-1, -2, -3, -4]) == -24

def test_product_of_list_mixed_numbers():
    assert product_of_list([-1, 2, -3, 4]) ==
2025-01-26 19:38:09.372 | WARNING  | metagpt.utils.cost_manager:update_cost:49 - Model glm-4-flash not found in TOKEN_COSTS.
2025-01-26 19:38:09.376 | INFO     | metagpt.roles.role:_act:391 - Charlie(SimpleReviewer): to do SimpleWriteReview(SimpleWriteReview)
 24

Critical Comment:
While the test cases cover a range of scenarios, including empty lists, single elements, positive numbers, negative numbers, and mixed numbers, there is a potential oversight in the test suite. The function product_of_list does not handle the case where the list contains zero. Since multiplying any number by zero results in zero, it would be beneficial to include a test case that verifies the function's behavior when zero is present in the list. This would ensure that the function is robust and can handle all possible integer values within the
2025-01-26 19:38:14.253 | WARNING | metagpt.utils.cost_manager:update_cost:49 - Model glm-4-flash not found in TOKEN_COSTS.

單動作多智能體實例:辯論#

這是一個展示如何設計多個智能體並促進它們之間互動的例子,我們將模擬代表拜登和特朗普的智能體共同合作會怎樣,這樣的組合可能會導致一些生動的交流,我們將這個實驗稱為 “拜登 - 特朗普辯論”。

實例代碼詳見:

https://github.com/geekan/MetaGPT/blob/main/examples/debate.py

辯論分三個步驟設定:

  1. 定義一個具有發言行為的辯手角色,詳見前期課程
  2. 處理辯手之間的通信,也就是讓拜登聽特朗普說話,反之亦然
  3. 初始化兩個辯手實例,拜登和特朗普,創建一個帶有環境的團隊,並使它們能夠相互交互

插入模塊

import asyncio
import platform
from typing import Any

import fire

from metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.team import Team
import asyncio
from metagpt.actions import Action,UserRequirement
from metagpt.schema import Message

定義動作

首先,我們需要定義一個 Action。這是一個辯論場景,所以讓我們將其命名為 SpeakAloud

class SpeakAloud(Action):
    """動作:在辯論中大聲說話(爭吵)"""

    PROMPT_TEMPLATE: str = """
    ## BACKGROUND
    Suppose you are {name}, you are in a debate with {opponent_name}.
    ## DEBATE HISTORY
    Previous rounds:
    {context}
    ## YOUR TURN
    Now it's your turn, you should closely respond to your opponent's latest argument, state your position, defend your arguments, and attack your opponent's arguments,
    craft a strong and emotional response in 80 words, in {name}'s rhetoric and viewpoints, your will argue:
    """

    def __init__(self, name="SpeakAloud", context=None, llm=None):
        super().__init__(name, context, llm)

    async def run(self, context: str, name: str, opponent_name: str):

        prompt = self.PROMPT_TEMPLATE.format(context=context, name=name, opponent_name=opponent_name)

        rsp = await self._aask(prompt)

        return rsp

定義角色

定義一個通用的 Role,稱為 Debator

set_actions 使我們的 Role 擁有我們剛剛定義的 SpeakAloud 動作。我們還使用_watch 監視了 SpeakAloud 和 UserRequierment,因為我們希望每個辯手關注來自對手的 SpeakAloud 消息,以及來自用戶的 UserRequirement (人類指令)。

class Debator(Role):
    def __init__(
        self,
        name: str,
        profile: str,
        opponent_name: str,
        **kwargs,
    ):
        super().__init__(name, profile, **kwargs)
        self.set_actions([SpeakAloud])
        self._watch([UserRequirement, SpeakAloud])
        self.name = name
        self.opponent_name = opponent_name

下面通過重寫_observe 函數,我們使每個辯手聽取對手論點。這一步很重要,因為在環境中將會有來自特朗普和拜登的 "SpeakAloud 消息"(由 SpeakAloud 觸發的 Message)。 我們不希望特朗普處理自己上一輪的 "SpeakAloud 消息",而是處理來自拜登的消息,反之亦然。

async def _observe(self) -> int:
        await super()._observe()
        # accept 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.send_to == self.name]
        return len(self.rc.news)

最後,我們使每個辯手能夠向對手發送反駁的論點。在這裡,我們從消息歷史中構建一個上下文,使 Debator 運行他擁有的 SpeakAloud 動作,並使用反駁論點內容創建一個新的 Message。請注意,我們定義每個 Debator 將把 Message 發送給他的對手

async def _act(self) -> Message:
    logger.info(f"{self._setting}: ready to {self.rc.todo}")
    todo = self.rc.todo # 一個 SpeakAloud 的實例

    memories = self.get_memories()
    context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories)

    rsp = await todo.run(context=context, name=self.name, opponent_name=self.opponent_name)

    msg = Message(
        content=rsp,
        role=self.profile,
        cause_by=todo,
        sent_from=self.name,
        send_to=self.opponent_name,
    )
    self.rc.memory.add(msg)
    return msg

創建團隊並添加角色

建立一個 Team 將角色動作組合起來,我們將通過將我們的指令(作為 UserRequirement)發送給拜登,讓他先開始。如果你想讓特朗普先說話,將 send_to 設置為 "Trump"。

運行 Team 就可以看到他們之間的對話!

async def debate(idea: str, investment: float = 3.0, n_round: int = 5):
    """運行拜登-特朗普辯論,觀看他們之間的友好對話 :) """
    Biden = Debator(name="Biden", profile="Democrat", opponent_name="Trump")
    Trump = Debator(name="Trump", profile="Republican", opponent_name="Biden")
    team = Team()
    team.hire([Biden, Trump])
    team.invest(investment)
    team.run_project(idea, send_to="Biden")  # 將辯論主題發送給拜登,讓他先說話
    await team.run(n_round=n_round)


import typer
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"
    :param investment: contribute a certain dollar amount to watch the debate
    :param n_round: maximum rounds of the debate
    :return:
    """
    if platform.system() == "Windows":
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    asyncio.run(debate(idea, investment, n_round))

if __name__ == '__main__':
    app()# run as python debate.py --idea="TOPIC" --investment=3.0 --n_round=5

但是,上述教程代碼運行不了。運行以下代碼:

import asyncio
import platform
from typing import Any

import fire

from metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.team import Team
import asyncio
from metagpt.actions import Action,UserRequirement
from metagpt.schema import Message
class SpeakAloud(Action):
    """動作:在辯論中大聲說話(爭吵)"""

    PROMPT_TEMPLATE: str = """
    ## BACKGROUND
    Suppose you are {name}, you are in a debate with {opponent_name}.
    ## DEBATE HISTORY
    Previous rounds:
    {context}
    ## YOUR TURN
    Now it's your turn, you should closely respond to your opponent's latest argument, state your position, defend your arguments, and attack your opponent's arguments,
    craft a strong and emotional response in 80 words, in {name}'s rhetoric and viewpoints, your will argue:
    """

    def __init__(self, name="SpeakAloud", context=None, llm=None):
        super().__init__(name, context, llm)

    async def run(self, context: str, name: str, opponent_name: str):

        prompt = self.PROMPT_TEMPLATE.format(context=context, name=name, opponent_name=opponent_name)

        rsp = await self._aask(prompt)

        return rsp
    
class Debator(Role):
    def __init__(
        self,
        **kwargs,
    ):
        super().__init__(**kwargs)
        self.set_actions([SpeakAloud])
        self._watch([UserRequirement, SpeakAloud])


    async def _observe(self) -> int:
        await super()._observe()
        # accept messages sent (from opponent) to self, disregard own messages from the last round
        self.rc.news = [msg for msg in self.rc.news if self.name in msg.send_to]
        return len(self.rc.news)

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: ready to {self.rc.todo}")
        todo = self.rc.todo # 一個 SpeakAloud 的實例

        memories = self.get_memories()
        context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories)

        rsp = await todo.run(context=context, name=self.name, opponent_name=self.opponent_name)

        msg = Message(
            content=rsp,
            role=self.profile,
            cause_by=todo,
            sent_from=self.name,
            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):
    """運行拜登-特朗普辯論,觀看他們之間的友好對話 :) """
    Biden = Debator(name="Biden", profile="Democrat", opponent_name="Trump")
    Trump = Debator(name="Trump", profile="Republican", opponent_name="Biden")
    team = Team()
    team.hire([Biden, Trump])
    team.invest(investment)
    team.run_project(idea, send_to="Biden")  # 將辯論主題發送給拜登,讓他先說話
    await team.run(n_round=n_round)


import asyncio
import platform
import typer
from metagpt.team import Team
app = typer.Typer()

@app.command()
def main(
    investment: float = 3.0,
    n_round: int = 5,//運行前建議將此處n_round修改小一點,否則對錢包不友好!!!
    shuffle: bool = True,
    add_human: bool = False,
    use_reflection: bool = True,
    use_experience: bool = False,
    use_memory_selection: bool = False,
    new_experience_version: str = "",
):
    asyncio.run(
        start_game(
            investment,
            n_round,
            shuffle,
            add_human,
            use_reflection,
            use_experience,
            use_memory_selection,
            new_experience_version,
        )
    )


if __name__ == "__main__":
    fire.Fire(main)

但是,上述教程代碼運行不了。運行以下代碼:

import asyncio
import platform
from typing import Any

import fire

from metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.team import Team
import asyncio
from metagpt.actions import Action,UserRequirement
from metagpt.schema import Message
class SpeakAloud(Action):
    """動作:在辯論中大聲說話(爭吵)"""

    PROMPT_TEMPLATE: str = """
    ## BACKGROUND
    Suppose you are {name}, you are in a debate with {opponent_name}.
    ## DEBATE HISTORY
    Previous rounds:
    {context}
    ## YOUR TURN
    Now it's your turn, you should closely respond to your opponent's latest argument, state your position, defend your arguments, and attack your opponent's arguments,
    craft a strong and emotional response in 80 words, in {name}'s rhetoric and viewpoints, your will argue:
    """

    def __init__(self, name="SpeakAloud", context=None, llm=None):
        super().__init__(name=name, context=context, llm=llm)

    async def run(self, context: str, name: str, opponent_name: str):

        prompt = self.PROMPT_TEMPLATE.format(context=context, name=name, opponent_name=opponent_name)

        rsp = await self._aask(prompt)

        return rsp
    
class Debator(Role):
    def __init__(
        self,
        **kwargs,
    ):
        super().__init__(**kwargs)
        self.set_actions([SpeakAloud])
        self._watch([UserRequirement, SpeakAloud])


    async def _observe(self) -> int:
        await super()._observe()
        # accept messages sent (from opponent) to self, disregard own messages from the last round
        self.rc.news = [msg for msg in self.rc.news if self.name in msg.send_to]
        return len(self.rc.news)

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: ready to {self.rc.todo}")
        todo = self.rc.todo # 一個 SpeakAloud 的實例

        memories = self.get_memories()
        context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories)

        rsp = await todo.run(context=context, name=self.name, opponent_name=self.opponent_name)

        msg = Message(
            content=rsp,
            role=self.profile,
            cause_by=todo,
            sent_from=self.name,
            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):
    """運行拜登-特朗普辯論,觀看他們之間的友好對話 :) """
    Biden = Debator(name="Biden", profile="Democrat", opponent_name="Trump")
    Trump = Debator(name="Trump", profile="Republican", opponent_name="Biden")
    team = Team()
    team.hire([Biden, Trump])
    team.invest(investment)
    team.run_project(idea, send_to="Biden")  # 將辯論主題發送給拜登,讓他先說話
    await team.run(n_round=n_round)


import asyncio
import platform
import typer
from metagpt.team import Team
app = typer.Typer()

@app.command()
def main(
    investment: float = 3.0,
    n_round: int = 5,//運行前建議將此處n_round修改小一點,否則對錢包不友好!!!
    shuffle: bool = True,
    add_human: bool = False,
    use_reflection: bool = True,
    use_experience: bool = False,
    use_memory_selection: bool = False,
    new_experience_version: str = "",
):
    asyncio.run(
        start_game(
            investment,
            n_round,
            shuffle,
            add_human,
            use_reflection,
            use_experience,
            use_memory_selection,
            new_experience_version,
        )
    )


if __name__ == "__main__":
    fire.Fire(main)

但是,上述教程代碼運行不了。運行以下代碼:

import asyncio
import platform
from typing import Any

import fire

from metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.team import Team
import asyncio
from metagpt.actions import Action,UserRequirement
from metagpt.schema import Message
class SpeakAloud(Action):
    """動作:在辯論中大聲說話(爭吵)"""

    PROMPT_TEMPLATE: str = """
    ## BACKGROUND
    Suppose you are {name}, you are in a debate with {opponent_name}.
    ## DEBATE HISTORY
    Previous rounds:
    {context}
    ##
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。