mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-17 15:35:21 +02:00
Merge branch 'minecraft' of github.com:geekan/MetaGPT into minecraft
This commit is contained in:
commit
eb9ea304a5
215 changed files with 10530 additions and 1257 deletions
54
tests/metagpt/actions/test_clone_function.py
Normal file
54
tests/metagpt/actions/test_clone_function.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.clone_function import CloneFunction, run_function_code
|
||||
|
||||
|
||||
source_code = """
|
||||
import pandas as pd
|
||||
import ta
|
||||
|
||||
def user_indicator():
|
||||
# 读取股票数据
|
||||
stock_data = pd.read_csv('./tests/data/baba_stock.csv')
|
||||
stock_data.head()
|
||||
# 计算简单移动平均线
|
||||
stock_data['SMA'] = ta.trend.sma_indicator(stock_data['Close'], window=6)
|
||||
stock_data[['Date', 'Close', 'SMA']].head()
|
||||
# 计算布林带
|
||||
stock_data['bb_upper'], stock_data['bb_middle'], stock_data['bb_lower'] = ta.volatility.bollinger_hband_indicator(stock_data['Close'], window=20), ta.volatility.bollinger_mavg(stock_data['Close'], window=20), ta.volatility.bollinger_lband_indicator(stock_data['Close'], window=20)
|
||||
stock_data[['Date', 'Close', 'bb_upper', 'bb_middle', 'bb_lower']].head()
|
||||
"""
|
||||
|
||||
template_code = """
|
||||
def stock_indicator(stock_path: str, indicators=['Simple Moving Average', 'BollingerBands', 'MACD]) -> pd.DataFrame:
|
||||
import pandas as pd
|
||||
# here is your code.
|
||||
"""
|
||||
|
||||
|
||||
def get_expected_res():
|
||||
import pandas as pd
|
||||
import ta
|
||||
|
||||
# 读取股票数据
|
||||
stock_data = pd.read_csv('./tests/data/baba_stock.csv')
|
||||
stock_data.head()
|
||||
# 计算简单移动平均线
|
||||
stock_data['SMA'] = ta.trend.sma_indicator(stock_data['Close'], window=6)
|
||||
stock_data[['Date', 'Close', 'SMA']].head()
|
||||
# 计算布林带
|
||||
stock_data['bb_upper'], stock_data['bb_middle'], stock_data['bb_lower'] = ta.volatility.bollinger_hband_indicator(stock_data['Close'], window=20), ta.volatility.bollinger_mavg(stock_data['Close'], window=20), ta.volatility.bollinger_lband_indicator(stock_data['Close'], window=20)
|
||||
stock_data[['Date', 'Close', 'bb_upper', 'bb_middle', 'bb_lower']].head()
|
||||
return stock_data
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clone_function():
|
||||
clone = CloneFunction()
|
||||
code = await clone.run(template_code, source_code)
|
||||
assert 'def ' in code
|
||||
stock_path = './tests/data/baba_stock.csv'
|
||||
df, msg = run_function_code(code, 'stock_indicator', stock_path)
|
||||
assert not msg
|
||||
expected_df = get_expected_res()
|
||||
assert df.equals(expected_df)
|
||||
|
|
@ -9,6 +9,7 @@ import pytest
|
|||
|
||||
from metagpt.actions.design_api import WriteDesign
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import Message
|
||||
from tests.metagpt.actions.mock import PRD_SAMPLE
|
||||
|
||||
|
||||
|
|
@ -18,9 +19,10 @@ async def test_design_api():
|
|||
|
||||
design_api = WriteDesign("design_api")
|
||||
|
||||
result = await design_api.run(prd)
|
||||
result = await design_api.run([Message(content=prd, instruct_content=None)])
|
||||
logger.info(result)
|
||||
assert len(result) > 0
|
||||
|
||||
assert result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -28,7 +30,7 @@ async def test_design_api_calculator():
|
|||
prd = PRD_SAMPLE
|
||||
|
||||
design_api = WriteDesign("design_api")
|
||||
result = await design_api.run(prd)
|
||||
result = await design_api.run([Message(content=prd, instruct_content=None)])
|
||||
logger.info(result)
|
||||
|
||||
assert len(result) > 10
|
||||
assert result
|
||||
|
|
|
|||
23
tests/metagpt/actions/test_detail_mining.py
Normal file
23
tests/metagpt/actions/test_detail_mining.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2023/9/13 00:26
|
||||
@Author : fisherdeng
|
||||
@File : test_detail_mining.py
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from metagpt.actions.detail_mining import DetailMining
|
||||
from metagpt.logs import logger
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_detail_mining():
|
||||
topic = "如何做一个生日蛋糕"
|
||||
record = "我认为应该先准备好材料,然后再开始做蛋糕。"
|
||||
detail_mining = DetailMining("detail_mining")
|
||||
rsp = await detail_mining.run(topic=topic, record=record)
|
||||
logger.info(f"{rsp.content=}")
|
||||
|
||||
assert '##OUTPUT' in rsp.content
|
||||
assert '蛋糕' in rsp.content
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ async def test_write_test():
|
|||
code_to_test=code,
|
||||
test_file_name="test_food.py",
|
||||
source_file_path="/some/dummy/path/cli_snake_game/cli_snake_game/food.py",
|
||||
workspace="/some/dummy/path/cli_snake_game"
|
||||
workspace="/some/dummy/path/cli_snake_game",
|
||||
)
|
||||
logger.info(test_code)
|
||||
|
||||
|
|
@ -40,3 +40,18 @@ async def test_write_test():
|
|||
assert "from cli_snake_game.food import Food" in test_code
|
||||
assert "class TestFood(unittest.TestCase)" in test_code
|
||||
assert "def test_generate" in test_code
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_code_invalid_code(mocker):
|
||||
# Mock the _aask method to return an invalid code string
|
||||
mocker.patch.object(WriteTest, "_aask", return_value="Invalid Code String")
|
||||
|
||||
# Create an instance of WriteTest
|
||||
write_test = WriteTest()
|
||||
|
||||
# Call the write_code method
|
||||
code = await write_test.write_code("Some prompt:")
|
||||
|
||||
# Assert that the returned code is the same as the invalid code string
|
||||
assert code == "Invalid Code String"
|
||||
|
|
|
|||
40
tests/metagpt/actions/test_write_tutorial.py
Normal file
40
tests/metagpt/actions/test_write_tutorial.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
# _*_ coding: utf-8 _*_
|
||||
"""
|
||||
@Time : 2023/9/6 21:41:34
|
||||
@Author : Stitch-z
|
||||
@File : test_write_tutorial.py
|
||||
"""
|
||||
from typing import Dict
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions.write_tutorial import WriteDirectory, WriteContent
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("language", "topic"),
|
||||
[("English", "Write a tutorial about Python")]
|
||||
)
|
||||
async def test_write_directory(language: str, topic: str):
|
||||
ret = await WriteDirectory(language=language).run(topic=topic)
|
||||
assert isinstance(ret, dict)
|
||||
assert "title" in ret
|
||||
assert "directory" in ret
|
||||
assert isinstance(ret["directory"], list)
|
||||
assert len(ret["directory"])
|
||||
assert isinstance(ret["directory"][0], dict)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("language", "topic", "directory"),
|
||||
[("English", "Write a tutorial about Python", {"Introduction": ["What is Python?", "Why learn Python?"]})]
|
||||
)
|
||||
async def test_write_content(language: str, topic: str, directory: Dict):
|
||||
ret = await WriteContent(language=language, directory=directory).run(topic=topic)
|
||||
assert isinstance(ret, str)
|
||||
assert list(directory.keys())[0] in ret
|
||||
for value in list(directory.values())[0]:
|
||||
assert value in ret
|
||||
7
tests/metagpt/planner/__init__.py
Normal file
7
tests/metagpt/planner/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2023/9/16 20:03
|
||||
@Author : femto Zheng
|
||||
@File : __init__.py
|
||||
"""
|
||||
29
tests/metagpt/planner/test_action_planner.py
Normal file
29
tests/metagpt/planner/test_action_planner.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2023/9/16 20:03
|
||||
@Author : femto Zheng
|
||||
@File : test_basic_planner.py
|
||||
"""
|
||||
import pytest
|
||||
from semantic_kernel.core_skills import FileIOSkill, MathSkill, TextSkill, TimeSkill
|
||||
from semantic_kernel.planning.action_planner.action_planner import ActionPlanner
|
||||
|
||||
from metagpt.actions import BossRequirement
|
||||
from metagpt.roles.sk_agent import SkAgent
|
||||
from metagpt.schema import Message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_planner():
|
||||
role = SkAgent(planner_cls=ActionPlanner)
|
||||
# let's give the agent 4 skills
|
||||
role.import_skill(MathSkill(), "math")
|
||||
role.import_skill(FileIOSkill(), "fileIO")
|
||||
role.import_skill(TimeSkill(), "time")
|
||||
role.import_skill(TextSkill(), "text")
|
||||
task = "What is the sum of 110 and 990?"
|
||||
role.recv(Message(content=task, cause_by=BossRequirement))
|
||||
|
||||
await role._think() # it will choose mathskill.Add
|
||||
assert "1100" == (await role._act()).content
|
||||
34
tests/metagpt/planner/test_basic_planner.py
Normal file
34
tests/metagpt/planner/test_basic_planner.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2023/9/16 20:03
|
||||
@Author : femto Zheng
|
||||
@File : test_basic_planner.py
|
||||
"""
|
||||
import pytest
|
||||
from semantic_kernel.core_skills import TextSkill
|
||||
|
||||
from metagpt.actions import BossRequirement
|
||||
from metagpt.const import SKILL_DIRECTORY
|
||||
from metagpt.roles.sk_agent import SkAgent
|
||||
from metagpt.schema import Message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_planner():
|
||||
task = """
|
||||
Tomorrow is Valentine's day. I need to come up with a few date ideas. She speaks French so write it in French.
|
||||
Convert the text to uppercase"""
|
||||
role = SkAgent()
|
||||
|
||||
# let's give the agent some skills
|
||||
role.import_semantic_skill_from_directory(SKILL_DIRECTORY, "SummarizeSkill")
|
||||
role.import_semantic_skill_from_directory(SKILL_DIRECTORY, "WriterSkill")
|
||||
role.import_skill(TextSkill(), "TextSkill")
|
||||
# using BasicPlanner
|
||||
role.recv(Message(content=task, cause_by=BossRequirement))
|
||||
await role._think()
|
||||
# assuming sk_agent will think he needs WriterSkill.Brainstorm and WriterSkill.Translate
|
||||
assert "WriterSkill.Brainstorm" in role.plan.generated_plan.result
|
||||
assert "WriterSkill.Translate" in role.plan.generated_plan.result
|
||||
# assert "SALUT" in (await role._act()).content #content will be some French
|
||||
93
tests/metagpt/roles/minecraft/test_action_developer.py
Normal file
93
tests/metagpt/roles/minecraft/test_action_developer.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import asyncio
|
||||
|
||||
from metagpt.minecraft_team import GameEnvironment
|
||||
from metagpt.roles.minecraft.action_developer import ActionDeveloper
|
||||
from metagpt.logs import logger
|
||||
|
||||
|
||||
async def main():
|
||||
events = [
|
||||
[
|
||||
"observe",
|
||||
{
|
||||
"voxels": ["grass_block", "dirt", "grass"],
|
||||
"status": {
|
||||
"health": 20,
|
||||
"food": 20,
|
||||
"saturation": 5,
|
||||
"oxygen": 20,
|
||||
"position": {"x": 0.5, "y": 84, "z": -207.5},
|
||||
"velocity": {"x": 0, "y": -0.0784000015258789, "z": 0},
|
||||
"yaw": 3.141592653589793,
|
||||
"pitch": 0,
|
||||
"onGround": True,
|
||||
"equipment": [None, None, None, None, None, None],
|
||||
"name": "bot",
|
||||
"isInWater": False,
|
||||
"isInLava": False,
|
||||
"isCollidedHorizontally": False,
|
||||
"isCollidedVertically": True,
|
||||
"biome": "plains",
|
||||
"entities": {
|
||||
"chicken": 29.071822119730644,
|
||||
"sheep": 20.361212992763768,
|
||||
},
|
||||
"timeOfDay": "day",
|
||||
"inventoryUsed": 0,
|
||||
"elapsedTime": 41,
|
||||
},
|
||||
"inventory": {},
|
||||
"nearbyChests": {"(1344, 64, 1381)": "Unknown"},
|
||||
"blockRecords": ["grass_block", "dirt", "grass"],
|
||||
},
|
||||
]
|
||||
]
|
||||
|
||||
code = """
|
||||
async function collectBamboo(bot) {
|
||||
// Equip the iron sword
|
||||
const ironSword = bot.inventory.findInventoryItem(mcData.itemsByName.iron_sword.id);
|
||||
await bot.equip(ironSword, "hand");
|
||||
|
||||
// Find bamboo plants using the exploreUntil function
|
||||
const bambooPlants = await exploreUntil(bot, new Vec3(1, 0, 1), 60, () => {
|
||||
const bambooPlants = bot.findBlocks({
|
||||
matching: block => block.name === "bamboo",
|
||||
maxDistance: 32,
|
||||
count: 10
|
||||
});
|
||||
return bambooPlants.length >= 10 ? bambooPlants : null;
|
||||
});
|
||||
if (!bambooPlants) {
|
||||
bot.chat("Could not find enough bamboo plants.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Break 10 bamboo plants using the iron sword
|
||||
for (const bambooPlant of bambooPlants) {
|
||||
const block = bot.blockAt(bambooPlant);
|
||||
await bot.dig(block);
|
||||
}
|
||||
bot.chat("Broke 10 bamboo plants.");
|
||||
|
||||
// Collect the dropped bamboo items
|
||||
for (const bambooPlant of bambooPlants) {
|
||||
await bot.pathfinder.goto(new GoalBlock(bambooPlant.x, bambooPlant.y, bambooPlant.z));
|
||||
}
|
||||
bot.chat("Collected 10 bamboo.");
|
||||
}
|
||||
"""
|
||||
ad = ActionDeveloper()
|
||||
ge = GameEnvironment()
|
||||
ge.update_event(events)
|
||||
ad.set_memory(shared_memory=ge)
|
||||
msg = ad.encapsule_message(events=ge.event, code=code)
|
||||
logger.info(f"Encapsuled_message: {msg}")
|
||||
|
||||
parsed_result = await ad.generate_action_code(**msg)
|
||||
|
||||
logger.info(f"Parsed_code_updating: {parsed_result}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
64
tests/metagpt/roles/minecraft/test_critic_agent.py
Normal file
64
tests/metagpt/roles/minecraft/test_critic_agent.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import asyncio
|
||||
|
||||
from metagpt.minecraft_team import GameEnvironment
|
||||
from metagpt.roles.minecraft.critic_agent import CriticReviewer
|
||||
from metagpt.logs import logger
|
||||
|
||||
|
||||
async def main():
|
||||
events = [
|
||||
[
|
||||
"observe",
|
||||
{
|
||||
"voxels": ["grass_block", "dirt", "grass"],
|
||||
"status": {
|
||||
"health": 20,
|
||||
"food": 20,
|
||||
"saturation": 5,
|
||||
"oxygen": 20,
|
||||
"position": {"x": 0.5, "y": 84, "z": -207.5},
|
||||
"velocity": {"x": 0, "y": -0.0784000015258789, "z": 0},
|
||||
"yaw": 3.141592653589793,
|
||||
"pitch": 0,
|
||||
"onGround": True,
|
||||
"equipment": [None, None, None, None, None, None],
|
||||
"name": "bot",
|
||||
"isInWater": False,
|
||||
"isInLava": False,
|
||||
"isCollidedHorizontally": False,
|
||||
"isCollidedVertically": True,
|
||||
"biome": "plains",
|
||||
"entities": {
|
||||
"chicken": 29.071822119730644,
|
||||
"sheep": 20.361212992763768,
|
||||
},
|
||||
"timeOfDay": "day",
|
||||
"inventoryUsed": 0,
|
||||
"elapsedTime": 41,
|
||||
},
|
||||
"inventory": {},
|
||||
"nearbyChests": {"(1344, 64, 1381)": "Unknown"},
|
||||
"blockRecords": ["grass_block", "dirt", "grass"],
|
||||
},
|
||||
]
|
||||
]
|
||||
task = "Obtain 3 more spruce logs"
|
||||
chest_observation = "Chests: None\n\n"
|
||||
|
||||
context = "Question: How to obtain 3 more spruce logs in Minecraft?\nAnswer: You can obtain more spruce logs in Minecraft by finding and chopping down spruce trees in a spruce forest biome. If you have already chopped down all the spruce trees in the area, you can either explore further to find more spruce trees or plant saplings and wait for them to grow into trees."
|
||||
cr = CriticReviewer()
|
||||
ge = GameEnvironment()
|
||||
ge.update_event(events)
|
||||
cr.set_memory(shared_memory=ge)
|
||||
msg = cr.encapsule_message(
|
||||
events=ge.event, task=task, context=context, chest_observation=chest_observation
|
||||
)
|
||||
logger.info(f"Encapsuled_message: {msg}")
|
||||
|
||||
verify = await cr.verify_task(**msg)
|
||||
|
||||
logger.info(f"Parsed_code_updating: {verify}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
67
tests/metagpt/roles/minecraft/test_curriculum_agent.py
Normal file
67
tests/metagpt/roles/minecraft/test_curriculum_agent.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import asyncio
|
||||
|
||||
from metagpt.minecraft_team import GameEnvironment
|
||||
from metagpt.roles.minecraft.curriculum_agent import CurriculumDesigner
|
||||
from metagpt.logs import logger
|
||||
|
||||
|
||||
async def main():
|
||||
events = [
|
||||
[
|
||||
"observe",
|
||||
{
|
||||
"voxels": ["grass_block", "dirt", "grass"],
|
||||
"status": {
|
||||
"health": 20,
|
||||
"food": 20,
|
||||
"saturation": 5,
|
||||
"oxygen": 20,
|
||||
"position": {"x": 0.5, "y": 84, "z": -207.5},
|
||||
"velocity": {"x": 0, "y": -0.0784000015258789, "z": 0},
|
||||
"yaw": 3.141592653589793,
|
||||
"pitch": 0,
|
||||
"onGround": True,
|
||||
"equipment": [None, None, None, None, None, None],
|
||||
"name": "bot",
|
||||
"isInWater": False,
|
||||
"isInLava": False,
|
||||
"isCollidedHorizontally": False,
|
||||
"isCollidedVertically": True,
|
||||
"biome": "plains",
|
||||
"entities": {
|
||||
"chicken": 29.071822119730644,
|
||||
"sheep": 20.361212992763768,
|
||||
},
|
||||
"timeOfDay": "day",
|
||||
"inventoryUsed": 0,
|
||||
"elapsedTime": 41,
|
||||
},
|
||||
"inventory": {},
|
||||
"nearbyChests": {"(1344, 64, 1381)": "Unknown"},
|
||||
"blockRecords": ["grass_block", "dirt", "grass"],
|
||||
},
|
||||
]
|
||||
]
|
||||
|
||||
cd = CurriculumDesigner()
|
||||
ge = GameEnvironment()
|
||||
ge.update_event(events)
|
||||
cd.set_memory(shared_memory=ge)
|
||||
|
||||
task_msg = cd.encapsule_design_task_message(
|
||||
events=ge.event, chest_observation=ge.chest_observation
|
||||
)
|
||||
logger.info(f"Encapsuled_design_task_message: {task_msg}")
|
||||
task = await cd.handle_task_design(**task_msg)
|
||||
logger.info(f"Design_task_updating: {task}")
|
||||
|
||||
context_msg = cd.encapsule_design_curriculum_message(
|
||||
events=ge.event, chest_observation=ge.chest_observation
|
||||
)
|
||||
logger.info(f"Encapsuled_design_context_message: {context_msg}")
|
||||
context = await cd.handle_curriculum_design(**context_msg)
|
||||
logger.info(f"Design_context_updating: {context}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
93
tests/metagpt/roles/minecraft/test_skill_manager.py
Normal file
93
tests/metagpt/roles/minecraft/test_skill_manager.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import asyncio
|
||||
|
||||
from metagpt.minecraft_team import GameEnvironment
|
||||
from metagpt.roles.minecraft.skill_manager import SkillManager
|
||||
from metagpt.logs import logger
|
||||
from metagpt.actions.minecraft.manage_skills import (
|
||||
GenerateSkillDescription,
|
||||
RetrieveSkills,
|
||||
AddNewSkills,
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
events = [
|
||||
[
|
||||
"observe",
|
||||
{
|
||||
"voxels": ["grass_block", "dirt", "grass"],
|
||||
"status": {
|
||||
"health": 20,
|
||||
"food": 20,
|
||||
"saturation": 5,
|
||||
"oxygen": 20,
|
||||
"position": {"x": 0.5, "y": 84, "z": -207.5},
|
||||
"velocity": {"x": 0, "y": -0.0784000015258789, "z": 0},
|
||||
"yaw": 3.141592653589793,
|
||||
"pitch": 0,
|
||||
"onGround": True,
|
||||
"equipment": [None, None, None, None, None, None],
|
||||
"name": "bot",
|
||||
"isInWater": False,
|
||||
"isInLava": False,
|
||||
"isCollidedHorizontally": False,
|
||||
"isCollidedVertically": True,
|
||||
"biome": "plains",
|
||||
"entities": {
|
||||
"chicken": 29.071822119730644,
|
||||
"sheep": 20.361212992763768,
|
||||
},
|
||||
"timeOfDay": "day",
|
||||
"inventoryUsed": 0,
|
||||
"elapsedTime": 41,
|
||||
},
|
||||
"inventory": {},
|
||||
"nearbyChests": {"(1344, 64, 1381)": "Unknown"},
|
||||
"blockRecords": ["grass_block", "dirt", "grass"],
|
||||
},
|
||||
]
|
||||
]
|
||||
program_code = 'async function obtainSpruceLogs(bot) {\n // Find 3 spruce_log blocks\n const spruceLogs = await exploreUntil(bot, new Vec3(1, 0, 1), 60, () => {\n const spruceLog = bot.findBlock({\n matching: mcData.blocksByName["spruce_log"].id,\n maxDistance: 32,\n count: 3\n });\n return spruceLog ? spruceLog : null;\n });\n if (spruceLogs) {\n // Mine the spruce_log blocks\n await mineBlock(bot, "spruce_log", 3);\n bot.chat("3 spruce logs obtained.");\n } else {\n bot.chat("Could not find enough spruce logs.");\n }\n}'
|
||||
program_name = "obtainSpruceLogs"
|
||||
task = "Obtain 3 more spruce logs"
|
||||
skills = {
|
||||
"mineWoodLog": {
|
||||
"code": 'async function mineWoodLog(bot) {\n const woodLogNames = ["oak_log", "birch_log", "spruce_log", "jungle_log", "acacia_log", "dark_oak_log", "mangrove_log"];\n\n // Find a wood log block\n const woodLog = await exploreUntil(bot, new Vec3(1, 0, 1), 60, () => {\n for (const name of woodLogNames) {\n const log = bot.findBlock({\n matching: mcData.blocksByName[name].id,\n maxDistance: 32\n });\n if (log) {\n return log;\n }\n }\n return null;\n });\n if (woodLog) {\n // Mine the wood log block\n await mineBlock(bot, woodLog.name, 1);\n bot.chat("Wood log mined.");\n } else {\n bot.chat("Could not find a wood log.");\n }\n}',
|
||||
"description": "async function mineWoodLog(bot) {\n // The function is about mining a wood log block. It searches for a wood log block by exploring the environment until it finds one of the seven types of wood logs. Once a wood log block is found, it is mined and a message is sent to the chat. If a wood log block is not found, a message is sent to the chat indicating that it could not be found.\n}",
|
||||
},
|
||||
"obtainSpruceLogs": {
|
||||
"code": 'async function obtainSpruceLogs(bot) {\n // Find 3 spruce_log blocks\n const spruceLogs = await exploreUntil(bot, new Vec3(1, 0, 1), 60, () => {\n const spruceLog = bot.findBlock({\n matching: mcData.blocksByName["spruce_log"].id,\n maxDistance: 32,\n count: 3\n });\n return spruceLog ? spruceLog : null;\n });\n if (spruceLogs) {\n // Mine the spruce_log blocks\n await mineBlock(bot, "spruce_log", 3);\n bot.chat("3 spruce logs obtained.");\n } else {\n bot.chat("Could not find enough spruce logs.");\n }\n}',
|
||||
"description": "async function obtainSpruceLogs(bot) {\n // The function is about obtaining 3 spruce logs. It explores the environment until it finds 3 spruce_log blocks within a certain distance. Once the blocks are found, it mines them and sends a message indicating that 3 spruce logs have been obtained. If the blocks are not found, it sends a message indicating that it could not find enough spruce logs.\n}",
|
||||
},
|
||||
}
|
||||
context = "Question: How to obtain 3 more spruce logs in Minecraft?\nAnswer: You can obtain more spruce logs in Minecraft by finding and chopping down spruce trees in a spruce forest biome. If you have already chopped down all the spruce trees in the area, you can either explore further to find more spruce trees or plant saplings and wait for them to grow into trees."
|
||||
|
||||
sm = SkillManager()
|
||||
ge = GameEnvironment()
|
||||
ge.update_event(events)
|
||||
sm.set_memory(shared_memory=ge)
|
||||
|
||||
generate_skill_message = sm.encapsule_message(program_code, program_name)
|
||||
logger.info(f"Generate_skill_message: {generate_skill_message}")
|
||||
desp = await sm.generate_skill_descp(**generate_skill_message)
|
||||
logger.info(f"Generate_skill_descp UPDATING: {desp}")
|
||||
|
||||
add_new_skills_message = {
|
||||
"task": task,
|
||||
"program_name": program_name,
|
||||
"program_code": program_code,
|
||||
"skills": skills,
|
||||
}
|
||||
logger.info(f"Handle_add_new_skills_message: {add_new_skills_message}")
|
||||
new_skills_info = await sm.handle_add_new_skills(**add_new_skills_message)
|
||||
logger.info(f"Handle_add_new_skills UPDATING: {new_skills_info}")
|
||||
|
||||
retrieve_skills_message_step1 = {"query": context}
|
||||
|
||||
logger.info(f"Retrieve_skills_message: {retrieve_skills_message_step1}")
|
||||
skills = await sm.retrieve_skills(**retrieve_skills_message_step1)
|
||||
logger.info(f"Retrieve_skills UPDATING: {skills}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
|
@ -18,4 +18,4 @@ async def test_product_manager():
|
|||
rsp = await product_manager.handle(MockMessages.req)
|
||||
logger.info(rsp)
|
||||
assert len(rsp.content) > 0
|
||||
assert "产品目标" in rsp.content
|
||||
assert "Product Goals" in rsp.content
|
||||
|
|
|
|||
27
tests/metagpt/roles/test_tutorial_assistant.py
Normal file
27
tests/metagpt/roles/test_tutorial_assistant.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env python3
|
||||
# _*_ coding: utf-8 _*_
|
||||
"""
|
||||
@Time : 2023/9/6 23:11:27
|
||||
@Author : Stitch-z
|
||||
@File : test_tutorial_assistant.py
|
||||
"""
|
||||
import aiofiles
|
||||
import pytest
|
||||
|
||||
from metagpt.roles.tutorial_assistant import TutorialAssistant
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("language", "topic"),
|
||||
[("Chinese", "Write a tutorial about Python")]
|
||||
)
|
||||
async def test_tutorial_assistant(language: str, topic: str):
|
||||
topic = "Write a tutorial about MySQL"
|
||||
role = TutorialAssistant(language=language)
|
||||
msg = await role.run(topic)
|
||||
filename = msg.content
|
||||
title = filename.split("/")[-1].split(".")[0]
|
||||
async with aiofiles.open(filename, mode="r") as reader:
|
||||
content = await reader.read()
|
||||
assert content.startswith(f"# {title}")
|
||||
21
tests/metagpt/test_minecraft_team.py
Normal file
21
tests/metagpt/test_minecraft_team.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 2023/09/28 00:03
|
||||
# @Author : yuymf
|
||||
# @Desc :
|
||||
import asyncio
|
||||
from metagpt.logs import logger
|
||||
from metagpt.minecraft_team import GameEnvironment
|
||||
|
||||
|
||||
async def main():
|
||||
test_code = "bot.chat(`/time set ${getNextTime()}`);"
|
||||
mc_port = 2745
|
||||
ge = GameEnvironment()
|
||||
ge.set_mc_port(mc_port)
|
||||
ge.update_code(test_code)
|
||||
result = await ge.on_event()
|
||||
logger.info("On event test done")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
42
tests/metagpt/tools/test_code_interpreter.py
Normal file
42
tests/metagpt/tools/test_code_interpreter.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import pytest
|
||||
import pandas as pd
|
||||
from pathlib import Path
|
||||
|
||||
from tests.data import sales_desc, store_desc
|
||||
from metagpt.tools.code_interpreter import OpenCodeInterpreter, OpenInterpreterDecorator
|
||||
from metagpt.actions import Action
|
||||
from metagpt.logs import logger
|
||||
|
||||
|
||||
logger.add('./tests/data/test_ci.log')
|
||||
stock = "./tests/data/baba_stock.csv"
|
||||
|
||||
|
||||
# TODO: 需要一种表格数据格式,能够支持schame管理的,标注字段类型和字段含义。
|
||||
class CreateStockIndicators(Action):
|
||||
@OpenInterpreterDecorator(save_code=True, code_file_path="./tests/data/stock_indicators.py")
|
||||
async def run(self, stock_path: str, indicators=['Simple Moving Average', 'BollingerBands']) -> pd.DataFrame:
|
||||
"""对stock_path中的股票数据, 使用pandas和ta计算indicators中的技术指标, 返回带有技术指标的股票数据,不需要去除空值, 不需要安装任何包;
|
||||
指标生成对应的三列: SMA, BB_upper, BB_lower
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_actions():
|
||||
# 计算指标
|
||||
indicators = ['Simple Moving Average', 'BollingerBands']
|
||||
stocker = CreateStockIndicators()
|
||||
df, msg = await stocker.run(stock, indicators=indicators)
|
||||
assert isinstance(df, pd.DataFrame)
|
||||
assert 'Close' in df.columns
|
||||
assert 'Date' in df.columns
|
||||
# 将df保存为文件,将文件路径传入到下一个action
|
||||
df_path = './tests/data/stock_indicators.csv'
|
||||
df.to_csv(df_path)
|
||||
assert Path(df_path).is_file()
|
||||
# 可视化指标结果
|
||||
figure_path = './tests/data/figure_ci.png'
|
||||
ci_ploter = OpenCodeInterpreter()
|
||||
ci_ploter.chat(f"使用seaborn对{df_path}中与股票布林带有关的数据列的Date, Close, SMA, BB_upper(布林带上界), BB_lower(布林带下界)进行可视化, 可视化图片保存在{figure_path}中。不需要任何指标计算,把Date列转换为日期类型。要求图片优美,BB_upper, BB_lower之间使用合适的颜色填充。")
|
||||
assert Path(figure_path).is_file()
|
||||
69
tests/metagpt/utils/minecraft/test_action_rsp_parser.py
Normal file
69
tests/metagpt/utils/minecraft/test_action_rsp_parser.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 2023/09/28 00:08
|
||||
# @Author : yuymf
|
||||
# @Desc :
|
||||
from metagpt.utils.minecraft import parse_js_code, parse_action_response
|
||||
from metagpt.logs import logger
|
||||
from typing import Any
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
msg = '''
|
||||
Explain: The code from the last round is a function called `collectBamboo` that is supposed to collect bamboo plants. It equips an iron sword, finds bamboo plants using the `exploreUntil` function, breaks 10 bamboo plants using the iron sword, and then collects the dropped bamboo items.
|
||||
|
||||
Plan:
|
||||
1) Check if the bot has an iron sword in its inventory. If not, collect the necessary materials and craft an iron sword using the `craftItem` function.
|
||||
2) Use the `exploreUntil` function to find at least 10 bamboo plants. If the function times out or cannot find enough bamboo plants, return and chat "Could not find enough bamboo plants."
|
||||
3) Equip the iron sword.
|
||||
4) Iterate over the found bamboo plants and break them using the iron sword.
|
||||
5) Chat "Broke 10 bamboo plants."
|
||||
6) Iterate over the found bamboo plants and collect the dropped bamboo items.
|
||||
7) Chat "Collected 10 bamboo."
|
||||
|
||||
Code:
|
||||
```javascript
|
||||
async function collectBamboo(bot) {
|
||||
// Check if the bot has an iron sword
|
||||
const ironSword = bot.inventory.findInventoryItem(mcData.itemsByName.iron_sword.id);
|
||||
if (!ironSword) {
|
||||
// Collect the necessary materials to craft an iron sword
|
||||
await mineBlock(bot, "iron_ore", 3);
|
||||
await smeltItem(bot, "iron_ore", "oak_planks", 3);
|
||||
await craftItem(bot, "iron_sword", 1);
|
||||
}
|
||||
|
||||
// Find bamboo plants using the exploreUntil function
|
||||
const bambooPlants = await exploreUntil(bot, new Vec3(1, 0, 1), 60, () => {
|
||||
const bambooPlants = bot.findBlocks({
|
||||
matching: block => block.name === "bamboo",
|
||||
maxDistance: 32,
|
||||
count: 10
|
||||
});
|
||||
return bambooPlants.length >= 10 ? bambooPlants : null;
|
||||
});
|
||||
if (!bambooPlants) {
|
||||
bot.chat("Could not find enough bamboo plants.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Equip the iron sword
|
||||
await bot.equip(ironSword, "hand");
|
||||
|
||||
// Break 10 bamboo plants using the iron sword
|
||||
for (const bambooPlant of bambooPlants) {
|
||||
const block = bot.blockAt(bambooPlant);
|
||||
await bot.dig(block);
|
||||
}
|
||||
bot.chat("Broke 10 bamboo plants.");
|
||||
|
||||
// Collect the dropped bamboo items
|
||||
for (const bambooPlant of bambooPlants) {
|
||||
await bot.pathfinder.goto(new GoalBlock(bambooPlant.x, bambooPlant.y, bambooPlant.z));
|
||||
}
|
||||
bot.chat("Collected 10 bamboo.");
|
||||
}
|
||||
```
|
||||
'''
|
||||
|
||||
logger.info(f"Parse_js_code result is HERE: {parse_js_code(msg)}")
|
||||
logger.info(f"Parse_action_response result is HERE: {parse_action_response(msg)}")
|
||||
72
tests/metagpt/utils/test_custom_decoder.py
Normal file
72
tests/metagpt/utils/test_custom_decoder.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2023/9/8 11:38
|
||||
@Author : femto Zheng
|
||||
@File : test_custom_decoder.py
|
||||
"""
|
||||
|
||||
|
||||
from metagpt.utils.custom_decoder import CustomDecoder
|
||||
|
||||
|
||||
def test_parse_single_quote():
|
||||
# Create a custom JSON decoder
|
||||
decoder = CustomDecoder(strict=False)
|
||||
# Your provided input with single-quoted strings and line breaks
|
||||
input_data = """{'a"
|
||||
b':'"title": "Reach and engagement of campaigns",
|
||||
"x-axis": "Low Reach --> High Reach",
|
||||
"y-axis": "Low Engagement --> High Engagement",
|
||||
"quadrant-1": "We should expand",
|
||||
"quadrant-2": "Need to promote",
|
||||
"quadrant-3": "Re-evaluate",
|
||||
"quadrant-4": "May be improved",
|
||||
"Campaign: A": [0.3, 0.6],
|
||||
"Campaign B": [0.45, 0.23],
|
||||
"Campaign C": [0.57, 0.69],
|
||||
"Campaign D": [0.78, 0.34],
|
||||
"Campaign E": [0.40, 0.34],
|
||||
"Campaign F": [0.35, 0.78],
|
||||
"Our Target Product": [0.5, 0.6]
|
||||
'
|
||||
}
|
||||
"""
|
||||
# Parse the JSON using the custom decoder
|
||||
|
||||
parsed_data = decoder.decode(input_data)
|
||||
assert 'a"\n b' in parsed_data
|
||||
|
||||
|
||||
def test_parse_triple_double_quote():
|
||||
# Create a custom JSON decoder
|
||||
decoder = CustomDecoder(strict=False)
|
||||
# Your provided input with single-quoted strings and line breaks
|
||||
input_data = '{"""a""":"b"}'
|
||||
# Parse the JSON using the custom decoder
|
||||
|
||||
parsed_data = decoder.decode(input_data)
|
||||
assert "a" in parsed_data
|
||||
|
||||
input_data = '{"""a""":"""b"""}'
|
||||
# Parse the JSON using the custom decoder
|
||||
|
||||
parsed_data = decoder.decode(input_data)
|
||||
assert parsed_data["a"] == "b"
|
||||
|
||||
|
||||
def test_parse_triple_single_quote():
|
||||
# Create a custom JSON decoder
|
||||
decoder = CustomDecoder(strict=False)
|
||||
# Your provided input with single-quoted strings and line breaks
|
||||
input_data = "{'''a''':'b'}"
|
||||
# Parse the JSON using the custom decoder
|
||||
|
||||
parsed_data = decoder.decode(input_data)
|
||||
assert "a" in parsed_data
|
||||
|
||||
input_data = "{'''a''':'''b'''}"
|
||||
# Parse the JSON using the custom decoder
|
||||
|
||||
parsed_data = decoder.decode(input_data)
|
||||
assert parsed_data["a"] == "b"
|
||||
26
tests/metagpt/utils/test_file.py
Normal file
26
tests/metagpt/utils/test_file.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env python3
|
||||
# _*_ coding: utf-8 _*_
|
||||
"""
|
||||
@Time : 2023/9/4 15:40:40
|
||||
@Author : Stitch-z
|
||||
@File : test_file.py
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.utils.file import File
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("root_path", "filename", "content"),
|
||||
[(Path("/code/MetaGPT/data/tutorial_docx/2023-09-07_17-05-20"), "test.md", "Hello World!")]
|
||||
)
|
||||
async def test_write_and_read_file(root_path: Path, filename: str, content: bytes):
|
||||
full_file_name = await File.write(root_path=root_path, filename=filename, content=content.encode('utf-8'))
|
||||
assert isinstance(full_file_name, Path)
|
||||
assert root_path / filename == full_file_name
|
||||
file_data = await File.read(full_file_name)
|
||||
assert file_data.decode("utf-8") == content
|
||||
|
||||
60
tests/metagpt/utils/test_json_to_markdown.py
Normal file
60
tests/metagpt/utils/test_json_to_markdown.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2023/9/11 11:53
|
||||
@Author : femto Zheng
|
||||
@File : test_json_to_markdown.py
|
||||
"""
|
||||
|
||||
from metagpt.utils.json_to_markdown import json_to_markdown
|
||||
|
||||
|
||||
def test_json_to_markdown():
|
||||
# Example nested JSON data
|
||||
json_data = {
|
||||
"title": "Sample JSON to Markdown Conversion",
|
||||
"description": "Convert JSON to Markdown with headings and lists.",
|
||||
"tags": ["json", "markdown", "conversion"],
|
||||
"content": {
|
||||
"section1": {"subsection1": "This is a subsection.", "subsection2": "Another subsection."},
|
||||
"section2": "This is the second section content.",
|
||||
},
|
||||
}
|
||||
|
||||
# Convert JSON to Markdown with nested sections
|
||||
markdown_output = json_to_markdown(json_data)
|
||||
|
||||
expected = """## title
|
||||
|
||||
Sample JSON to Markdown Conversion
|
||||
|
||||
## description
|
||||
|
||||
Convert JSON to Markdown with headings and lists.
|
||||
|
||||
## tags
|
||||
|
||||
- json
|
||||
- markdown
|
||||
- conversion
|
||||
|
||||
## content
|
||||
|
||||
### section1
|
||||
|
||||
#### subsection1
|
||||
|
||||
This is a subsection.
|
||||
|
||||
#### subsection2
|
||||
|
||||
Another subsection.
|
||||
|
||||
### section2
|
||||
|
||||
This is the second section content.
|
||||
|
||||
"""
|
||||
# Print or use the generated Markdown
|
||||
# print(markdown_output)
|
||||
assert expected == markdown_output
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
@Author : chengmaoyu
|
||||
@File : test_output_parser.py
|
||||
"""
|
||||
from typing import List, Tuple
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -64,6 +64,59 @@ def test_parse_data():
|
|||
assert OutputParser.parse_data(test_data) == expected_result
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("text", "data_type", "parsed_data", "expected_exception"),
|
||||
[
|
||||
(
|
||||
"""xxx [1, 2, ["a", "b", [3, 4]], {"x": 5, "y": [6, 7]}] xxx""",
|
||||
list,
|
||||
[1, 2, ["a", "b", [3, 4]], {"x": 5, "y": [6, 7]}],
|
||||
None,
|
||||
),
|
||||
(
|
||||
"""xxx ["1", "2", "3"] xxx \n xxx \t xx""",
|
||||
list,
|
||||
["1", "2", "3"],
|
||||
None,
|
||||
),
|
||||
(
|
||||
"""{"title": "a", "directory": {"sub_dir1": ["title1, title2"]}, "sub_dir2": [1, 2]}""",
|
||||
dict,
|
||||
{"title": "a", "directory": {"sub_dir1": ["title1, title2"]}, "sub_dir2": [1, 2]},
|
||||
None,
|
||||
),
|
||||
(
|
||||
"""xxx {"title": "x", \n \t "directory": ["x", \n "y"]} xxx \n xxx \t xx""",
|
||||
dict,
|
||||
{"title": "x", "directory": ["x", "y"]},
|
||||
None,
|
||||
),
|
||||
(
|
||||
"""xxx xx""",
|
||||
list,
|
||||
None,
|
||||
Exception,
|
||||
),
|
||||
(
|
||||
"""xxx [1, 2, []xx""",
|
||||
list,
|
||||
None,
|
||||
Exception,
|
||||
),
|
||||
]
|
||||
)
|
||||
def test_extract_struct(text: str, data_type: Union[type(list), type(dict)], parsed_data: Union[list, dict], expected_exception):
|
||||
def case():
|
||||
resp = OutputParser.extract_struct(text, data_type)
|
||||
assert resp == parsed_data
|
||||
|
||||
if expected_exception:
|
||||
with pytest.raises(expected_exception):
|
||||
case()
|
||||
else:
|
||||
case()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
t_text = '''
|
||||
## Required Python third-party packages
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue