2024.2 DataWhale 多智能体实战 第四章 多智能体开发

sda

项目地址:GitHub - datawhalechina/hugging-multi-agent: A tutorial to quickly help you understand the concept of agent and muti-agent and get started with coding development

4.1 Multi Agent概念概述:略,请参考项目文档

4.2 多智能体组件介绍

4.2.1开发一个简单的多智能体系统

下面我们将演示使用MetaGPT开发一个多智能体系统,这个系统中包含“学生”和“老师”两个角色。当用户输入一个主题,学生便开始以用户输入的主题创作一首诗词。而后老师会对学生的诗词提出修改意见,学生则会根据老师的意见对之前的诗词进行修改。老师和学生分别执行一次,记为一轮,智能体系统持续执行这套操作,直至达到预设的循环次数。

系统架构图如下:

代码如下:在metagpt目录下,新建write_poem.py,并复制如下代码:

import asyncio

from metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.environment import Environment

from metagpt.const import MESSAGE_ROUTE_TO_ALL


class WritePoem(Action):

    name: str = "WritePoem"

    PROMPT_TEMPLATE: str = """
    Here is the historical conversation record : {msg} .
    Write a poem about the subject provided by human, Return only the content of the generated poem with NO other texts.
    If the teacher provides suggestions about the poem, revise the student's poem based on the suggestions and return.
    your poem:
    """

    async def run(self, msg: str):

        prompt = self.PROMPT_TEMPLATE.format(msg = msg)

        rsp = await self._aask(prompt)

        return rsp

class ReviewPoem(Action):

    name: str = "ReviewPoem"

    PROMPT_TEMPLATE: str = """

    Here is the historical conversation record : {msg} .
    Check student-created poems about the subject provided by human and give your suggestions for revisions. You prefer poems with elegant sentences and retro style.
    Return only your comments with NO other texts.
    your comments:
    """

    async def run(self, msg: str):

        prompt = self.PROMPT_TEMPLATE.format(msg = msg)

        rsp = await self._aask(prompt)

        return rsp

class Student(Role):

    name: str = "xiaoming"
    profile: str = "Student"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([WritePoem])
        self._watch([UserRequirement, ReviewPoem])

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: ready to {self.rc.todo}")
        todo = self.rc.todo

        msg = self.get_memories()  # 获取所有记忆
        # logger.info(msg)
        poem_text = await WritePoem().run(msg)
        logger.info(f'student : {poem_text}')
        msg = Message(content=poem_text, role=self.profile,
                      cause_by=type(todo))

        return msg

class Teacher(Role):

    name: str = "laowang"
    profile: str = "Teacher"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([ReviewPoem])
        self._watch([WritePoem])

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: ready to {self.rc.todo}")
        todo = self.rc.todo

        msg = self.get_memories()  # 获取所有记忆
        poem_text = await ReviewPoem().run(msg)
        logger.info(f'teacher : {poem_text}')
        msg = Message(content=poem_text, role=self.profile,
                      cause_by=type(todo))

        return msg

classroom = Environment()
async def main(topic: str, n_round=3):

    classroom.add_roles([Student(), Teacher()])

    classroom.publish_message(
        Message(role="Human", content=topic, cause_by=UserRequirement,
                send_to='' or MESSAGE_ROUTE_TO_ALL),
        peekable=False,
    )

    while n_round > 0:
        # self._save()
        n_round -= 1
        logger.debug(f"max {n_round=} left.")

        await classroom.run()
    return classroom.history

asyncio.run(main(topic='wirte a poem about moon'))

运行write_poem.py,可以看到学生作诗和老师提出修改意见这两个步骤交替运行的过程

