# MetaGPT智能體學(xué)習(xí) | 第三章復(fù)習(xí)

目錄

  • 資料來源
  • 安裝
  • 測試環(huán)境
  • 學(xué)習(xí)過程
    1. 智能體的智能中樞:大模型問答模塊_aask
    2. 智能體的行動(dòng)模塊:Action
    3. 智能體的角色模塊:Role
  • 作業(yè)

資料來源

MetaGPT github:https://github.com/geekan/MetaGPT
MetaGPT 官方文檔:https://docs.deepwisdom.ai/main/zh/guide/get_started/introduction.html
MetaGPT 官方教程(飛書):https://deepwisdom.feishu.cn/wiki/KhCcweQKmijXi6kDwnicM0qpnEf
Datawhale 合作課程:https://spvrm23ffj.feishu.cn/docx/RZNpd5uXfoebTPxMXCFcWHdMnZb

安裝

本筆記基于目前最新的v0.7.*版本育拨。
作為一種學(xué)習(xí)途徑,我會(huì)嘗試根據(jù)最新版本修改教程中不兼容的代碼柱宦。

使用mamba(conda的加速版)安裝依賴:

mamba create -n metagpt
git clone https://github.com/geekan/MetaGPT.git
cd MetaGPT
git checkout v0.7.3 # choose the latest v0.7.*
pip install ./

v0.7版本變更了配置文件生成方式:

metagpt --init-config

在生成的$HOME/.metagpt/config2.yaml文件中添加對應(yīng)大語言模型的API key国瓮。

測試環(huán)境

目前我使用Jupyter Notebook進(jìn)行測試灭必,需要安裝jupyterlab

#安裝jupyterlab
conda install -c conda-forge jupyterlab
#啟動(dòng)jupyterlab
jupyter-lab 

學(xué)習(xí)過程

1. 智能體的智能中樞:與大模型的模塊_aask

首先來感受智能體的核心:與大模型交互的模塊_aask:

from typing import Optional

async def _aask(self, prompt: str, system_msgs: Optional[list[str]] = None) -> str:
    """Append default prefix"""
    if not system_msgs:
        system_msgs = []
    system_msgs.append(self.prefix)
    return await self.llm.aask(prompt, system_msgs)

測試該模塊,直觀感受它的作用:

msg = "生成一段DNA序列的反向互補(bǔ)序列."
myask = Action()

resp01 = await myask._aask(msg)

output:

生成DNA序列的反向互補(bǔ)序列需要遵循幾個(gè)基本原則:DNA由四種核苷酸組成乃摹,分別是腺嘌呤(A)禁漓、胸腺嘧啶(T)、胞嘧啶(C)和鳥嘌呤(G)孵睬。在DNA的反向互補(bǔ)序列中播歼,A與T互補(bǔ),C與G互補(bǔ)掰读。生成反向互補(bǔ)序列時(shí)秘狞,首先需要將原序列反向,然后將每個(gè)核苷酸替換為其互補(bǔ)核苷酸蹈集。

例如烁试,如果原始DNA序列是:

5'-ATGCTAGC-3'

那么,首先將其反向:

3'-CGATGCAT-5'

然后將每個(gè)核苷酸替換為其互補(bǔ)核苷酸:

5'-GCTACGTA-3'

這就是原始DNA序列的反向互補(bǔ)序列拢肆。

如果你有一個(gè)具體的DNA序列需要轉(zhuǎn)換减响,請?zhí)峁┰撔蛄?2024-03-02 21:11:44.964 | INFO     | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.009 | Max budget: $10.000 | Current cost: $0.009, prompt_tokens: 33, completion_tokens: 295
,我將為你生成其反向互補(bǔ)序列郭怪。

注意支示,在jupyter中需要直接使用await獲取異步調(diào)用結(jié)果。
同時(shí)可以感受到移盆,_aask其實(shí)就是與指定LLM進(jìn)行問答的過程悼院。給予提示詞,返回大模型推理咒循、思考据途、組織的結(jié)果。

2. 智能體的行動(dòng)模塊:Action

下面再來看一下智能體利用大模型問答能力來實(shí)現(xiàn)的動(dòng)作模塊Action:

from metagpt.actions import Action
import re  # 導(dǎo)入正則表達(dá)式庫

