目錄
- 資料來源
- 安裝
- 測試環(huán)境
- 學(xué)習(xí)過程
- 智能體的智能中樞:大模型問答模塊
_aask
- 智能體的行動(dòng)模塊:
Action
- 智能體的角色模塊:
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è)處理:
- PROMPT_TEMPLATE:通過對指令進(jìn)行提示工程,添加指引俺榆、約束和規(guī)范感昼,讓大模型返回可以方便我們處理的格式
- 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)也可以寫條件,多試試可以加深理解植袍。