(metagpt) root@autodl-container-8fc8469a3e-392a032c:~/metagpt# python write_poem.py 
2024-03-03 22:28:59.275 | INFO     | metagpt.const:get_metagpt_package_root:32 - Package root set to /root/metagpt
2024-03-03 22:28:59.785 | INFO     | metagpt.config:get_default_llm_provider_enum:126 - API: LLMProviderEnum.ZHIPUAI
2024-03-03 22:29:07.543 | INFO     | metagpt.config:get_default_llm_provider_enum:126 - API: LLMProviderEnum.ZHIPUAI
2024-03-03 22:29:07.554 | INFO     | metagpt.config:get_default_llm_provider_enum:126 - API: LLMProviderEnum.ZHIPUAI
2024-03-03 22:29:07.556 | INFO     | __main__:_act:63 - xiaoming(Student): ready to WritePoem
2024-03-03 22:29:07.557 | INFO     | metagpt.config:get_default_llm_provider_enum:126 - API: LLMProviderEnum.ZHIPUAI
 When the moon adorns the sky,
A silver disk so bright,
It casts its glow upon the land,
A beacon in the night.

The shadows dance upon the ground,
As moonlight plays its tune,
A symphony of light and dark,
In delicate balance found.

The stars align their constellations,
In stories ancient and true,
Whose characters illuminate,
The vast expanse of blue.

The moon, a constant in our lives,
A celestial body grand,
It watches over us with grace,
Our guardian in the sand.

For when the sun has set its course,
And darkness blankets the earth,
The moon arises with purpose,
To light our path with worth.

So let us gaze upon its glow,
And drink in all its splendor,
For in its radiance we find,
The2024-03-03 22:29:18.234 | INFO     | metagpt.utils.cost_manager:update_cost:48 - Total running cost: $0.000 | Max budget: $10.000 | Current cost: $0.000, prompt_tokens: 79, completion_tokens: 201
2024-03-03 22:29:18.235 | INFO     | __main__:_act:69 - student :  When the moon adorns the sky,
A silver disk so bright,
It casts its glow upon the land,
A beacon in the night.

The shadows dance upon the ground,
As moonlight plays its tune,
A symphony of light and dark,
In delicate balance found.

The stars align their constellations,
In stories ancient and true,
Whose characters illuminate,
The vast expanse of blue.

The moon, a constant in our lives,
A celestial body grand,
It watches over us with grace,
Our guardian in the sand.

For when the sun has set its course,
And darkness blankets the earth,
The moon arises with purpose,
To light our path with worth.

So let us gaze upon its glow,
And drink in all its splendor,
For in its radiance we find,
The beauty of our own wonder.
2024-03-03 22:29:18.240 | INFO     | __main__:_act:86 - laowang(Teacher): ready to ReviewPoem
2024-03-03 22:29:18.240 | INFO     | metagpt.config:get_default_llm_provider_enum:126 - API: LLMProviderEnum.ZHIPUAI
 beauty of our own wonder. 1. Beautiful imagery and phrasing throughout the poem. The student has captured the essence of the moon and its enchanting qualities.

2. The structure of the poem is well-organized, with each stanza focusing on a different aspect of the moon's significance.

3. The use of personification adds a touch of whimsy and depth to the poem, such as describing the moon as a "beacon in the night" and a "guardian in the sand."

4. The poem incorporates intricate descriptions of the moon's impact on the natural world, such as the shadows dancing on the ground and the constellations aligning in the sky.

5. The student's use of language is evocative and flowing, creating a serene and mesmerizing atmosphere.

6. To give the poem a more retro feel, consider incorporating classical poetic devices like alliteration, assonance, or internal rhyme. For example, in the first stanza, "When the moon adorns the sky, / A silver disk so bright," the repetition of "s" sounds adds a subtle rhythm to the lines.

7. To further enhance the elegance of the poem, the student could experiment with more complex sentence structures and phrasing. For instance, in the third stanza, "The stars align their constellations, / In stories ancient and true," a more intricate sentence structure might convey a sense of awe and reverence for the celestial bodies.

8. The poem would benefit from the use of more vivid language to describe the moon's beauty and its impact on the land. For example, instead of "It casts its glow upon the land," a phrase like "It bathes the earth in ethereal radiance" would add a richer, more descriptive touch.

9. To maintain the retro style, the student could employ more archaic words or phrases, such as "thy" or "thou" to address the moon, adding a sense of timelessness to the poem.

12024-03-03 22:29:42.538 | INFO     | metagpt.utils.cost_manager:update_cost:48 - Total running cost: $0.000 | Max budget: $10.000 | Current cost: $0.000, prompt_tokens: 289, completion_tokens: 473
2024-03-03 22:29:42.539 | INFO     | __main__:_act:91 - teacher :  1. Beautiful imagery and phrasing throughout the poem. The student has captured the essence of the moon and its enchanting qualities.

2. The structure of the poem is well-organized, with each stanza focusing on a different aspect of the moon's significance.

3. The use of personification adds a touch of whimsy and depth to the poem, such as describing the moon as a "beacon in the night" and a "guardian in the sand."

4. The poem incorporates intricate descriptions of the moon's impact on the natural world, such as the shadows dancing on the ground and the constellations aligning in the sky.

5. The student's use of language is evocative and flowing, creating a serene and mesmerizing atmosphere.

6. To give the poem a more retro feel, consider incorporating classical poetic devices like alliteration, assonance, or internal rhyme. For example, in the first stanza, "When the moon adorns the sky, / A silver disk so bright," the repetition of "s" sounds adds a subtle rhythm to the lines.

7. To further enhance the elegance of the poem, the student could experiment with more complex sentence structures and phrasing. For instance, in the third stanza, "The stars align their constellations, / In stories ancient and true," a more intricate sentence structure might convey a sense of awe and reverence for the celestial bodies.

8. The poem would benefit from the use of more vivid language to describe the moon's beauty and its impact on the land. For example, instead of "It casts its glow upon the land," a phrase like "It bathes the earth in ethereal radiance" would add a richer, more descriptive touch.

9. To maintain the retro style, the student could employ more archaic words or phrases, such as "thy" or "thou" to address the moon, adding a sense of timelessness to the poem.

10. Finally, the poem could be further improved by refining the phrasing and ensuring that each line flows seamlessly into the next. This will create a more immersive and evocative reading experience.
2024-03-03 22:29:42.543 | INFO     | __main__:_act:63 - xiaoming(Student): ready to WritePoem
2024-03-03 22:29:42.543 | INFO     | metagpt.config:get_default_llm_provider_enum:126 - API: LLMProviderEnum.ZHIPUAI
0. Finally, the poem could be further improved by refining the phrasing and ensuring that each line flows seamlessly into the next. This will create a more immersive and evocative reading experience. When the moon adorns the sky,
A silver disk so bright,
It bathes the earth in ethereal radiance,
A beacon in the night.

The shadows dance upon the ground,
As moonlight plays its tune,
A symphony of light and dark,
In delicate balance found.

The stars align their constellations,
In stories ancient and true,
Whose characters illuminate,
The vast expanse of blue.

The moon, a constant in our lives,
A celestial body grand,
It watches over us with grace,
Our guardian in the sand.

For when the sun has set its course,
And darkness blankets the earth,
The moon arises with purpose,
To light our path with worth.

So let us gaze upon its glow,
And drink in all its splendor,
For in its radiance we find,
The2024-03-03 22:29:53.205 | INFO     | metagpt.utils.cost_manager:update_cost:48 - Total running cost: $0.001 | Max budget: $10.000 | Current cost: $0.000, prompt_tokens: 802, completion_tokens: 204
2024-03-03 22:29:53.206 | INFO     | __main__:_act:69 - student :  When the moon adorns the sky,
A silver disk so bright,
It bathes the earth in ethereal radiance,
A beacon in the night.

The shadows dance upon the ground,
As moonlight plays its tune,
A symphony of light and dark,
In delicate balance found.

The stars align their constellations,
In stories ancient and true,
Whose characters illuminate,
The vast expanse of blue.

The moon, a constant in our lives,
A celestial body grand,
It watches over us with grace,
Our guardian in the sand.

For when the sun has set its course,
And darkness blankets the earth,
The moon arises with purpose,
To light our path with worth.

So let us gaze upon its glow,
And drink in all its splendor,
For in its radiance we find,
The beauty of our own wonder.

代码解析:

详细解析请参考文章顶部项目链接中的4.2.2,我们这里仅对代码结构做概要解读。

要实现这么一个复杂的多智能体系统,我们需要定义若干动作(Action),角色(Role)以及环境(Environment),本次demo的智能体系统包括了:

1. Action类WritePoem:学生根据用户输入的主题作诗,以及根据老师的修改意见对诗词进行修改

2. Action类ReviewPoem:老师根据学生做的诗,提出修改意见

3. Role类Student:学生,特别注意的是:

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([WritePoem])
        self._watch([UserRequirement, ReviewPoem])

  Student的__init__函数中,对WritePoem动作进行初始化;同时监听UserRequirement(即用户输入想要作诗的主题)以及ReviewPoem(老师对诗词提出修改意见)两个动作,可以理解为学生在收到在用户输入或者老师提出修改意见时触发行动。

4.Role类Teacher:老师,需要注意的是:

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._init_actions([ReviewPoem])
        self._watch([WritePoem])

Teacher的__init__函数中,对ReviewPoem动作进行初始化,同时监听WritePoem,可以理解为老师在看到学生写的诗时触发行动。

4.2.2 基于Team开发一个智能体团队

Team是基于Environment二次封装的产物,相比于Environment,Team提供了以下更多支持:(1)investment, 用于管理团队成本(即限制token花费)

(2)idea,效果则等同于告诉你的团队接下来该围绕什么工作

关于Team的详细介绍请参考文章顶部项目链接中的4.2.3章节,我们会在下面的代码解读中对比Team和Environment在代码中的异同。

下面我们将演示适用Team构建一个智能体团队,它可以:

(1) 根据用户需求编写python代码

(2)为上一步生成的python代码编写测试用例代码

(3)对上一步生成的测试用例进行审查,并给出改进意见

步骤如下:在metagpt目录下新建team_coder.py,并复制以下代码:

"""
Filename: MetaGPT/examples/build_customized_multi_agents.py
Created Date: Wednesday, November 15th 2023, 7:12:39 pm
Author: garylin2099
"""
import re

import fire

from metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.team import Team


def parse_code(rsp):
    pattern = r"```python(.*)```"
    match = re.search(pattern, rsp, re.DOTALL)
    code_text = match.group(1) if match else rsp
    return code_text


class SimpleWriteCode(Action):
    PROMPT_TEMPLATE: str = """
    Write a python function that can {instruction}.
    Return ```python your_code_here ``` with NO other texts,
    your code:
    """
    name: str = "SimpleWriteCode"

    async def run(self, instruction: str):
        prompt = self.PROMPT_TEMPLATE.format(instruction=instruction)

        rsp = await self._aask(prompt)

        code_text = parse_code(rsp)

        return code_text


class SimpleCoder(Role):
    name: str = "Alice"
    profile: str = "SimpleCoder"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._watch([UserRequirement])
        self.init_actions([SimpleWriteCode])


class SimpleWriteTest(Action):
    PROMPT_TEMPLATE: str = """
    Context: {context}
    Write {k} unit tests using pytest for the given function, assuming you have imported it.
    Return ```python your_code_here ``` with NO other texts,
    your code:
    """

    name: str = "SimpleWriteTest"

    async def run(self, context: str, k: int = 3):
        prompt = self.PROMPT_TEMPLATE.format(context=context, k=k)

        rsp = await self._aask(prompt)

        code_text = parse_code(rsp)

        return code_text


class SimpleTester(Role):
    name: str = "Bob"
    profile: str = "SimpleTester"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.init_actions([SimpleWriteTest])
        self._watch([SimpleWriteCode])
        # self._watch([SimpleWriteCode, SimpleWriteReview])  # feel free to try this too

    async def _act(self) -> Message:
        logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
        todo = self.rc.todo

        context = self.get_memories(k=1)[0].content # use the most recent memory as context
        # context = self.get_memories()  # use all memories as context

        code_text = await todo.run(context, k=5)  # specify arguments
        msg = Message(content=code_text, role=self.profile, cause_by=type(todo))

        return msg