class SimpleWriteCode(Action):
    # 定義代碼prompt模板
    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
    name: str = "SimpleWriteCode"

    # 定義異步方法run叙甸,用于接收指令并運(yùn)行生成代碼的函數(shù)
    async def run(self, instruction: str):
        # 將輸入內(nèi)容整合到Prompt模版中
        prompt = self.PROMPT_TEMPLATE.format(instruction=instruction)

        # 發(fā)送Prompt,向LLM提問,接收答復(fù)
        rsp = await self._aask(prompt)

        # 解析答復(fù)文本,提取其中的代碼部份
        code_text = SimpleWriteCode.parse_code(rsp)

        # 返回提取出的代碼文本
        return code_text

    # 定義靜態(tài)方法parse_code颖医,用于從答復(fù)文本中解析提取出代碼
    @staticmethod
    def parse_code(rsp):
        # 定義正則表達(dá)式匹配模式,用于識(shí)別包含在```python```標(biāo)記中的代碼
        pattern = r"```python(.*)```"
        # 使用正則表達(dá)式搜索匹配的代碼文本
        match = re.search(pattern, rsp, re.DOTALL)
        # 如果搜索到匹配裆蒸,提取第一個(gè)捕獲組(即我們需要的代碼)熔萧,否則返回完整的響應(yīng)文本
        code_text = match.group(1) if match else rsp
        # 返回提取的代碼文本
        return code_text

先感受下同樣的輸入指令,SimpleWriteCode_aask輸出的不同:

msg = "生成一段DNA序列的反向互補(bǔ)序列."
test = SimpleWriteCode()

resp02 = await test.run(msg)

output:

 ```python
def reverse_complement_dna(dna_sequence):
    complement = {'A': 'T', 'T': 'A', 'C': 'G', 'G': 'C'}
    return ''.join([complement[base] for base in reversed(dna_sequence)])

# Test cases
print(reverse_complement_dna("ATCG"))  # Expected output: CGAT
print(reverse_complement_dna("AAGCTT"))  # Expected output: A
2024-03-02 21:22:32.325 | INFO     | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.004 | Max budget: $10.000 | Current cost: $0.004, prompt_tokens: 73, completion_tokens: 103
AGCTT
```#           

可以發(fā)現(xiàn),這次給出的結(jié)果不再廢話佛致,而是輸出純純的代碼贮缕。這得益于以下幾個(gè)處理:

  1. PROMPT_TEMPLATE:通過對指令進(jìn)行提示工程,添加指引俺榆、約束和規(guī)范感昼,讓大模型返回可以方便我們處理的格式
  2. parse_code(): 格式統(tǒng)一之后,我們就能用通用代碼罐脊,將其中我們需要的部份提取出來定嗓,而將不需要的廢話和修飾格式去除。

3. 智能體的角色模塊:Role

下面封裝一個(gè)可以靈活運(yùn)用諸如上述動(dòng)作模塊的智能體角色:一位生信程序員「馬湃森」并讓他擁有寫python代碼的能力萍桌。

from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.logs import logger

class BioCoder(Role):
    name: str = "馬湃森"
    profile: str = "BioCoder"

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

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: ready to {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 todo.run(msg.content)
        msg = Message(content=code_text, role=self.profile, cause_by=type(todo))

        return msg

注意:0.7版本中宵溅,_init_actions被替換為了set_actions. _rc也在先前的版本中改為了rc.

依然利用相同的提示詞來感受下Role的能力:

msg = "生成一段DNA序列的反向互補(bǔ)序列."
role = BioCoder()

resp03 = await role.run(msg)

output:

2024-03-02 21:24:12.863 | INFO     | __main__:_act:14 - 馬湃森(BioCoder): ready to SimpleWriteCode
```python
def reverse_complement(dna_sequence):
    complement = {'A': 'T', 'T': 'A', 'C': 'G', 'G': 'C'}
    return ''.join([complement[base] for base in reversed(dna_sequence)])

# Test case 1
print(reverse_complement("ATCG"))  # Expected output: CGAT

# Test case 2
print(reverse_complement("GGCCAA"))  # Expected output: TTGGCC
```2024-03-02 21:24:16.965 | INFO     | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.004 | Max budget: $10.000 | Current cost: $0.004, prompt_tokens: 87, completion_tokens: 103

我們發(fā)現(xiàn)輸出內(nèi)容跟直接調(diào)用SimpleWriteCode動(dòng)作是幾乎相同的。因?yàn)樵谙惹暗亩x中上炎,這個(gè)角色只被賦予了SimpleWriteCode動(dòng)作恃逻,還沒有發(fā)揮role的全部潛力。

下面我們再‘教’給馬派森一個(gè)新的動(dòng)作:運(yùn)行SimpleWriteCode給出的代碼:

class SimpleRunCode(Action):
    name: str = "SimpleRunCode"

    async def run(self, code_text: str):
        result = subprocess.run(["python3", "-c", code_text], capture_output=True, text=True)
        code_result = result.stdout
        code_stderr = result.stderr
        logger.info(f"{code_result=}")
        if(code_stderr is not None):
            logger.info(f"{code_stderr=}")
            code_result = code_result + f"{code_stderr=}"
        return code_result

# test
tRun = SimpleRunCode()
resp04 = await tRun.run(resp03.content)

output:

2024-03-02 21:35:02.651 | INFO     | __main__:run:8 - code_result='CGAT\nTTGGCC\n'
2024-03-02 21:35:02.653 | INFO     | __main__:run:10 - code_stderr=''

可以看出,這個(gè)步驟很簡單,都不需要問LLM, 只需要調(diào)用subprocess運(yùn)行一下即可判斷腳本寫的正不正確.

接下來重新定義role反症,正式讓「馬湃森」的技能升級(jí)到lv.2, 增加SimpleRunCode的能力:

from metagpt.roles.role import Role, RoleReactMode
import re

class RunnableBioCoder(Role):
    name: str = "馬湃森"
    profile: str = "RunnableBioCoder"

    def __init__(self, **kwargs):
        super().__init__( **kwargs)
        self.set_actions([SimpleWriteCode, SimpleRunCode]) # 就在這里增加Actions
        self._set_react_mode(react_mode=RoleReactMode.BY_ORDER.value)

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
        # 通過在底層按順序選擇動(dòng)作
        # todo 首先是 SimpleWriteCode() 然后是 SimpleRunCode()
        todo = self.rc.todo

        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))
        self.rc.memory.add(msg)
        return msg

再次測試效果:

msg = "生成一段DNA序列的反向互補(bǔ)序列."

role = RunnableBioCoder()

resp05 = await role.run(msg)

output:

2024-03-02 21:35:30.025 | INFO     | __main__:_act:14 - 馬湃森(RunnableBioCoder): to do SimpleWriteCode(SimpleWriteCode)
```python
def reverse_complement(dna_sequence):
    complement = {'A': 'T', 'T': 'A', 'C': 'G', 'G': 'C'}
    return ''.join([complement[base] for base in reversed(dna_sequence)])

# Test case 1
print(reverse_complement("ATCG"))  # Expected output: CGAT

# Test case 2
print(reverse_complement("GGATCC"))  # Expected output: GGATCC
```2024-03-02 21:35:36.567 | INFO     | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.004 | Max budget: $10.000 | Current cost: $0.004, prompt_tokens: 88, completion_tokens: 103
2024-03-02 21:35:36.570 | INFO     | __main__:_act:14 - 馬湃森(RunnableBioCoder): to do SimpleRunCode(SimpleRunCode)
2024-03-02 21:35:37.652 | INFO     | __main__:run:8 - code_result='CGAT\nGGATCC\n'
2024-03-02 21:35:37.653 | INFO     | __main__:run:10 - code_stderr=''

如上辛块,實(shí)現(xiàn)了對生成代碼的立刻運(yùn)行測試。

作業(yè)

經(jīng)過上面的學(xué)習(xí)铅碍,我想你已經(jīng)對 MetaGPT 的框架有了基本了解,現(xiàn)在我希望你能夠自己編寫這樣一個(gè) agent

  • 這個(gè) Agent 擁有三個(gè)動(dòng)作 打印1 打印2 打印3(初始化時(shí) init_action([print,print,print]))
  • 重寫有關(guān)方法(請不要使用act_by_order线椰,我希望你能獨(dú)立實(shí)現(xiàn))使得 Agent 順序執(zhí)行上面三個(gè)動(dòng)作
  • 當(dāng)上述三個(gè)動(dòng)作執(zhí)行完畢后胞谈,為 Agent 生成新的動(dòng)作 打印4 打印5 打印6 并順序執(zhí)行,(之前我們初始化了三個(gè) print 動(dòng)作憨愉,執(zhí)行完畢后烦绳,重新init_action([...,...,...]),然后順序執(zhí)行這個(gè)新生成的動(dòng)作列表)

答:
先定義一個(gè)Action:

from metagpt.actions import Action

class print123(Action):
    PROMPT_TEMPLATE: str = """
    I may say something with explaination and/or description.
    Focus on the ojbject I mentioned. It should be a number.
    Remember this number, response this number + 1.
    Otherwise, if you realize that the object I mentioned is not a number, response NOTHING.
    Return ```markdown your_response``` with NO other texts,
    I say: {message}
    your responce:
    """
    name: str = "print123"
    prefix: str = "repeat what I say + 1. NO extral word."  # aask*時(shí)會(huì)加上prefix配紫,作為system_message
    desc: str = "一個(gè)簡單的打印動(dòng)作径密,你說啥它打印啥+1。"  # for skill manager
    
    async def run(self, message: str):
        prompt = self.PROMPT_TEMPLATE.format(message=message)

        rsp = await self._aask(prompt)

        rsp_text = print123.parse_text(rsp)

        return rsp_text

    @staticmethod
    def parse_text(rsp):
        pattern = r"```markdown(.*)```"
        match = re.search(pattern, rsp, re.DOTALL)
        text = match.group(1) if match else rsp
        return text

測試:

msg = "My luck item is cake."
msg = "My luck number is 7."
msg = "Today is Sunday."
msg = "This month is September."
msg = "Now it's 5 o'clock"
msg = "Now it's five o'clock"
msg = "I say fourteen."
myprint = print123()

resp123 = await myprint.run(msg)

output:

2024-03-02 16:46:22.940 | INFO     | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.001 | Max budget: $10.000 | Current cost: $0.001, prompt_tokens: 103, completion_tokens: 6
```markdown
15
```#

再來構(gòu)建一個(gè)將這個(gè)打印執(zhí)行兩遍躺孝,每遍三次的Role:

from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.logs import logger

class print126(Role):
    name: str = "Printer"
    profile: str = "printer"
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        #self.set_actions([print123,print123,print123])
        
    async def _think(self) -> None:
        """Determine the next action to be taken by the role."""
        msg = self.get_memories(k=1)[0] # find the most recent messages
    
        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 _react(self) -> Message:
        """Execute the assistant's think and actions.

        Returns:
            A message containing the final result of the assistant's actions.
        """
        acts = 2
        while acts > 0 :
            self.set_actions([print123,print123,print123])
            while True:
                await self._think()
                if self.rc.todo is None:
                    break
                resp = await self._act()
            acts = acts - 1
        return resp

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: ready to {self.rc.todo}")
        todo = self.rc.todo

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

測試效果:

msg='0'

theRole = print126()

resp = await theRole.run(msg)

output:

2024-03-02 18:16:50.882 | INFO     | __main__:_act:44 - Printer(printer): ready to print123
2024-03-02 18:16:53.005 | INFO     | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.001 | Max budget: $10.000 | Current cost: $0.001, prompt_tokens: 109, completion_tokens: 6
2024-03-02 18:16:53.008 | INFO     | __main__:_act:44 - Printer(printer): ready to print123
```markdown
1
```#
2024-03-02 18:16:55.001 | INFO     | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.003 | Max budget: $10.000 | Current cost: $0.001, prompt_tokens: 109, completion_tokens: 6
2024-03-02 18:16:55.002 | INFO     | __main__:_act:44 - Printer(printer): ready to print123
```markdown
2
```#
2024-03-02 18:16:55.914 | INFO     | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.004 | Max budget: $10.000 | Current cost: $0.001, prompt_tokens: 109, completion_tokens: 6
2024-03-02 18:16:55.917 | INFO     | __main__:_act:44 - Printer(printer): ready to print123
```markdown
3
```#
2024-03-02 18:16:56.783 | INFO     | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.005 | Max budget: $10.000 | Current cost: $0.001, prompt_tokens: 109, completion_tokens: 6
2024-03-02 18:16:56.785 | INFO     | __main__:_act:44 - Printer(printer): ready to print123
```markdown
4
```#
2024-03-02 18:16:57.993 | INFO     | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.006 | Max budget: $10.000 | Current cost: $0.001, prompt_tokens: 109, completion_tokens: 6
2024-03-02 18:16:57.995 | INFO     | __main__:_act:44 - Printer(printer): ready to print123
```markdown
5
```#
2024-03-02 18:16:59.073 | INFO     | metagpt.utils.cost_manager:update_cost:52 - Total running cost: $0.008 | Max budget: $10.000 | Current cost: $0.001, prompt_tokens: 109, completion_tokens: 6
```markdown
6
```#

這里react的方法很多享扔,可以寫循環(huán)也可以寫條件,多試試可以加深理解植袍。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惧眠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子于个,更是在濱河造成了極大的恐慌氛魁,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異秀存,居然都是意外死亡捶码,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門或链,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惫恼,“玉大人,你說我怎么就攤上這事株扛∮瓤穑” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵洞就,是天一觀的道長盆繁。 經(jīng)常有香客問我,道長旬蟋,這世上最難降的妖魔是什么油昂? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮倾贰,結(jié)果婚禮上冕碟,老公的妹妹穿的比我還像新娘。我一直安慰自己匆浙,他們只是感情好安寺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著首尼,像睡著了一般挑庶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上软能,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天迎捺,我揣著相機(jī)與錄音,去河邊找鬼查排。 笑死凳枝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的跋核。 我是一名探鬼主播岖瑰,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼了罪!你這毒婦竟也來了锭环?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤泊藕,失蹤者是張志新(化名)和其女友劉穎辅辩,沒想到半個(gè)月后难礼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡玫锋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年蛾茉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撩鹿。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谦炬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出节沦,到底是詐尸還是另有隱情键思,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布甫贯,位于F島的核電站吼鳞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏叫搁。R本人自食惡果不足惜赔桌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望渴逻。 院中可真熱鬧疾党,春花似錦、人聲如沸惨奕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梨撞。三九已至茧泪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間聋袋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工穴吹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留幽勒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓港令,卻偏偏與公主長得像啥容,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子顷霹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容