MetaGPT-打卡-day2,MetaGPT框架组件学习

这篇具有很好参考价值的文章主要介绍了MetaGPT-打卡-day2,MetaGPT框架组件学习。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

今天是第二天的打卡~昨天是关于一些概念的大杂烩,今天的话,就来到了Hello World环节。 从单个Agnet到多个Agent,再到组合更复杂的工作流来解决问题。

Agent组件

虽然看过了一些资料,但是还是有些不知道该怎么去理解Agent的概念。从单词本身的翻译来看,很多都是经纪人、代理人的意思,关于称之为“数字人”或者“智能体”的方式,目前还是没有很好的理解到,希望在后面的学习中,能解答自己的困惑。
【By INSCODE AI创作助手】
在大模型中,Agent是指可以独立行动、具有决策能力和交互能力的实体。这些实体可以是人、机器人、虚拟角色等。Agent通常通过感知环境、收集信息、分析数据、制定策略和执行动作来实现自己的目标。

在大模型中,Agent的概念主要用于描述智能系统中的个体或实体,这些个体可以与其他Agent或环境进行交互,通过学习和适应来改进自己的行为和决策能力。

Agent通常具有以下特征:

  1. 自主性:Agent可以自主地根据自身的目标和环境条件制定行动计划,并进行决策。

  2. 学习能力:Agent可以通过学习和适应来改进自己的行为和决策能力。

  3. 感知能力:Agent可以感知环境中的信息,并根据这些信息做出相应的决策。

  4. 交互能力:Agent可以与其他Agent或环境进行交互,进行信息的传递和共享。

  5. 目标导向性:Agent具有特定的目标或任务,其行动和决策都是为了实现这些目标或完成这些任务。

在大模型中,Agent的概念常常用于建模和仿真复杂的智能系统,如人工智能系统、自动驾驶系统、多智能体系统等。Agent的设计和实现可以基于传统的规则和逻辑,也可以基于机器学习和深度学习等方法。 Agent的研究和应用可以帮助我们更好地理解和处理复杂的现实世界问题。


在MetaGPT中,Agent有如下的定义。

Agent=大语言模型LLM+观察+思考+行动+记忆。

在MetaGPT中定义的一个agent运行流程如下:

MetaGPT-打卡-day2,MetaGPT框架组件学习,大模型,python,GPT,python,大模型,MetaGPT

当启动定义好的Agent后,可以通过获取接收到的信息,进行分析,然后决定下一步的操作,然后紧接着执行相应的操作,得到这个环节的结果。

不过这里面感觉需要去解决的问题还很多(不过可以先不考虑这些内容,先让代码跑起来看看效果):

  • 观察、思考、提出方案等完全依赖大模型的效果
  • 大模型有关于token的限制,这就需要考虑 任务的信息量或者解决大模型的限制
  • 新生信息的处理和分析
  • ……

在metaggpt中,Role类是智能体的逻辑抽象,一个角色能够执行特定的动作,拥有记忆、思考、执行行动的能力。

实现一个单动作的Agent

import os

os.environ["ZHIPUAI_API_KEY"] = "秘钥"

import re
import asyncio
from bson import Code
from metagpt.actions import Action
from metagpt.roles import Role
from metagpt.logs import logger
from metagpt.schema import Message

class Coding(Action):
    PROMPT_TEMPLATE="""
    Write a python function that can {instruction} and provide test cases.
    Return ```python your_code_here ```with NO other texts,
    your code:
    """
    
    def __init__(self,name="Coding",context=None,llm=None):
        super().__init__(name,context,llm)
    
    async def run(self,instruction:str,**kwargs):
        prompt=self.PROMPT_TEMPLATE.format(instruction=instruction)
        rsp=await self._aask(prompt)
        code_text=Coding.parse_code(rsp)
        return code_text

    @staticmethod
    def parse_code(text:str):
        pattern =r'```python\n(.*)```'
        match=re.search(pattern,text,re.DOTALL)
        code_text=match.group(1) if match else text
        return code_text
    
class AloneCoder(Role):
    def __init__(self, name="ZhangSan", profile="AloneCoder",**kwargs):
        super().__init__(name, profile, **kwargs)
        self._init_actions(actions=[Coding])
    async def _act(self) -> Message:
        logger.info(f"{self.name} is coding...")
        logger.info(f"{self._setting}:ready to {self._rc.todo}")
        todo=self._rc.todo

        msg=self.get_memories(k=1)[0]
        code_text=await todo.run(msg.content)
        msg=Message(content=code_text,role=self.profile,cause_by=type(todo))
        return msg
    
async def main():
    msg="编写一个函数,计算输入日期范围内的所有星期五"
    role=AloneCoder()
    logger.info(msg)
    result=await role.run(with_message=msg)
    logger.info(result)

asyncio.run(main())    
    


输出:

2024-01-17 14:31:18.604 | INFO     | metagpt.const:get_metagpt_package_root:32 - Package root set to C:\Users\youxi\uenv\GPT\code
2024-01-17 14:31:19.452 | INFO     | __main__:main:53 - 编写一个函数,计算输入日期范围内的所有星期五
2024-01-17 14:31:19.454 | INFO     | __main__:_act:41 - ZhangSan is coding...
2024-01-17 14:31:19.455 | INFO     | __main__:_act:42 - ZhangSan(AloneCoder):ready to Coding
 ```python
from datetime import datetime, timedelta

def find_fridays(start_date, end_date):
    fridays = []
    start_date = datetime.strptime(start_date, '%Y-%m-%d')
end_date = datetime.strptime(end_date, '%Y-%m-%d')

    while start_date <= end_date:
        if start_date.weekday() == 4:  # 判断星期五
            fridays.append(start_date)
        start_date += timedelta(days=1)

    return fridays

# 测试案例
test_case_1 = ('2021-09-01', '2021-09-30')
test_case_2 = ('2021-01-01', '2021-12-31')

print(find_fridays(test_case_1[0], test_case_1[1]))
print(find_fridays(test_case_2[0], test_case_2[1]))
```

运行结果:

```
['2021-09-03', '2021-09-10', '2021-09-17', '2021-09-24']
['2021-01-08', '2021-01-15', '2021-01-22', '2021-01-29', '2021-02-05', '2021-02-12', '2021-02-19', '2021-02-26', '2021-03-05', '2021-03-12', '2021-03-19', '2021-03-26', '2021-04-02', '2021-04-09', '2021-04-16', '2021-04-23', '2021-04-30', '2021-05-07', '2021-05-14', '2021-05-21', '2021-05-28', '2021-06-04', '2021-06-11', '2021-06-18', '2021-06-25', '2021-07-02', '2021-07-09', '2021-07-16', '2021-07-23', '2021-07-30', '2021-08-06', '2021-08-13', '2021-08-20', '2021-08-27', '2021-09-03', '2021-09-10', '2021-09-17', '2021-09-24', '2021-09-30']
``
2024-01-17 14:31:57.378 | INFO     | metagpt.provider.openai_api:update_cost:91 - Total running cost: $0.001 | Max budget: $10.000 | Current cost: $0.001, prompt_tokens: 53, completion_tokens: 793
2024-01-17 14:31:57.380 | INFO     | __main__:main:55 - AloneCoder: 
from datetime import datetime, timedelta

def find_fridays(start_date, end_date):
    fridays = []
    start_date = datetime.strptime(start_date, '%Y-%m-%d')
    end_date = datetime.strptime(end_date, '%Y-%m-%d')

    while start_date <= end_date:
        if start_date.weekday() == 4:  # 判断星期五
            fridays.append(start_date)
        start_date += timedelta(days=1)

    return fridays

# 测试案例
test_case_1 = ('2021-09-01', '2021-09-30')
test_case_2 = ('2021-01-01', '2021-12-31')

print(find_fridays(test_case_1[0], test_case_1[1]))
print(find_fridays(test_case_2[0], test_case_2[1]))
```
运行结果:
```
['2021-09-03', '2021-09-10', '2021-09-17', '2021-09-24']
['2021-01-08', '2021-01-15', '2021-01-22', '2021-01-29', '2021-02-05', '2021-02-12', '2021-02-19', '2021-02-26', '2021-03-05', '2021-03-12', '2021-03-19', '2021-03-26', '2021-04-02', '2021-04-09', '2021-04-16', '2021-04-23', '2021-04-30', '2021-05-07', '2021-05-14', '2021-05-21', '2021-05-28', '2021-06-04', '2021-06-11', '2021-06-18', '2021-06-25', '2021-07-02', '2021-07-09', '2021-07-16', '2021-07-23', '2021-07-30', '2021-08-06', '2021-08-13', '2021-08-20', '2021-08-27', '2021-09-03', '2021-09-10', '2021-09-17', '2021-09-24', '2021-09-30']

`

