学习背景
个人学习笔记
学前准备:
因为metagpt官网推荐用的0.52,但实际上本章学习会出现许多bug,这几天学习一直很头疼,需要进行调试修改
1.没有scheme参数
比如下面这样
解决方案
# child = await i.simple_fill(schema=schema, mode=mode)
child = await i.simple_fill(to=schema, mode=mode)
2.ActionNode一直在循环执行,这个我一直没有解决,好像升级最新会好,有解决方案的老兄麻烦告诉一下
3._rc变化:0。6版本之后_rc这种成员变量都换成了不带下划线的:‘rc’
什么是actionnode
在MG框架0.5版本中,新增加了ActionNode
类,为Agent的动作执行提供更强的能力
ActionNode
可以被视为一组动作树,根据类内定义,一个动作树的父节点可以访问所有的子动作节点;也就是说,定义了一个完整的动作树之后,可以从父节点按树的结构顺序执行每一个子动作节点。因此,动作的执行也可以突破0.4版本框架中,需要在Role的_react
内循环执行的限制,达到更好的CoT效果。
ActionNode
仍然需要基于Action
类进行构建,在定义一个ActionNode
动作树之后,需要将该动作树作为参数赋予一个Action
子类,在并将其输入到Role
中作为其动作。在这个意义上,一个ActionNode
动作树可以被视为一个内置CoT思考的Action
。
同时,在ActionNode
基类中,也配置了更多格式检查和格式规范工具,让CoT执行过程中,内容的传递更加结构化。这也服务于让MetaGPT框架生成更好、更长、更少Bug的代码这一目的。
它的常用方法:
add_child
增加子ActionNode
add_children
批量增加子ActionNode
from_children
直接从一系列的子nodes初始化
compile
编译ActionNode的Prompt模板
_aask_v1
使用ActionOutput封装调aask访问模型的输出
fill 对ActionNode
进行填槽,即实现执行传入的prompt并获取结果返回,并将结果存储在自身中
实现技术文档助手
实现代码:
import os
import ast
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 import Role
from metagpt.schema import Message
from metagpt.utils.file import File
import fire
from pathlib import Path
from typing import Any, List, Tuple, Union
from typing import Dict
from metagpt.actions import Action
from metagpt.prompts.tutorial_assistant import DIRECTORY_PROMPT, CONTENT_PROMPT
from metagpt.utils.common import OutputParser
from metagpt.actions.action_node import ActionNode
DIRECTORY_INSTRUCTION = """
You are now a seasoned technical professional in the field of the internet.
We need you to write a technical tutorial".
您现在是互联网领域的经验丰富的技术专业人员。
我们需要您撰写一个技术教程。
"""
CONTENT_INSTRUCTION = """
You are now a seasoned technical professional in the field of the internet.
"""
# 实例化一个ActionNode,输入对应的参数
DIRECTORY_WRITE = ActionNode(
# ActionNode的名称
key="Directory Write",
# 期望输出的格式
expected_type=str,
# 命令文本
instruction=DIRECTORY_INSTRUCTION,
# 例子输入,在这里我们可以留空
example="",
)
class WriteDirectory(Action):
"""Action class for writing tutorial directories.
Args:
name: The name of the action.
language: The language to output, default is "Chinese".
"""
language: str = ""
def __init__(self, name: str = "", language: str = "Chinese", *args, **kwargs):
super().__init__()
self.language = language
async def run(self, topic: str, *args, **kwargs) -> Dict:
DIRECTORY_PROMPT = """
We need you to write a technical tutorial with the topic "{topic}".
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": [{{"第一章": ["1.1 xxxx", "1.2 xxxx"]}}, {{"第二章": ["2.1 xxxx", "2.2 xxxx"]}}]}}.
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.
6. 以标准的JSON格式输出
The good example is below, it is just an format example, please do not write the content exactly the same with it, write with your idea:
{{"title": "Git 教程",
"directory": [
{{"第一章 Git简介": ["1.1 Git定义", "1.2 Git的起源", "1.3 为什么选择Git"]}},
{{"第二章 Git基础设置": ["2.1 安装Git", "2.2 设置用户名和邮箱"]}},
{{"第三张 Git仓库": ["3.1 创建新仓库", "3.2克隆已有仓库"]}}
]
}}
"""
prompt = DIRECTORY_PROMPT.format(topic=topic,language=self.language)
resp_node = await DIRECTORY_WRITE.fill(context=prompt, llm=self.llm, schema="raw")
#fixed_string = resp_node.content.replace(",",",").replace("目录:", '"目录":').replace("标题:", '"标题":').replace('["', '[').replace('"]', ']').replace('{"', '{').replace('"}', '}')
return OutputParser.extract_struct(resp_node.content, 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".
"""
language: str = ""
directory: str = ""
def __init__(self, name: str = "", directory: list = [], language: str = "Chinese", *args, **kwargs):
super().__init__()
self.language = language
self.directory = directory
self.name = name
action_nodes = list()
for dir in directory:
action_nodes.append(ActionNode(
# ActionNode的名称
key=f"{dir}",
# 期望输出的格式
expected_type=str,
# 命令文本
instruction=DIRECTORY_INSTRUCTION,
# 例子输入,在这里我们可以留空
example="",
)
)
self.node = ActionNode.from_children("Write Content",action_nodes)
async def run(self, topic: str, *args, **kwargs) -> str:
CONTENT_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}".
"""
resp: str = ""
if os.path.exists("Tutorial.md"):
os.remove("Tutorial.md")
#顺序执行所有子node
for _, i in self.node.children.items():
prompt = CONTENT_PROMPT.format(topic=topic, language=self.language, directory=i.key)
i.set_context(prompt)
i.set_llm(self.llm)
child = await i.simple_fill(schema="raw", mode="auto")
resp += child.content + "\n"
#In case LLM stop output by accident, save the finished parts
try:
with open("Tutorial.md", 'a') as f:
f.write(child.content)
f.write("\n\n\n")
print(f"Child ActionNode Finished wirting!!!\n --{i.key}\n\n\n\n\n")
except Exception as e:
print(f"An error occurred: {e}")
return resp
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.
"""
topic: str = ""
main_title: str = ""
total_content: str = ""
language: str = ""
def __init__(
self,
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",
):
super().__init__()
self._init_actions([WriteDirectory(language=language)])
self.topic = ""
self.main_title = ""
self.total_content = ""
self.language = language
self.name = name
self.profile = profile
self.goal = goal
self.constraints = constraints
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}"
subdir = list()
first_dir: dict =[]
for first_dir in titles.get("directory"):
subdir.append(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._init_actions([WriteContent(self.topic,directory=subdir)])
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)
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 = "Git 教程"
role = TutorialAssistant()
logger.info(msg)
result = await role.run(msg)
logger.info(result)
asyncio.run(main())