metagpt第五章学习笔记 ActionNode

学习背景

个人学习笔记

学前准备:

因为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())

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值