class SimpleWriteReview(Action):
    PROMPT_TEMPLATE: str = """
    Context: {context}
    Review the test cases and provide one critical comments:
    """

    name: str = "SimpleWriteReview"

    async def run(self, context: str):
        prompt = self.PROMPT_TEMPLATE.format(context=context)

        rsp = await self._aask(prompt)

        return rsp


class SimpleReviewer(Role):
    name: str = "Charlie"
    profile: str = "SimpleReviewer"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.init_actions([SimpleWriteReview])
        self._watch([SimpleWriteTest])


async def main(
    idea: str = "write a function that calculates the product of a list",
    investment: float = 3.0,
    n_round: int = 5,
    add_human: bool = False,
):
    logger.info(idea)

    team = Team()
    team.hire(
        [
            SimpleCoder(),
            SimpleTester(),
            SimpleReviewer(is_human=add_human),
        ]
    )

    team.invest(investment=investment)
    team.run_project(idea)
    await team.run(n_round=n_round)


if __name__ == "__main__":
    fire.Fire(main)

运行team_coder.py,结果如下:

(metagpt) root@autodl-container-8fc8469a3e-392a032c:~/metagpt# python team_coder.py 
2024-03-07 21:59:32.082 | INFO     | metagpt.const:get_metagpt_package_root:32 - Package root set to /root/metagpt
2024-03-07 21:59:32.562 | INFO     | metagpt.config:get_default_llm_provider_enum:126 - API: LLMProviderEnum.ZHIPUAI
2024-03-07 21:59:39.347 | INFO     | __main__:main:127 - write a function that calculates the product of a list
2024-03-07 21:59:39.362 | INFO     | metagpt.config:get_default_llm_provider_enum:126 - API: LLMProviderEnum.ZHIPUAI
2024-03-07 21:59:39.372 | INFO     | metagpt.config:get_default_llm_provider_enum:126 - API: LLMProviderEnum.ZHIPUAI
2024-03-07 21:59:39.373 | INFO     | metagpt.config:get_default_llm_provider_enum:126 - API: LLMProviderEnum.ZHIPUAI
2024-03-07 21:59:39.373 | INFO     | metagpt.team:invest:86 - Investment: $3.0.
2024-03-07 21:59:39.375 | INFO     | metagpt.roles.role:_act:357 - Alice(SimpleCoder): to do SimpleWriteCode(SimpleWriteCode)
 def product_of_list(lst):
    return reduce(lambda x, y: x * y, lst)

# Example usage:
lst = [2, 3, 4, 5]
result = product_of_list(lst)
print2024-03-07 21:59:43.481 | INFO     | metagpt.utils.cost_manager:update_cost:48 - Total running cost: $0.000 | Max budget: $10.000 | Current cost: $0.000, prompt_tokens: 48, completion_tokens: 74
2024-03-07 21:59:43.489 | INFO     | __main__:_act:83 - Bob(SimpleTester): to do SimpleWriteTest(SimpleWriteTest)
(result)  # Output: 120 ```python
import pytest
from typing import List

def product_of_list(lst: List[int]) -> int:
    return reduce(lambda x, y: x * y, lst)

@pytest.mark.parametrize("input_list, expected_output", [
    ([2, 3, 4, 5], 120),
    ([1, 2, 3, 4], 24),
    ([6, 5, 4, 3], 72),
    ([-1, 2, -3, 4], -24),
    ([0, 1, 2, 3], 0),
])
def test_product_of_list(input_list: List[int], expected_output: int):
    assert product_of_list(input_list) == expected_output

@pytest.mark.parametrize("input_list", [
    [],
    [1],
    [2, 2],
    [2, 3, 2],
    [2, 3, 4, 5, 2],
])
def test_product_of_empty_list_and_single_elements(input_list: List[int]):
    assert product_of_list(input_list) == 1

@pytest.mark.parametrize("input_list", [
    [1, 2, 3, 4, 5],
    [2, 3, 4, 5, 6],
    [6, 5, 4, 3, 2],
    [5, 4, 3, 2, 1],
    [1, 2, 3, 4, 5, 6],
])
def test_product_of_list_with_repeated_elements(input_list: List[int]):
    assert product_of_list(input_list) == 120

@pytest.mark.parametrize("input_list", [
    [1, 2, 3, 4, 5],
    [2, 3, 4, 5, 6],
    [6, 5, 4, 3, 2],
    [5, 4, 3, 2, 1],
    [1, 2, 3, 4, 5, 6],
])
def test_product_of_list_with_negative_elements(input_list: List[int]):
    assert product_of_list(input_list) == 120

@pytest.mark.parametrize("input_list", [
    [1, 2, 3, 4, 5],
    [2, 3, 4, 5, 6],
    [6, 5, 4, 3, 2],
    [5, 4, 3, 2, 1],
    [1, 2, 3, 4, 5, 6],
])
def test_product_of_list_with_floats(input_list: List[float]):
    assert product_of_list(input_list) == 120
``2024-03-07 22:00:23.635 | INFO     | metagpt.utils.cost_manager:update_cost:48 - Total running cost: $0.001 | Max budget: $10.000 | Current cost: $0.001, prompt_tokens: 120, completion_tokens: 777
2024-03-07 22:00:23.641 | INFO     | metagpt.roles.role:_act:357 - Charlie(SimpleReviewer): to do SimpleWriteReview(SimpleWriteReview)
2024-03-07 22:00:30.478 | INFO     | metagpt.utils.cost_manager:update_cost:48 - Total running cost: $0.001 | Max budget: $10.000 | Current cost: $0.000, prompt_tokens: 879, completion_tokens: 119
` Critical comment: The test cases provided cover a variety of scenarios, including different lists with integers, repeated elements, negative numbers, and empty lists. However, there is no test case that checks for invalid input types, such as lists with non-integer elements or strings. It would be beneficial to add test cases that cover these scenarios to ensure the function works correctly with different input types. Additionally, it would be useful to include test cases that test the behavior of the function when the input list is empty or contains only one element, as this is not covered in the existing test cases.

首先Alice(SimpleCoder)根据用户的需求"write a function that calculates the product of a list"书写了一段python代码,

然后Bob(SimpleTester)为Alice的代码编写了一些pytest测试,测试代码中可以发现一些明显的错误,比如 “([6, 5, 4, 3], 72),”,6*5*4*3不等于72,

最后Charlie(SimpleReviewer)对Bob编写的测试做出评价,可以发现的是Charlie的评论中并未提及Bob代码中的错误。

代码解读:

本代码包括了以下几个组成部分:

(1)Action类SimpleWriteCode:根据用户需求编写python代码

(2)Action类SimpleWriteTest:为SimpleWriteCode生成的代码编写测试用例

(3)Action类SimpleWriteReview:对SimpleWriteTest生成的测试用例进行评价,给出建议

(4)Role类SimpleCoder:根据用户需求编写python代码,与SimpleWriteCode绑定

(5)Role类SimpleTester:为SimpleWriteCode生成的代码编写测试用例。与SimpleWriteTest绑定

(6)Role类SimpleReviewer:对SimpleWriteTest生成的测试用例进行评价,给出建议。与SimpleWriteReview绑定

(7)main函数:创建一个Team对象,将上述三个Role加入Team并执行主程序。

Team与Environment对比:

上一章节我们用Environment创建了一个可以作诗、改诗的智能体系统,本章则是用Team(Environment的更高层次封装)构造了一个写代码和测试的智能体系统。二者对比如下:

Environment截图:

Team截图:

我们可以发现:

1. Environment使用add_roles()函数来为当前环境增添角色,而Team采用hire()函数来“雇佣”角色
2. Environment中的public_message()代码,在Team中被封装成了run_project()函数;类似的,Environment中的while loop在Team中被封装成了run()函数;相比之下Team的呈现更加简洁。

  • 19
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值