feat: merge geekan:dev

This commit is contained in:
莘权 马 2024-01-02 22:36:16 +08:00
commit 49a95e6608
29 changed files with 223 additions and 562 deletions

View file

@ -5,6 +5,8 @@
@Author : alexanderwu
@File : test_action.py
"""
import pytest
from metagpt.actions import Action, ActionType, WritePRD, WriteTest
@ -18,3 +20,22 @@ def test_action_type():
assert ActionType.WRITE_TEST.value == WriteTest
assert ActionType.WRITE_PRD.name == "WRITE_PRD"
assert ActionType.WRITE_TEST.name == "WRITE_TEST"
def test_simple_action():
action = Action(name="AlexSay", instruction="Express your opinion with emotion and don't repeat it")
assert action.name == "AlexSay"
assert action.node.instruction == "Express your opinion with emotion and don't repeat it"
def test_empty_action():
action = Action()
assert action.name == "Action"
assert not action.node
@pytest.mark.asyncio
async def test_empty_action_exception():
action = Action()
with pytest.raises(NotImplementedError):
await action.run()

View file

@ -8,6 +8,7 @@
from typing import List, Tuple
import pytest
from pydantic import ValidationError
from metagpt.actions import Action
from metagpt.actions.action_node import ActionNode
@ -20,35 +21,35 @@ from metagpt.team import Team
@pytest.mark.asyncio
async def test_debate_two_roles():
action1 = Action(name="BidenSay", instruction="Express opinions and argue vigorously, and strive to gain votes")
action2 = Action(name="TrumpSay", instruction="Express opinions and argue vigorously, and strive to gain votes")
action1 = Action(name="AlexSay", instruction="Express your opinion with emotion and don't repeat it")
action2 = Action(name="BobSay", instruction="Express your opinion with emotion and don't repeat it")
biden = Role(
name="Biden", profile="Democratic candidate", goal="Win the election", actions=[action1], watch=[action2]
name="Alex", profile="Democratic candidate", goal="Win the election", actions=[action1], watch=[action2]
)
trump = Role(
name="Trump", profile="Republican candidate", goal="Win the election", actions=[action2], watch=[action1]
name="Bob", profile="Republican candidate", goal="Win the election", actions=[action2], watch=[action1]
)
env = Environment(desc="US election live broadcast")
team = Team(investment=10.0, env=env, roles=[biden, trump])
history = await team.run(idea="Topic: climate change. Under 80 words per message.", send_to="Biden", n_round=3)
assert "Biden" in history
history = await team.run(idea="Topic: climate change. Under 80 words per message.", send_to="Alex", n_round=3)
assert "Alex" in history
@pytest.mark.asyncio
async def test_debate_one_role_in_env():
action = Action(name="Debate", instruction="Express opinions and argue vigorously, and strive to gain votes")
biden = Role(name="Biden", profile="Democratic candidate", goal="Win the election", actions=[action])
action = Action(name="Debate", instruction="Express your opinion with emotion and don't repeat it")
biden = Role(name="Alex", profile="Democratic candidate", goal="Win the election", actions=[action])
env = Environment(desc="US election live broadcast")
team = Team(investment=10.0, env=env, roles=[biden])
history = await team.run(idea="Topic: climate change. Under 80 words per message.", send_to="Biden", n_round=3)
assert "Biden" in history
history = await team.run(idea="Topic: climate change. Under 80 words per message.", send_to="Alex", n_round=3)
assert "Alex" in history
@pytest.mark.asyncio
async def test_debate_one_role():
action = Action(name="Debate", instruction="Express opinions and argue vigorously, and strive to gain votes")
biden = Role(name="Biden", profile="Democratic candidate", goal="Win the election", actions=[action])
action = Action(name="Debate", instruction="Express your opinion with emotion and don't repeat it")
biden = Role(name="Alex", profile="Democratic candidate", goal="Win the election", actions=[action])
msg: Message = await biden.run("Topic: climate change. Under 80 words per message.")
assert len(msg.content) > 10
@ -113,6 +114,10 @@ t_dict = {
"Anything UNCLEAR": "We need clarification on how the high score should be stored. Should it persist across sessions (stored in a database or a file) or should it reset every time the game is restarted? Also, should the game speed increase as the snake grows, or should it remain constant throughout the game?",
}
t_dict_min = {
"Required Python third-party packages": '"""\nflask==1.1.2\npygame==2.0.1\n"""\n',
}
WRITE_TASKS_OUTPUT_MAPPING = {
"Required Python third-party packages": (str, ...),
"Required Other language third-party packages": (str, ...),
@ -139,11 +144,19 @@ def test_create_model_class():
assert output.schema()["properties"]["Full API spec"]
def test_create_model_class_missing():
def test_create_model_class_with_fields_unrecognized():
test_class = ActionNode.create_model_class("test_class", WRITE_TASKS_OUTPUT_MAPPING_MISSING)
assert test_class.__name__ == "test_class"
_ = test_class(**t_dict) # 这里应该要挂掉
_ = test_class(**t_dict) # just warning
def test_create_model_class_with_fields_missing():
test_class = ActionNode.create_model_class("test_class", WRITE_TASKS_OUTPUT_MAPPING)
assert test_class.__name__ == "test_class"
with pytest.raises(ValidationError):
_ = test_class(**t_dict_min)
def test_create_model_class_with_mapping():

View file

@ -30,3 +30,11 @@ async def test_search_xlsx():
query = "Which facial cleanser is good for oily skin?"
result = await role.run(query)
logger.info(result)
@pytest.mark.asyncio
async def test_write():
store = FaissStore(EXAMPLE_PATH / "example.xlsx", meta_col="Answer", content_col="Question")
_faiss_store = store.write()
assert _faiss_store.docstore
assert _faiss_store.index

View file

@ -14,11 +14,14 @@ from metagpt.provider.general_api_base import (
APIRequestor,
ApiType,
OpenAIResponse,
_aiohttp_proxies_arg,
_build_api_url,
_make_session,
_requests_proxies_arg,
log_debug,
log_info,
log_warn,
logfmt,
parse_stream,
parse_stream_helper,
)
@ -36,6 +39,10 @@ def test_basic():
log_warn("warn")
log_info("info")
logfmt({"k1": b"v1", "k2": 1, "k3": "a b"})
_build_api_url(url="http://www.baidu.com/s?wd=", query="baidu")
def test_openai_response():
resp = OpenAIResponse(data=[], headers={"retry-after": 3})
@ -53,11 +60,18 @@ def test_proxy():
assert _requests_proxies_arg(proxy=proxy) == {"http": proxy, "https": proxy}
proxy_dict = {"http": proxy}
assert _requests_proxies_arg(proxy=proxy_dict) == proxy_dict
assert _aiohttp_proxies_arg(proxy_dict) == proxy
proxy_dict = {"https": proxy}
assert _requests_proxies_arg(proxy=proxy_dict) == proxy_dict
assert _aiohttp_proxies_arg(proxy_dict) == proxy
assert _make_session() is not None
assert _aiohttp_proxies_arg(None) is None
assert _aiohttp_proxies_arg("test") == "test"
with pytest.raises(ValueError):
_aiohttp_proxies_arg(-1)
def test_parse_stream():
assert parse_stream_helper(None) is None
@ -83,6 +97,29 @@ async def mock_interpret_async_response(
return b"baidu", True
def test_requestor_headers():
# validate_headers
headers = api_requestor._validate_headers(None)
assert not headers
with pytest.raises(Exception):
api_requestor._validate_headers(-1)
with pytest.raises(Exception):
api_requestor._validate_headers({1: 2})
with pytest.raises(Exception):
api_requestor._validate_headers({"test": 1})
supplied_headers = {"test": "test"}
assert api_requestor._validate_headers(supplied_headers) == supplied_headers
api_requestor.organization = "test"
api_requestor.api_version = "test123"
api_requestor.api_type = ApiType.OPEN_AI
request_id = "test123"
headers = api_requestor.request_headers(method="post", extra={}, request_id=request_id)
assert headers["LLM-Organization"] == api_requestor.organization
assert headers["LLM-Version"] == api_requestor.api_version
assert headers["X-Request-Id"] == request_id
def test_api_requestor(mocker):
mocker.patch("metagpt.provider.general_api_base.APIRequestor._interpret_response", mock_interpret_response)
resp, _, _ = api_requestor.request(method="get", url="/s?wd=baidu")

View file

@ -7,23 +7,25 @@ import pytest
from metagpt.provider.human_provider import HumanProvider
resp_content = "test"
def mock_llm_ask(msg: str, timeout: int = 3) -> str:
return resp_content
async def mock_llm_aask(msg: str, timeout: int = 3) -> str:
return mock_llm_ask(msg)
resp_exit = "exit"
@pytest.mark.asyncio
async def test_async_human_provider(mocker):
mocker.patch("metagpt.provider.human_provider.HumanProvider.aask", mock_llm_aask)
mocker.patch("builtins.input", lambda _: resp_content)
human_provider = HumanProvider()
resp = human_provider.ask(resp_content)
assert resp == resp_content
resp = await human_provider.aask(None)
assert resp_content == resp
mocker.patch("builtins.input", lambda _: resp_exit)
with pytest.raises(SystemExit):
human_provider.ask(resp_exit)
resp = await human_provider.acompletion([])
assert not resp
resp = await human_provider.acompletion_text([])
assert resp == ""

View file

@ -17,10 +17,23 @@ prompt_msg = "who are you"
resp_content = "I'm Spark"
def test_get_msg_from_web():
class MockWebSocketApp(object):
def __init__(self, ws_url, on_message=None, on_error=None, on_close=None, on_open=None):
pass
def run_forever(self, sslopt=None):
pass
def test_get_msg_from_web(mocker):
mocker.patch("websocket.WebSocketApp", MockWebSocketApp)
get_msg_from_web = GetMessageFromWeb(text=prompt_msg)
assert get_msg_from_web.gen_params()["parameter"]["chat"]["domain"] == "xxxxxx"
ret = get_msg_from_web.run()
assert ret == ""
def mock_spark_get_msg_from_web_run(self) -> str:
return resp_content
@ -29,6 +42,7 @@ def mock_spark_get_msg_from_web_run(self) -> str:
@pytest.mark.asyncio
async def test_spark_acompletion(mocker):
mocker.patch("metagpt.provider.spark_api.GetMessageFromWeb.run", mock_spark_get_msg_from_web_run)
spark_gpt = SparkLLM()
resp = await spark_gpt.acompletion([])

View file

@ -16,3 +16,11 @@ async def test_async_sse_client():
async_sse_client = AsyncSSEClient(event_source=Iterator())
async for event in async_sse_client.async_events():
assert event.data, "test_value"
class InvalidIterator(object):
async def __aiter__(self):
yield b"invalid: test_value"
async_sse_client = AsyncSSEClient(event_source=InvalidIterator())
async for event in async_sse_client.async_events():
assert not event

View file

@ -14,7 +14,7 @@ from metagpt.provider.zhipuai.zhipu_model_api import ZhiPuModelAPI
api_key = "xxx.xxx"
zhipuai.api_key = api_key
default_resp = {"result": "test response"}
default_resp = b'{"result": "test response"}'
async def mock_requestor_arequest(self, **kwargs) -> Tuple[Any, Any, str]:
@ -39,3 +39,6 @@ async def test_zhipu_model_api(mocker):
InvokeType.SYNC, stream=False, method="get", headers={}, kwargs={"model": "chatglm_turbo"}
)
assert result == default_resp
result = await ZhiPuModelAPI.ainvoke()
assert result["result"] == "test response"

View file

@ -0,0 +1,33 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2024/1/2 21:00
@Author : alexanderwu
@File : test_document.py
"""
from metagpt.config import CONFIG
from metagpt.document import Repo
from metagpt.logs import logger
def set_existing_repo(path):
repo1 = Repo.from_path(path)
repo1.set("doc/wtf_file.md", "wtf content")
repo1.set("code/wtf_file.py", "def hello():\n print('hello')")
logger.info(repo1) # check doc
def load_existing_repo(path):
repo = Repo.from_path(path)
logger.info(repo)
logger.info(repo.eda())
assert repo
assert repo.get("doc/wtf_file.md").content == "wtf content"
assert repo.get("code/wtf_file.py").content == "def hello():\n print('hello')"
def test_repo_set_load():
repo_path = CONFIG.workspace_path / "test_repo"
set_existing_repo(repo_path)
load_existing_repo(repo_path)