实现一个多动作的Agent

对于上面仅执行一个动作的话,其实没必要这么折腾。所以Role的抽象,主要是用于动作的组合,通过连接动作,构建工作流来解决任务。

这里结合示例代码来实现一个根据自然语言描述,生成代码并允许的例子。

import os

os.environ["ZHIPUAI_API_KEY"] = "xxxxxxxxxxx"

import re
import asyncio
from metagpt.actions import Action
from metagpt.roles import Role
from metagpt.logs import logger
from metagpt.schema import Message

class Coding(Action):
    PROMPT_TEMPLATE="""
    Write a python function that can {instruction} and provide two runnnable test cases.
    Return ```python your_code_here ```with NO other texts,
    your code:
    """
    
    def __init__(self,name="Coding",context=None,llm=None):
        super().__init__(name,context,llm)
    
    async def run(self,instruction:str,**kwargs):
        prompt=self.PROMPT_TEMPLATE.format(instruction=instruction)
        rsp=await self._aask(prompt)
        code_text=Coding.parse_code(rsp)
        return code_text

    @staticmethod
    def parse_code(text:str):
        pattern =r'```python\n(.*)```'
        match=re.search(pattern,text,re.DOTALL)
        code_text=match.group(1) if match else text
        return code_text
    
class AloneCoder(Role):
    def __init__(self, name="ZhangSan", profile="AloneCoder",**kwargs):
        super().__init__(name, profile, **kwargs)
        self._init_actions(actions=[Coding])
    async def _act(self) -> Message:
        logger.info(f"{self.name} is coding...")
        logger.info(f"{self._setting}:ready to {self._rc.todo}")
        todo=self._rc.todo

        msg=self.get_memories(k=1)[0]
        code_text=await todo.run(msg.content)
        msg=Message(content=code_text,role=self.profile,cause_by=type(todo))
        return msg
    
async def main():
    msg="编写一个函数,计算输入日期范围内的所有星期五"
    role=AloneCoder()
    logger.info(msg)
    result=await role.run(with_message=msg)
    logger.info(result)

asyncio.run(main())    
    

输出:

2024-01-17 15:20:43.206 | INFO     | metagpt.const:get_metagpt_package_root:32 - Package root set to C:\Users\youxi\uenv\GPT\code
2024-01-17 15:20:44.092 | INFO     | __main__:main:74 - 编写一个函数,计算列表中数字的众数
2024-01-17 15:20:44.094 | INFO     | __main__:_act:63 - Yang(RunnableCoder):准备WriteCode
 ```python
def find_mode(data):
    if not data:
        return None
    frequency = {}
    for num in data:
        if num in frequency:
            frequency[num] += 1
        else:
            frequency[num] = 1

    max_count = 0
    mode = None
    for num, count in frequency.items():
        if count > max_count:
            max_count = count
            mode = num

    return mode

# 测试用例
data = [1, 2, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20]  
print(find_mode(data))  # 输出:5
``2024-01-17 15:21:03.128 | INFO     | metagpt.provider.openai_api:update_cost:91 - Total running cost: $0.000 | Max budget: $10.000 | Current cost: $0.000, prompt_tokens: 57, completion_tokens: 391
2024-01-17 15:21:03.130 | INFO     | __main__:_act:63 - Yang(RunnableCoder):准备RunCode
2024-01-17 15:21:03.215 | INFO     | __main__:run:46 - code_result='5\n'
2024-01-17 15:21:03.217 | INFO     | __main__:main:76 - RunnableCoder: 5

相比较单一的Agent,基于多个Agent就可以执行更多有趣的事情了。面对复杂的问题,我们可以把问题拆分开来,一点点的让大模型折腾,不过这样还是有些浪费人力,所以还是交给框架去解决。

技术文档生成助手

思路: 由于token限制,不可能一下完成很复杂的内容处理,所以就可以这么样处理:先生成大纲,然后根据大纲生成每个标题的内容,最后再合并起来内容。这里的话,就可以创建两个Action: 写大纲,写内容,然后再定义一个角色,输入需求,生成,然后合并结果。

import os
os.environ["ZHIPUAI_API_KEY"] = "xxxxxxxx"
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

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

class WriteDirectory(Action):
    """Action class for writing tutorial directories.

    Args:
        name: The name of the action.
        language: The language to output, default is "Chinese".
    """

    def __init__(self, name: str = "", language: str = "Chinese", *args, **kwargs):
        super().__init__(name, *args, **kwargs)
        self.language = language

    async def run(self, topic: str, *args, **kwargs) -> Dict:
        """Execute the action to generate a tutorial directory according to the topic.

        Args:
            topic: The tutorial topic.

        Returns:
            the tutorial directory information, including {"title": "xxx", "directory": [{"dir 1": ["sub dir 1", "sub dir 2"]}]}.
        """
        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}".
        """

        DIRECTORY_PROMPT = COMMON_PROMPT + """
        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.
        """
        prompt = DIRECTORY_PROMPT.format(topic=topic, language=self.language)
        resp = await self._aask(prompt=prompt)
        return 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".
    """

    def __init__(self, name: str = "", directory: str = "", language: str = "Chinese", *args, **kwargs):
        super().__init__(name, *args, **kwargs)
        self.language = language
        self.directory = directory

    async def run(self, topic: str, *args, **kwargs) -> str:
        """Execute the action to write document content according to the directory and topic.

        Args:
            topic: The tutorial topic.

        Returns:
            The written tutorial content.
        """
        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}".
        """
        prompt = CONTENT_PROMPT.format(
            topic=topic, language=self.language, directory=self.directory)
        return await self._aask(prompt=prompt)

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.
    """

    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__(name, profile, goal, constraints)
        self._init_actions([WriteDirectory(language=language)])
        self.topic = ""
        self.main_title = ""
        self.total_content = ""
        self.language = language

    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}"
        actions = list()
        for first_dir in titles.get("directory"):
            actions.append(WriteContent(
                language=self.language, directory=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(actions)
        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)
        if self.total_content != "":
            self.total_content += "\n\n\n"
        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())

其他尝试

嗯,其实是想写一下作业的,不过时间上可能来不及了,手头的工作总是忙不完。对于MetaGPT的API可能也得再研究下,虽然看过了视频,但是可能还是需要再多阅读会儿。然后后面再补上相关的代码。文章来源地址https://www.toymoban.com/news/detail-804750.html

到了这里,关于MetaGPT-打卡-day2,MetaGPT框架组件学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • day2:TCP、UDP网络通信模型

    思维导图 机械臂实现 按下后机械臂反应

    2024年01月16日
    浏览(30)
  • C++学习(day2)

    C语言风格的字符串依然支持,使用字符数组的形式存储字符串,字符串标志:‘\\0’ C++风格的字符串,本质上是string类的对象 使用要求:需要加头文件:#include 单个数据的初始化和赋值 方式 解释 方式1 string s2 = “ni hao”; 方式2 string s3(“shang hai”); 方式3 string s4{“zhangpeng

    2023年04月24日
    浏览(28)
  • Go学习-Day2

    个人博客 驼峰法,首字母大写可以在其他包里使用,首字母小写只能在本包内使用 跨包使用,的import地址从src的子目录开始,src以及src所在的GOPATH自动补全 定义变量 var+变量名+变量类型 自动推断类型 简略写法 对应的,可以声明多个变量 另一种声明方法,开发中常用

    2024年02月12日
    浏览(28)
  • 黑马机器学习day2

    转换器和预估器(estimator) 实例化一个转换器类        Transformer 调用fit_transform() 转换器调用有以下几种形式: fit_transform fit transform 在sklearn中,估计器是一个重要的角色,是一类实现了算法的API 1、用于分类的估计器: 1)sklearn.neighbors k近邻算法 2)sklearn.native_bayes 贝叶斯

    2024年02月13日
    浏览(29)
  • 【剑指offer】学习计划day2

    目录 一. 前言  二.从尾到头打印链表         a.题目          b.题解分析           c.AC代码   三. 反转链表          a.题目          b.题解分析         c.AC代码  四. 复杂链表的复制          a.题目          b.题解分析          c.AC代码  本系列是针对Leetcod

    2024年02月05日
    浏览(37)
  • 【cs61b】学习笔记day2

    【cs61b】学习笔记day1 思考下面两个代码分别输出什么 从下图可以看出,a和b指向的同一个实例,而x和y是两个独立的 在计算机中,数字72和大写字母H都是以01001000来存储,那么java是如何区分他们的? 答: 以类型区分 在Java, 有8 八种基本类型: byte, short, int, long, float, double, b

    2024年02月13日
    浏览(25)
  • flutter学习-day2-认识flutter

    本文学习和引用自《Flutter实战·第二版》:作者:杜文 Flutter 是 Google 推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。开发者可以通过 Dart 语言开发 App,一套代码同时运行在 iOS 和 Android平台。 Flutter 提供了丰富的组件、接口,开发者可以很快地为 Flutter 添加

    2024年02月04日
    浏览(31)
  • Vue3 学习笔记(Day2)

    「写在前面」 本文为尚硅谷禹神 Vue3 教程的学习笔记。本着自己学习、分享他人的态度,分享学习笔记,希望能对大家有所帮助。推荐先按顺序阅读往期内容: 1. Vue3 学习笔记(Day1) 目录 3 Vue3 核心语法 3.1 选项式API 与 组合式API 3.2 setup 3.3 ref 和 reactive 3.4 computed 3.5 watch 3.

    2024年02月22日
    浏览(37)
  • 【Node.js学习 day2——预备知识】

    1、概念 Buffer是一个类似于数组的对象,用于表示固定长度的字节序列 Buffer本质是一段内存空间,专门用来处理二进制数据 2、特点 Buffer大小规定且无法调整 Buffer性能较好,可以直接对计算机内存进行操作 每个元素的大小为1字节(byte) 3、使用 创建Buffer(3种方式) 4、 B

    2024年01月25日
    浏览(33)
  • Verilog语法-模块module[Day2学习笔记]

    1概述 Verilog HDL是一种用于数字逻辑电路设计的硬件描述语言,可用来进行数字电路的仿真验证、时序分析、逻辑综合。 用Verilog HDL描述的电路设计就是该电路的Verilog HDL模型。 Verilog HDL既是一种 行为 描述语言,也是一种 结构 描述语言。 既可以用电路的功能描述,也可以用

    2024年02月15日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包