【AI的未来 - AI Agent系列】【MetaGPT】6. 用ActionNode重写技术文档助手

文章目录

    • 0. 前置推荐阅读
    • 1. 重写WriteDirectory Action
      • 1.1 实现WriteDirectory的ActionNode:DIRECTORY_WRITE
      • 1.2 将 DIRECTORY_WRITE 包进 WriteDirectory中
    • 2. 重写WriteContent Action
      • 2.1 思考重写方案
      • 2.2 实现WriteContent的ActionNode
      • 2.3 改写WriteContent Action
    • 3. 改写TutorialAssistant Role
    • 4. 完整代码及执行结果

前文【【AI的未来 - AI Agent系列】【MetaGPT】5. 更复杂的Agent实战 - 实现技术文档助手】我们用Action实现了一个技术文档助手,在学习了ActionNode技术之后,我们用ActionNode来尝试重写一下这个技术文档助手。

0. 前置推荐阅读

  • 【AI的未来 - AI Agent系列】【MetaGPT】5. 更复杂的Agent实战 - 实现技术文档助手
  • 【AI的未来 - AI Agent系列】【MetaGPT】4. ActionNode从理论到实战
  • 【AI的未来 - AI Agent系列】【MetaGPT】4.1 细说我在ActionNode实战中踩的那些坑

1. 重写WriteDirectory Action

根据我们之前的需求,WriteDirectory Action实现的其实就是根据用户输入的内容,直接去询问大模型,然后生成一份技术文档大纲目录。

1.1 实现WriteDirectory的ActionNode:DIRECTORY_WRITE