View file

@ -18,7 +18,7 @@ from metagpt.config import CONFIG
@pytest.mark.asyncio
async def test_hello():
workdir = Path(__file__).parent.parent.parent.parent
script_pathname = workdir / "metagpt/tools/hello.py"
script_pathname = workdir / "metagpt/tools/openapi_v3_hello.py"
env = CONFIG.new_environ()
env["PYTHONPATH"] = str(workdir) + ":" + env.get("PYTHONPATH", "")
process = subprocess.Popen(["python", str(script_pathname)], cwd=workdir, env=env)

View file

@ -91,6 +91,10 @@ class TestGetProjectRoot:
x=(TutorialAssistant, RunCode(), "a"),
want={"metagpt.roles.tutorial_assistant.TutorialAssistant", "metagpt.actions.run_code.RunCode", "a"},
),
Input(
x={"a": TutorialAssistant, "b": RunCode(), "c": "a"},
want={"a", "metagpt.roles.tutorial_assistant.TutorialAssistant", "metagpt.actions.run_code.RunCode"},
),
]
for i in inputs:
v = any_to_str_set(i.x)

View file

@ -119,95 +119,7 @@ def test_extract_struct(
case()
if __name__ == "__main__":
t_text = '''
## Required Python third-party packages
```python
"""
flask==1.1.2
pygame==2.0.1
"""
```
## Required Other language third-party packages
```python
"""
No third-party packages required for other languages.
"""
```
## Full API spec
```python
"""
openapi: 3.0.0
info:
title: Web Snake Game API
version: 1.0.0
paths:
/game:
get:
summary: Get the current game state
responses:
'200':
description: A JSON object of the game state
post:
summary: Send a command to the game
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
command:
type: string
responses:
'200':
description: A JSON object of the updated game state
"""
```
## Logic Analysis
```python
[
("app.py", "Main entry point for the Flask application. Handles HTTP requests and responses."),
("game.py", "Contains the Game and Snake classes. Handles the game logic."),
("static/js/script.js", "Handles user interactions and updates the game UI."),
("static/css/styles.css", "Defines the styles for the game UI."),
("templates/index.html", "The main page of the web application. Displays the game UI.")
]
```
## Task list
```python
[
"game.py",
"app.py",
"static/css/styles.css",
"static/js/script.js",
"templates/index.html"
]
```
## Shared Knowledge
```python
"""
'game.py' contains the Game and Snake classes which are responsible for the game logic. The Game class uses an instance of the Snake class.
'app.py' is the main entry point for the Flask application. It creates an instance of the Game class and handles HTTP requests and responses.
'static/js/script.js' is responsible for handling user interactions and updating the game UI based on the game state returned by 'app.py'.
'static/css/styles.css' defines the styles for the game UI.
'templates/index.html' is the main page of the web application. It displays the game UI and loads 'static/js/script.js' and 'static/css/styles.css'.
"""
```
## Anything UNCLEAR
We need clarification on how the high score should be stored. Should it persist across sessions (stored in a database or a file) or should it reset every time the game is restarted? Also, should the game speed increase as the snake grows, or should it remain constant throughout the game?
'''
def test_parse_with_markdown_mapping():
OUTPUT_MAPPING = {
"Original Requirements": (str, ...),
"Product Goals": (List[str], ...),
@ -218,7 +130,7 @@ We need clarification on how the high score should be stored. Should it persist
"Requirement Pool": (List[Tuple[str, str]], ...),
"Anything UNCLEAR": (str, ...),
}
t_text1 = """## Original Requirements:
t_text_with_content_tag = """[CONTENT]## Original Requirements:
The user wants to create a web-based version of the game "Fly Bird".
@ -286,8 +198,11 @@ The product should be a web-based version of the game "Fly Bird" that is engagin
## Anything UNCLEAR:
There are no unclear points.
"""
d = OutputParser.parse_data_with_mapping(t_text1, OUTPUT_MAPPING)
[/CONTENT]"""
t_text_raw = t_text_with_content_tag.replace("[CONTENT]", "").replace("[/CONTENT]", "")
d = OutputParser.parse_data_with_mapping(t_text_with_content_tag, OUTPUT_MAPPING)
import json
print(json.dumps(d))
assert d["Original Requirements"] == t_text_raw.split("## Original Requirements:")[1].split("##")[0].strip()