feat: merge geekan:dev

This commit is contained in:
莘权 马 2024-02-02 16:47:52 +08:00
commit dadd09bfb5
105 changed files with 5201 additions and 350 deletions

View file

@ -5,6 +5,7 @@
@Author : alexanderwu
@File : test_action_node.py
"""
from pathlib import Path
from typing import List, Tuple
import pytest
@ -17,6 +18,7 @@ from metagpt.llm import LLM
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.team import Team
from metagpt.utils.common import encode_image
@pytest.mark.asyncio
@ -241,6 +243,18 @@ def test_create_model_class_with_mapping():
assert value == ["game.py", "app.py", "static/css/styles.css", "static/js/script.js", "templates/index.html"]
@pytest.mark.asyncio
async def test_action_node_with_image():
invoice = ActionNode(
key="invoice", expected_type=bool, instruction="if it's a invoice file, return True else False", example="False"
)
invoice_path = Path(__file__).parent.joinpath("..", "..", "data", "invoices", "invoice-2.png")
img_base64 = encode_image(invoice_path)
node = await invoice.fill(context="", llm=LLM(), images=[img_base64])
assert node.instruct_content.invoice
class ToolDef(BaseModel):
tool_name: str = Field(default="a", description="tool name", examples=[])
description: str = Field(default="b", description="tool description", examples=[])

View file

@ -28,9 +28,9 @@ async def test_collect_links(mocker, search_engine_mocker, context):
return "[1,2]"
mocker.patch("metagpt.provider.base_llm.BaseLLM.aask", mock_llm_ask)
resp = await research.CollectLinks(search_engine=SearchEngine(SearchEngineType.DUCK_DUCK_GO), context=context).run(
"The application of MetaGPT"
)
resp = await research.CollectLinks(
search_engine=SearchEngine(engine=SearchEngineType.DUCK_DUCK_GO), context=context
).run("The application of MetaGPT")
for i in ["MetaGPT use cases", "The roadmap of MetaGPT", "The function of MetaGPT", "What llm MetaGPT support"]:
assert i in resp
@ -50,7 +50,9 @@ async def test_collect_links_with_rank_func(mocker, search_engine_mocker, contex
mocker.patch("metagpt.provider.base_llm.BaseLLM.aask", mock_collect_links_llm_ask)
resp = await research.CollectLinks(
search_engine=SearchEngine(SearchEngineType.DUCK_DUCK_GO), rank_func=rank_func, context=context
search_engine=SearchEngine(engine=SearchEngineType.DUCK_DUCK_GO),
rank_func=rank_func,
context=context,
).run("The application of MetaGPT")
for x, y, z in zip(rank_before, rank_after, resp.values()):
assert x[::-1] == y

View file

@ -0,0 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc :

View file

@ -0,0 +1,75 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc : the unittest of AndroidExtEnv
from pathlib import Path
from metagpt.environment.android_env.android_ext_env import AndroidExtEnv
from metagpt.environment.android_env.const import ADB_EXEC_FAIL
def mock_device_shape(self, adb_cmd: str) -> str:
return "shape: 720x1080"
def mock_device_shape_invalid(self, adb_cmd: str) -> str:
return ADB_EXEC_FAIL
def mock_list_devices(self, adb_cmd: str) -> str:
return "devices\nemulator-5554"
def mock_get_screenshot(self, adb_cmd: str) -> str:
return "screenshot_xxxx-xx-xx"
def mock_get_xml(self, adb_cmd: str) -> str:
return "xml_xxxx-xx-xx"
def mock_write_read_operation(self, adb_cmd: str) -> str:
return "OK"
def test_android_ext_env(mocker):
device_id = "emulator-5554"
mocker.patch(
"metagpt.environment.android_env.android_ext_env.AndroidExtEnv.execute_adb_with_cmd", mock_device_shape
)
ext_env = AndroidExtEnv(device_id=device_id, screenshot_dir="/data2/", xml_dir="/data2/")
assert ext_env.adb_prefix == f"adb -s {device_id} "
assert ext_env.adb_prefix_shell == f"adb -s {device_id} shell "
assert ext_env.adb_prefix_si == f"adb -s {device_id} shell input "
assert ext_env.device_shape == (720, 1080)
mocker.patch(
"metagpt.environment.android_env.android_ext_env.AndroidExtEnv.execute_adb_with_cmd", mock_device_shape_invalid
)
assert ext_env.device_shape == (0, 0)
mocker.patch(
"metagpt.environment.android_env.android_ext_env.AndroidExtEnv.execute_adb_with_cmd", mock_list_devices
)
assert ext_env.list_devices() == [device_id]
mocker.patch(
"metagpt.environment.android_env.android_ext_env.AndroidExtEnv.execute_adb_with_cmd", mock_get_screenshot
)
assert ext_env.get_screenshot("screenshot_xxxx-xx-xx", "/data/") == Path("/data/screenshot_xxxx-xx-xx.png")
mocker.patch("metagpt.environment.android_env.android_ext_env.AndroidExtEnv.execute_adb_with_cmd", mock_get_xml)
assert ext_env.get_xml("xml_xxxx-xx-xx", "/data/") == Path("/data/xml_xxxx-xx-xx.xml")
mocker.patch(
"metagpt.environment.android_env.android_ext_env.AndroidExtEnv.execute_adb_with_cmd", mock_write_read_operation
)
res = "OK"
assert ext_env.system_back() == res
assert ext_env.system_tap(10, 10) == res
assert ext_env.user_input("test_input") == res
assert ext_env.user_longpress(10, 10) == res
assert ext_env.user_swipe(10, 10) == res
assert ext_env.user_swipe_to((10, 10), (20, 20)) == res

View file

@ -0,0 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc :

View file

@ -0,0 +1,15 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc :
from metagpt.environment.api.env_api import EnvAPIRegistry
def test_env_api_registry():
def test_func():
pass
env_api_registry = EnvAPIRegistry()
env_api_registry["test"] = test_func
env_api_registry.get("test") == test_func

View file

@ -0,0 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc :

View file

@ -0,0 +1,14 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc : the unittest of MincraftExtEnv
from metagpt.environment.mincraft_env.const import MC_CKPT_DIR
from metagpt.environment.mincraft_env.mincraft_ext_env import MincraftExtEnv
def test_mincraft_ext_env():
ext_env = MincraftExtEnv()
assert ext_env.server, f"{ext_env.server_host}:{ext_env.server_port}"
assert MC_CKPT_DIR.joinpath("skill/code").exists()
assert ext_env.warm_up.get("optional_inventory_items") == 7

View file

@ -0,0 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc :

View file

@ -0,0 +1,40 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc : the unittest of StanfordTownExtEnv
from pathlib import Path
from metagpt.environment.stanford_town_env.stanford_town_ext_env import (
StanfordTownExtEnv,
)
maze_asset_path = (
Path(__file__).absolute().parent.joinpath("..", "..", "..", "data", "environment", "stanford_town", "the_ville")
)
def test_stanford_town_ext_env():
ext_env = StanfordTownExtEnv(maze_asset_path=maze_asset_path)
tile_coord = ext_env.turn_coordinate_to_tile((64, 64))
assert tile_coord == (2, 2)
tile = (58, 9)
assert len(ext_env.get_collision_maze()) == 100
assert len(ext_env.get_address_tiles()) == 306
assert ext_env.access_tile(tile=tile)["world"] == "the Ville"
assert ext_env.get_tile_path(tile=tile, level="world") == "the Ville"
assert len(ext_env.get_nearby_tiles(tile=tile, vision_r=5)) == 121
event = ("double studio:double studio:bedroom 2:bed", None, None, None)
ext_env.add_tiles_event(tile[1], tile[0], event=event)
ext_env.add_event_from_tile(event, tile)
assert len(ext_env.tiles[tile[1]][tile[0]]["events"]) == 1
ext_env.turn_event_from_tile_idle(event, tile)
ext_env.remove_event_from_tile(event, tile)
assert len(ext_env.tiles[tile[1]][tile[0]]["events"]) == 0
ext_env.remove_subject_events_from_tile(subject=event[0], tile=tile)
assert len(ext_env.tiles[tile[1]][tile[0]]["events"]) == 0

View file

@ -0,0 +1,54 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc : the unittest of ExtEnv&Env
import pytest
from metagpt.environment.api.env_api import EnvAPIAbstract
from metagpt.environment.base_env import (
Environment,
env_read_api_registry,
env_write_api_registry,
mark_as_readable,
mark_as_writeable,
)
class ForTestEnv(Environment):
value: int = 0
@mark_as_readable
def read_api_no_param(self):
return self.value
@mark_as_readable
def read_api(self, a: int, b: int):
return a + b
@mark_as_writeable
def write_api(self, a: int, b: int):
self.value = a + b
@mark_as_writeable
async def async_read_api(self, a: int, b: int):
return a + b
@pytest.mark.asyncio
async def test_ext_env():
env = ForTestEnv()
assert len(env_read_api_registry) > 0
assert len(env_write_api_registry) > 0
apis = env.get_all_available_apis(mode="read")
assert len(apis) > 0
assert len(apis["read_api"]) == 3
_ = await env.step(EnvAPIAbstract(api_name="write_api", kwargs={"a": 5, "b": 10}))
assert env.value == 15
with pytest.raises(ValueError):
await env.observe("not_exist_api")
assert await env.observe("read_api_no_param") == 15
assert await env.observe(EnvAPIAbstract(api_name="read_api", kwargs={"a": 5, "b": 5})) == 10

View file

@ -0,0 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc :

View file

@ -0,0 +1,64 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc : the unittest of WerewolfExtEnv
from metagpt.environment.werewolf_env.werewolf_ext_env import RoleState, WerewolfExtEnv
from metagpt.roles.role import Role
class Werewolf(Role):
profile: str = "Werewolf"
class Villager(Role):
profile: str = "Villager"
class Witch(Role):
profile: str = "Witch"
class Guard(Role):
profile: str = "Guard"
def test_werewolf_ext_env():
players_state = {
"Player0": ("Werewolf", RoleState.ALIVE),
"Player1": ("Werewolf", RoleState.ALIVE),
"Player2": ("Villager", RoleState.ALIVE),
"Player3": ("Witch", RoleState.ALIVE),
"Player4": ("Guard", RoleState.ALIVE),
}
ext_env = WerewolfExtEnv(players_state=players_state, step_idx=4, special_role_players=["Player3", "Player4"])
assert len(ext_env.living_players) == 5
assert len(ext_env.special_role_players) == 2
assert len(ext_env.werewolf_players) == 2
curr_instr = ext_env.curr_step_instruction()
assert ext_env.step_idx == 5
assert "Werewolves, please open your eyes" in curr_instr["content"]
# current step_idx = 5
ext_env.wolf_kill_someone(wolf=Role(name="Player10"), player_name="Player4")
ext_env.wolf_kill_someone(wolf=Werewolf(name="Player0"), player_name="Player4")
ext_env.wolf_kill_someone(wolf=Werewolf(name="Player1"), player_name="Player4")
assert ext_env.player_hunted == "Player4"
assert len(ext_env.living_players) == 5 # hunted but can be saved by witch
for idx in range(13):
_ = ext_env.curr_step_instruction()
# current step_idx = 18
assert ext_env.step_idx == 18
ext_env.vote_kill_someone(voteer=Werewolf(name="Player0"), player_name="Player2")
ext_env.vote_kill_someone(voteer=Werewolf(name="Player1"), player_name="Player3")
ext_env.vote_kill_someone(voteer=Villager(name="Player2"), player_name="Player3")
ext_env.vote_kill_someone(voteer=Witch(name="Player3"), player_name="Player4")
ext_env.vote_kill_someone(voteer=Guard(name="Player4"), player_name="Player2")
assert ext_env.player_current_dead == "Player2"
assert len(ext_env.living_players) == 4
player_names = ["Player0", "Player2"]
assert ext_env.get_players_state(player_names) == dict(zip(player_names, [RoleState.ALIVE, RoleState.KILLED]))

View file

@ -16,6 +16,6 @@ async def test_google_search(search_engine_mocker):
result = await google_search(
seed.input,
engine=SearchEngineType.SERPER_GOOGLE,
serper_api_key="mock-serper-key",
api_key="mock-serper-key",
)
assert result != ""

View file

@ -1,4 +1,5 @@
import pytest
from PIL import Image
from metagpt.const import TEST_DATA_PATH
from metagpt.llm import LLM
@ -62,6 +63,18 @@ async def test_speech_to_text():
assert "你好" == resp.text
@pytest.mark.asyncio
async def test_gen_image():
llm = LLM()
model = "dall-e-3"
prompt = 'a logo with word "MetaGPT"'
images: list[Image] = await llm.gen_image(model=model, prompt=prompt)
assert images[0].size == (1024, 1024)
images: list[Image] = await llm.gen_image(model=model, prompt=prompt, resp_format="b64_json")
assert images[0].size == (1024, 1024)
class TestOpenAI:
def test_make_client_kwargs_without_proxy(self):
instance = OpenAILLM(mock_llm_config)

View file

@ -36,7 +36,7 @@ async def test_researcher(mocker, search_engine_mocker, context):
role = researcher.Researcher(context=context)
for i in role.actions:
if isinstance(i, CollectLinks):
i.search_engine = SearchEngine(SearchEngineType.DUCK_DUCK_GO)
i.search_engine = SearchEngine(engine=SearchEngineType.DUCK_DUCK_GO)
await role.run(topic)
assert (researcher.RESEARCH_PATH / f"{topic}.md").read_text().startswith("# Research Report")

View file

@ -6,6 +6,7 @@
@File : test_incremental_dev.py
"""
import os
import shutil
import subprocess
import time
@ -45,6 +46,7 @@ PROJECT_NAMES = [
]
@pytest.mark.skip
def test_simple_add_calculator():
result = get_incremental_dev_result(IDEAS[0], PROJECT_NAMES[0])
log_and_check_result(result)
@ -115,10 +117,14 @@ def get_incremental_dev_result(idea, project_name, use_review=True):
if not project_path.exists():
# If the project does not exist, extract the project file
try:
# Use the tar command to extract the .zip file
subprocess.run(["tar", "-xf", f"{project_path}.zip", "-C", str(project_path.parent)], check=True)
if shutil.which("unzip"):
subprocess.run(["unzip", f"{project_path}.zip", "-d", str(project_path.parent)], check=True)
elif shutil.which("tar"):
subprocess.run(["tar", "-xf", f"{project_path}.zip", "-C", str(project_path.parent)], check=True)
logger.info(f"Extracted project {project_name} successfully.")
except FileNotFoundError as e:
raise FileNotFoundError(f"Neither 'unzip' nor 'tar' command found. Error: {e}")
except subprocess.CalledProcessError as e:
# If the extraction fails, throw an exception
raise Exception(f"Failed to extract project {project_name}. Error: {e}")
check_or_create_base_tag(project_path)

View file

@ -12,6 +12,7 @@ from typing import Callable
import pytest
from metagpt.config2 import config
from metagpt.configs.search_config import SearchConfig
from metagpt.logs import logger
from metagpt.tools import SearchEngineType
from metagpt.tools.search_engine import SearchEngine
@ -49,27 +50,34 @@ async def test_search_engine(
search_engine_mocker,
):
# Prerequisites
search_engine_config = {}
search_engine_config = {"engine": search_engine_type, "run_func": run_func}
if search_engine_type is SearchEngineType.SERPAPI_GOOGLE:
assert config.search
search_engine_config["serpapi_api_key"] = "mock-serpapi-key"
search_engine_config["api_key"] = "mock-serpapi-key"
elif search_engine_type is SearchEngineType.DIRECT_GOOGLE:
assert config.search
search_engine_config["google_api_key"] = "mock-google-key"
search_engine_config["google_cse_id"] = "mock-google-cse"
search_engine_config["api_key"] = "mock-google-key"
search_engine_config["cse_id"] = "mock-google-cse"
elif search_engine_type is SearchEngineType.SERPER_GOOGLE:
assert config.search
search_engine_config["serper_api_key"] = "mock-serper-key"
search_engine_config["api_key"] = "mock-serper-key"
search_engine = SearchEngine(search_engine_type, run_func, **search_engine_config)
rsp = await search_engine.run("metagpt", max_results, as_string)
logger.info(rsp)
if as_string:
assert isinstance(rsp, str)
else:
assert isinstance(rsp, list)
assert len(rsp) <= max_results
async def test(search_engine):
rsp = await search_engine.run("metagpt", max_results, as_string)
logger.info(rsp)
if as_string:
assert isinstance(rsp, str)
else:
assert isinstance(rsp, list)
assert len(rsp) <= max_results
await test(SearchEngine(**search_engine_config))
search_engine_config["api_type"] = search_engine_config.pop("engine")
if run_func:
await test(SearchEngine.from_search_func(run_func))
search_engine_config["search_func"] = search_engine_config.pop("run_func")
await test(SearchEngine.from_search_config(SearchConfig(**search_engine_config)))
if __name__ == "__main__":

View file

@ -3,7 +3,6 @@
import pytest
from metagpt.config2 import config
from metagpt.tools import web_browser_engine_playwright
from metagpt.utils.parse_html import WebPage
@ -19,26 +18,22 @@ from metagpt.utils.parse_html import WebPage
ids=["chromium-normal", "firefox-normal", "webkit-normal"],
)
async def test_scrape_web_page(browser_type, use_proxy, kwagrs, url, urls, proxy, capfd):
global_proxy = config.proxy
try:
if use_proxy:
server, proxy_url = await proxy()
config.proxy = proxy_url
browser = web_browser_engine_playwright.PlaywrightWrapper(browser_type=browser_type, **kwagrs)
result = await browser.run(url)
assert isinstance(result, WebPage)
assert "MetaGPT" in result.inner_text
proxy_url = None
if use_proxy:
server, proxy_url = await proxy()
browser = web_browser_engine_playwright.PlaywrightWrapper(browser_type=browser_type, proxy=proxy_url, **kwagrs)
result = await browser.run(url)
assert isinstance(result, WebPage)
assert "MetaGPT" in result.inner_text
if urls:
results = await browser.run(url, *urls)
assert isinstance(results, list)
assert len(results) == len(urls) + 1
assert all(("MetaGPT" in i.inner_text) for i in results)
if use_proxy:
server.close()
assert "Proxy:" in capfd.readouterr().out
finally:
config.proxy = global_proxy
if urls:
results = await browser.run(url, *urls)
assert isinstance(results, list)
assert len(results) == len(urls) + 1
assert all(("MetaGPT" in i.inner_text) for i in results)
if use_proxy:
server.close()
assert "Proxy:" in capfd.readouterr().out
if __name__ == "__main__":

View file

@ -4,7 +4,6 @@
import browsers
import pytest
from metagpt.config2 import config
from metagpt.tools import web_browser_engine_selenium
from metagpt.utils.parse_html import WebPage
@ -40,27 +39,22 @@ from metagpt.utils.parse_html import WebPage
async def test_scrape_web_page(browser_type, use_proxy, url, urls, proxy, capfd):
# Prerequisites
# firefox, chrome, Microsoft Edge
proxy_url = None
if use_proxy:
server, proxy_url = await proxy()
browser = web_browser_engine_selenium.SeleniumWrapper(browser_type=browser_type, proxy=proxy_url)
result = await browser.run(url)
assert isinstance(result, WebPage)
assert "MetaGPT" in result.inner_text
global_proxy = config.proxy
try:
if use_proxy:
server, proxy_url = await proxy()
config.proxy = proxy_url
browser = web_browser_engine_selenium.SeleniumWrapper(browser_type=browser_type)
result = await browser.run(url)
assert isinstance(result, WebPage)
assert "MetaGPT" in result.inner_text
if urls:
results = await browser.run(url, *urls)
assert isinstance(results, list)
assert len(results) == len(urls) + 1
assert all(("MetaGPT" in i.inner_text) for i in results)
if use_proxy:
server.close()
assert "Proxy:" in capfd.readouterr().out
finally:
config.proxy = global_proxy
if urls:
results = await browser.run(url, *urls)
assert isinstance(results, list)
assert len(results) == len(urls) + 1
assert all(("MetaGPT" in i.inner_text) for i in results)
if use_proxy:
server.close()
assert "Proxy:" in capfd.readouterr().out
if __name__ == "__main__":