# 命令文本
DIRECTORY_STRUCTION = """You are now a seasoned technical professional in the field of the internet. We need you to write a technical tutorial".您现在是互联网领域的经验丰富的技术专业人员。我们需要您撰写一个技术教程。"""# 实例化一个ActionNode,输入对应的参数
DIRECTORY_WRITE = ActionNode(# ActionNode的名称key="Directory Write",# 期望输出的格式expected_type=str,# 命令文本instruction=DIRECTORY_STRUCTION,# 例子输入,在这里我们可以留空example="",)

1.2 将 DIRECTORY_WRITE 包进 WriteDirectory中

class WriteDirectory(Action):language: str = "Chinese"def __init__(self, name: str = "", language: str = "Chinese", *args, **kwargs):super().__init__()self.language = languageasync def run(self, topic: str, *args, **kwargs) -> Dict:DIRECTORY_PROMPT = """The topic of tutorial is {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": [{{"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.教程的主题是{topic}。请按照以下要求提供本教程的具体目录: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,作为ActionNode的输入prompt = DIRECTORY_PROMPT.format(topic=topic, language=self.language)# resp = await self._aask(prompt=prompt)# 直接调用ActionNode.fill方法,注意输入llm# 该方法会返回self,也就是一个ActionNode对象resp_node = await DIRECTORY_WRITE.fill(context=prompt, llm=self.llm, schema="raw")# 选取ActionNode.content,获得我们期望的返回信息resp = resp_node.contentreturn OutputParser.extract_struct(resp, dict)

重点是这一句 resp_node = await DIRECTORY_WRITE.fill(context=prompt, llm=self.llm, schema="raw"),将原来的直接拿Prompt询问大模型获取结果,变成了使用ActionNode的fill函数,去内部询问大模型并获取结果。

2. 重写WriteContent Action

2.1 思考重写方案

WriteContent的目的是根据目录标题询问大模型,生成具体的技术文档内容。

最直观的重写方法:每个WriteContent包一个ActionNode,像WriteDirectory一样,如下图:

在这里插入图片描述
像不用ActionNode一样,每个WriteContent执行完毕返回结果到Role中进行处理和组装,然后执行下一个WriteContent Action。可能你也看出来了,这种重写方法其实就是将WriteContent直接调用大模型改成了使用ActionNode调用大模型,其它都没变。我认为这种重写方法的意义不大,没体现出ActionNode的作用和价值。

于是我想到了第二种重写方法,如下图:
在这里插入图片描述
将每一个章节内容的书写作为一个ActionNode,一起放到WriteContent动作里执行,这样外部Role只需执行一次WriteContent动作,所有内容就都完成了,可以实现ActionNode设计的初衷:突破需要在Role的_react内循环执行的限制,达到更好的CoT效果。

2.2 实现WriteContent的ActionNode

CONTENT_WRITE = ActionNode(key="Content Write",expected_type=str,instruction="",example="",
)

这里可以将instruction放空,后面用context设置prompt可以实现相同的效果。

2.3 改写WriteContent Action

主要修改点:
(1)初始化时接收一个ActionNode List,使用这个List初始化 self.node,作为父节点
(2)run方法中不再直接调用大模型,而是依次执行子节点的simple_fill函数获取结果
(3)在调用子节点的simple_fill函数前,记得更新prompt
(4)子节点返回的内容进行组装
(5)最后返回组装后的结果

更多代码细节注释请看下面:

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 = "Chinese"directory: str = ""total_content: str = "" ## 组装所有子节点的输出def __init__(self, name: str = "", action_nodes: list = [], language: str = "Chinese", *args, **kwargs):super().__init__()self.language = languageself.node = ActionNode.from_children("WRITE_CONTENT_NODES", action_nodes) ## 根据传入的action_nodes列表,生成一个父节点async def run(self, topic: str, *args, **kwargs) -> str: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}"."""for _, i in self.node.children.items():time.sleep(20) ## 避免OpenAI的API调用频率过高prompt = CONTENT_PROMPT.format(topic=topic, language=self.language, directory=i.key)i.set_llm(self.llm) ## 这里要设置一下llm,即使设置为None,也可以正常工作,但不设置就没法正常工作## 为子节点设置context,也就是Prompt,ActionNode中我们将instruction放空,instruction和context都会作为prompt给大模型## 所以两者有一个为空也没关系,只要prompt完整就行i.set_context(prompt)child = await i.simple_fill(schema="raw", mode="auto") ## 这里的schema注意写"raw"self.total_content += child.content ## 组装所有子节点的输出logger.info("writecontent:", self.total_content)return self.total_content

3. 改写TutorialAssistant Role

TutorialAssistant Role的作用是执行以上两个Action,输出最终结果。改写内容如下:
(1)将原本的生成Action List改为生成ActionNode List

  • 注意细节:生成的ActionNode的key为每个章节的目录标题,在WriteContent中更新每个node的prompt时使用了

(2)将ActionNode List传给WriteContent Action进行WriteContent Action的初始化
(3)将WriteContent初始化到Role的动作中

  • 注意细节:这里不再是之前每个first_dir创建一个WriteContent了,而是最后只初始化一个。

更多代码细节注释请看下面:

    async def _handle_directory(self, titles: Dict) -> Message:self.main_title = titles.get("title")directory = f"{self.main_title}\n"self.total_content += f"# {self.main_title}"action_nodes = list()for first_dir in titles.get("directory"):logger.info(f"================== {first_dir}")action_nodes.append(ActionNode( ## 每个章节初始化一个ActionNodekey=f"{first_dir}",  ## 注意key为本章目录标题expected_type=str,instruction="",example=""))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(language=self.language, action_nodes=action_nodes)]) ## 初始化一个WriteContent Action,不是多个了self.rc.todo = Nonereturn Message(content=directory)

4. 完整代码及执行结果

# 加载 .env 到环境变量
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())import asyncio
import re
import time
from typing import Dictfrom metagpt.actions.action import Action
from metagpt.actions.action_node import ActionNode
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.utils.common import OutputParser
from metagpt.const import TUTORIAL_PATH
from datetime import datetime
from metagpt.utils.file import File# 命令文本
DIRECTORY_STRUCTION = """You are now a seasoned technical professional in the field of the internet. We need you to write a technical tutorial".您现在是互联网领域的经验丰富的技术专业人员。我们需要您撰写一个技术教程。"""# 实例化一个ActionNode,输入对应的参数
DIRECTORY_WRITE = ActionNode(# ActionNode的名称key="Directory Write",# 期望输出的格式expected_type=str,# 命令文本instruction=DIRECTORY_STRUCTION,# 例子输入,在这里我们可以留空example="",)CONTENT_WRITE = ActionNode(key="Content Write",expected_type=str,instruction="",example="",
)class WriteDirectory(Action):language: str = "Chinese"def __init__(self, name: str = "", language: str = "Chinese", *args, **kwargs):super().__init__()self.language = languageasync def run(self, topic: str, *args, **kwargs) -> Dict:DIRECTORY_PROMPT = """The topic of tutorial is {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": [{{"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.教程的主题是{topic}。请按照以下要求提供本教程的具体目录: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,作为ActionNode的输入prompt = DIRECTORY_PROMPT.format(topic=topic, language=self.language)# resp = await self._aask(prompt=prompt)# 直接调用ActionNode.fill方法,注意输入llm# 该方法会返回self,也就是一个ActionNode对象resp_node = await DIRECTORY_WRITE.fill(context=prompt, llm=self.llm, schema="raw")# 选取ActionNode.content,获得我们期望的返回信息resp = resp_node.contentreturn OutputParser.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"."""language: str = "Chinese"directory: str = ""total_content: str = "" ## 组装所有子节点的输出def __init__(self, name: str = "", action_nodes: list = [], language: str = "Chinese", *args, **kwargs):super().__init__()self.language = languageself.node = ActionNode.from_children("WRITE_CONTENT_NODES", action_nodes) ## 根据传入的action_nodes列表,生成一个父节点async def run(self, topic: str, *args, **kwargs) -> str: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}"."""for _, i in self.node.children.items():time.sleep(20) ## 避免OpenAI的API调用频率过高prompt = CONTENT_PROMPT.format(topic=topic, language=self.language, directory=i.key)i.set_llm(self.llm) ## 这里要设置一下llm,即使设置为None,也可以正常工作,但不设置就没法正常工作## 为子节点设置context,也就是Prompt,ActionNode中我们将instruction放空,instruction和context都会作为prompt给大模型## 所以两者有一个为空也没关系,只要prompt完整就行i.set_context(prompt)child = await i.simple_fill(schema="raw", mode="auto") ## 这里的schema注意写"raw"self.total_content += child.content ## 组装所有子节点的输出logger.info("writecontent:", self.total_content)return self.total_contentclass TutorialAssistant(Role):topic: str = ""main_title: str = ""total_content: str = ""language: str = "Chinese"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.language = languageasync 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)returnif self.rc.state + 1 < len(self.states):self._set_state(self.rc.state + 1)else:self.rc.todo = Noneasync def _handle_directory(self, titles: Dict) -> Message:self.main_title = titles.get("title")directory = f"{self.main_title}\n"self.total_content += f"# {self.main_title}"action_nodes = list()# actions = list()for first_dir in titles.get("directory"):logger.info(f"================== {first_dir}")action_nodes.append(ActionNode(key=f"{first_dir}",expected_type=str,instruction="",example=""))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(language=self.language, action_nodes=action_nodes)])self.rc.todo = Nonereturn 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.todoif type(todo) is WriteDirectory:msg = self.rc.memory.get(k=1)[0]self.topic = msg.contentresp = 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 += respreturn 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:breakmsg = await self._act()root_path = TUTORIAL_PATH / datetime.now().strftime("%Y-%m-%d_%H-%M-%S")logger.info(f"Write tutorial to {root_path}")await File.write(root_path, f"{self.main_title}.md", self.total_content.encode('utf-8'))return msgasync def main():msg = "Git 教程"role = TutorialAssistant()logger.info(msg)result = await role.run(msg)logger.info(result)asyncio.run(main())
  • 执行结果

在这里插入图片描述

下一篇继续实战ActionNode:【AI Agent系列】【MetaGPT】7. 实战:只用两个字,让MetaGPT写一篇小说

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/645168.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UV紫外激光打标机的优缺点是什么

​ UV紫外激光打标机具有以下优点&#xff1a; 1. 精度高&#xff1a;紫外激光打标机的光束质量好&#xff0c;聚焦光斑小&#xff0c;可以实现在各种材料上进行超精细打标。 2. 速度快&#xff1a;由于紫外激光的独特特性&#xff0c;打标速度非常快&#xff0c;提高了生产效…

冷链温湿度监控解决方案,实时监测,助力运输安全

为了确保药品、生鲜等在冷链运输过程中的安全监管,需要对冷链、仓库等环节的温湿度信息进行实时自动检测和记录&#xff0c;有效防范储运过程中可能影响产品质量安全的各类风险&#xff0c;确保储存和运输过程的产品质量。 冷链温湿度监控系统解决方案&#xff0c;利用智能温湿…

【目标跟踪】多相机环视跟踪

文章目录 一、前言二、流程图三、实现原理3.1、初始化3.2、输入3.3、初始航迹3.4、航迹预测3.5、航迹匹配3.6、输出结果 四、c 代码五、总结 一、前言 多相机目标跟踪主要是为了实现 360 度跟踪。单相机检测存在左右后的盲区视野。在智能驾驶领域&#xff0c;要想靠相机实现无…

springboot120企业级工位管理系统

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的企业级工位管理系统 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 …

SpringMVC 注解配置SpringMVC

文章目录 1、创建初始化类&#xff0c;代替web.xml2、创建SpringConfig配置类&#xff0c;代替spring的配置文件3、创建WebConfig配置类&#xff0c;代替SpringMVC的配置文件4、测试功能 使用配置类和注解代替web.xml和SpringMVC配置文件的功能 1、创建初始化类&#xff0c;代替…

Vue3实战:显示后台获取的用户数据

文章目录 一、实战概述二、实战步骤&#xff08;一&#xff09;创建数据库与表&#xff08;二&#xff09;编写后端程序1、创建Spring Boot项目2、配置数据源3、创建用户实体类4、创建用户仓库接口5、创建用户服务类6、创建用户控制器7、启动应用&#xff0c;查看结果 &#xf…

蓝牙BLE基础知识

目录 一、初识蓝牙BLE 1.课程介绍 2.为什么需要蓝牙技术 3.蓝牙发展历史 4.蓝牙技术优势 5.蓝牙技术简介 6.学习补充 二、物理层&#xff08;Physical layer&#xff09; 1.模拟调制 2.数字调制 3.射频信道 4.学习补充 三、链路层&#xff08;link layer&#xff0…

Jmeter 设置全局请求 重点cook

原因 在使用jmeter 过程中为了方便 &#xff0c;会设置很多公众信心 比如请求头 请求cook 还会设置多个线程组 在同一个线程组中 我们只需要设置一个请求请求cook 就可以了 但是 有逆骨 就是喜欢多个线程组所以出现问题了 解决方案 设置一个全局变量 步骤 在测试计划中设…

图形用户界面(GUI)开发教程

文章目录 写在前面MATLAB GUI启动方式按钮&#xff08;Push Button&#xff09;查看属性tag的命名方式回调函数小小的总结 下拉菜单&#xff08;Pop-up Menu&#xff09;单选框&#xff08;Radio Button&#xff09;和复选框&#xff08;Check Box&#xff09;静态文本&#xf…

12.前端--CSS-背景属性

1.背景颜色 样式名称&#xff1a; background-color 定义元素的背景颜色 使用方式: background-color:颜色值; 其他说明&#xff1a; 元素背景颜色默认值是 transparent&#xff08;透明&#xff09;      background-color:transparent; 代码演示&#xff1a; 背景色…

硬件基础:组合逻辑电路

什么是组合逻辑电路 组合逻辑电路是由一些基本的逻辑门电路组成的&#xff0c;没有反馈&#xff0c;输出仅取决于输入。 组合逻辑电路是数字逻辑电路中一种重要的电路类型&#xff0c;它是由多个逻辑门&#xff08;例如与门、或门、非门等&#xff09;组成的电路。组合逻辑电路…

APUE学习之信号(Signal)

目录 一、信号 1、基本概念 2、用户处理信号的方式 3、查看信号 4、可靠信号和不可靠信号 5、信号种类 6、终止进程信号的区别 二、进程对信号的处理 1、signal&#xff08;&#xff09;函数 2、sigaction&#xff08;&#xff09;函数 3、代码演示 4、运行结果…

k8s---HPA 命名空间资源限制

目录 HPA相关知识 HPA&#xff08;Horizontal Pod Autoscaling&#xff09;Pod 水平自动伸缩&#xff0c;Kubernetes 有一个 HPA 的资源&#xff0c;HPA 可以根据 CPU 利用率自动伸缩一个 Replication Controller、 Deployment 或者Replica Set 中的 Pod 数量。 &#xff08;1…

LTD261次升级 | 小程序支持抖音客服、支持抖音登录 • 短信发送需实名认证 • 表单提交成功收邮件提醒

1、 抖音小程序新增抖音IM客服功能&#xff1b; 2、 抖音小程序支持一键登录、支持快捷授权手机号 3、 表单新增发送邮件到提交者邮箱&#xff1b; 4、 表单支持配置不自动推送客户管理&#xff1b; 5、 短信发送需实名认证签署承诺书&#xff1b; 6、 其他已知问题修复与优化&…

安装 nvm

前言&#xff1a; nvm 即 node 版本管理工具 (node version manager)&#xff0c;好处是方便切换 node.js 版本。 通过将多个 node 版本安装在指定路径&#xff0c;然后通过 nvm 命令切换时&#xff0c;就会切换我们环境变量中 node 命令指定的实际执行的软件路径。 使用场景…

数据结构笔记1

来自《Python数据结构学习笔记》&#xff08;张清云 编著&#xff09; 第一章 数据结构基础 1.逻辑结构 集合&#xff1a;结构中的数据元素除了同属于一种类型外&#xff0c;别无其他关系线性结构&#xff1a;数据元素之间一对一的关系树形结构&#xff1a;数据元素之间一对…

抖音向微信引流主要有哪几种方法-数灵通

近年来&#xff0c;随着智能设备的普及和信息技术的进步&#xff0c;短视频制作门槛逐渐降低&#xff0c;用户自创视频数量迅猛增长&#xff0c;用户规模持续扩大&#xff0c;有力推动了移动短视频的繁荣发展&#xff0c;市场规模也在不断扩张。作为当下炙手可热的短视频APP&am…

pycharm安装过程

1、安装包官网下载 PyCharm: the Python IDE for Professional Developers by JetBrains 点击下载 下拉选择社区版本 选择下载 下载完成后&#xff0c;双击exe安装。 安装完成&#xff0c;生成的桌面快捷方式 同意并继续 进入开发界面

网络安全学习 --- 小实验

题目 要求 1.防火墙线下使用子接口分别对应两个内部区域。 2.所有分区设备可以ping通网关。 过程 1.接口&#xff0c;区域配置完成。 2.配置SWL2 vlan 2 vlan 3 # interface GigabitEthernet0/0/1port link-type trunkport trunk allow-pass vlan 2 to 3 # interface Giga…

JavaWeb之JavaScript-Vue --黑马笔记

什么是JavaScript&#xff1f; JavaScript&#xff08;简称&#xff1a;JS&#xff09; 是一门跨平台、面向对象的脚本语言。是用来控制网页行为的&#xff0c;它能使网页可交互。 JavaScript 和 Java 是完全不同的语言&#xff0c;不论是概念还是设计。但是基础语法类似。 …