mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-05-01 03:46:23 +02:00
Merge branch 'geekan/dev' into feature/rebuild
This commit is contained in:
commit
5f88e12a7d
62 changed files with 1109 additions and 565 deletions
|
|
@ -8,7 +8,7 @@
|
|||
from typing import List, Tuple
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
|
||||
from metagpt.actions import Action
|
||||
from metagpt.actions.action_node import ActionNode, ReviewMode, ReviseMode
|
||||
|
|
@ -241,6 +241,47 @@ def test_create_model_class_with_mapping():
|
|||
assert value == ["game.py", "app.py", "static/css/styles.css", "static/js/script.js", "templates/index.html"]
|
||||
|
||||
|
||||
class ToolDef(BaseModel):
|
||||
tool_name: str = Field(default="a", description="tool name", examples=[])
|
||||
description: str = Field(default="b", description="tool description", examples=[])
|
||||
|
||||
|
||||
class Task(BaseModel):
|
||||
task_id: int = Field(default=1, description="task id", examples=[1, 2, 3])
|
||||
name: str = Field(default="Get data from ...", description="task name", examples=[])
|
||||
dependent_task_ids: List[int] = Field(default=[], description="dependent task ids", examples=[1, 2, 3])
|
||||
tool: ToolDef = Field(default=ToolDef(), description="tool use", examples=[])
|
||||
|
||||
|
||||
class Tasks(BaseModel):
|
||||
tasks: List[Task] = Field(default=[], description="tasks", examples=[])
|
||||
|
||||
|
||||
def test_action_node_from_pydantic_and_print_everything():
|
||||
node = ActionNode.from_pydantic(Task)
|
||||
print("1. Tasks")
|
||||
print(Task().model_dump_json(indent=4))
|
||||
print(Tasks.model_json_schema())
|
||||
print("2. Task")
|
||||
print(Task.model_json_schema())
|
||||
print("3. ActionNode")
|
||||
print(node)
|
||||
print("4. node.compile prompt")
|
||||
prompt = node.compile(context="")
|
||||
assert "tool_name" in prompt, "tool_name should be in prompt"
|
||||
print(prompt)
|
||||
print("5. node.get_children_mapping")
|
||||
print(node._get_children_mapping())
|
||||
print("6. node.create_children_class")
|
||||
children_class = node._create_children_class()
|
||||
print(children_class)
|
||||
import inspect
|
||||
|
||||
code = inspect.getsource(Tasks)
|
||||
print(code)
|
||||
assert "tasks" in code, "tasks should be in code"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_create_model_class()
|
||||
test_create_model_class_with_mapping()
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@ async def test_rebuild(context):
|
|||
@pytest.mark.parametrize(
|
||||
("path", "direction", "diff", "want"),
|
||||
[
|
||||
("metagpt/startup.py", "=", ".", "metagpt/startup.py"),
|
||||
("metagpt/startup.py", "+", "MetaGPT", "MetaGPT/metagpt/startup.py"),
|
||||
("metagpt/startup.py", "-", "metagpt", "startup.py"),
|
||||
("metagpt/software_company.py", "=", ".", "metagpt/software_company.py"),
|
||||
("metagpt/software_company.py", "+", "MetaGPT", "MetaGPT/metagpt/software_company.py"),
|
||||
("metagpt/software_company.py", "-", "metagpt", "software_company.py"),
|
||||
],
|
||||
)
|
||||
def test_align_path(path, direction, diff, want):
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ async def test_run_script(context):
|
|||
@pytest.mark.asyncio
|
||||
async def test_run(context):
|
||||
inputs = [
|
||||
(RunCodeContext(mode="text", code_filename="a.txt", code="print('Hello, World')"), "PASS"),
|
||||
(RunCodeContext(mode="text", code_filename="a.txt", code="result = 'helloworld'"), "PASS"),
|
||||
(
|
||||
RunCodeContext(
|
||||
mode="script",
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ class TestSkillAction:
|
|||
"type": "string",
|
||||
"description": "OpenAI API key, For more details, checkout: `https://platform.openai.com/account/api-keys`",
|
||||
},
|
||||
"METAGPT_TEXT_TO_IMAGE_MODEL_URL": {"type": "string", "description": "Model url."},
|
||||
"metagpt_tti_url": {"type": "string", "description": "Model url."},
|
||||
},
|
||||
"required": {"oneOf": ["OPENAI_API_KEY", "METAGPT_TEXT_TO_IMAGE_MODEL_URL"]},
|
||||
"required": {"oneOf": ["OPENAI_API_KEY", "metagpt_tti_url"]},
|
||||
},
|
||||
parameters={
|
||||
"text": Parameter(type="string", description="The text used for image conversion."),
|
||||
|
|
|
|||
|
|
@ -1,27 +1,21 @@
|
|||
import asyncio
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
|
||||
from metagpt.learn.google_search import google_search
|
||||
from metagpt.tools import SearchEngineType
|
||||
|
||||
|
||||
async def mock_google_search():
|
||||
@pytest.mark.asyncio
|
||||
async def test_google_search(search_engine_mocker):
|
||||
class Input(BaseModel):
|
||||
input: str
|
||||
|
||||
inputs = [{"input": "ai agent"}]
|
||||
|
||||
for i in inputs:
|
||||
seed = Input(**i)
|
||||
result = await google_search(seed.input)
|
||||
result = await google_search(
|
||||
seed.input,
|
||||
engine=SearchEngineType.SERPER_GOOGLE,
|
||||
serper_api_key="mock-serper-key",
|
||||
)
|
||||
assert result != ""
|
||||
|
||||
|
||||
def test_suite():
|
||||
loop = asyncio.get_event_loop()
|
||||
task = loop.create_task(mock_google_search())
|
||||
loop.run_until_complete(task)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_suite()
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ async def test_text_to_image(mocker):
|
|||
mocker.patch.object(S3, "cache", return_value="http://mock/s3")
|
||||
|
||||
config = Config.default()
|
||||
assert config.METAGPT_TEXT_TO_IMAGE_MODEL_URL
|
||||
assert config.metagpt_tti_url
|
||||
|
||||
data = await text_to_image("Panda emoji", size_type="512x512", config=config)
|
||||
assert "base64" in data or "http" in data
|
||||
|
|
@ -52,7 +52,7 @@ async def test_openai_text_to_image(mocker):
|
|||
mocker.patch.object(S3, "cache", return_value="http://mock.s3.com/0.png")
|
||||
|
||||
config = Config.default()
|
||||
config.METAGPT_TEXT_TO_IMAGE_MODEL_URL = None
|
||||
config.metagpt_tti_url = None
|
||||
assert config.get_openai_llm()
|
||||
|
||||
data = await text_to_image("Panda emoji", size_type="512x512", config=config)
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ from metagpt.utils.s3 import S3
|
|||
async def test_azure_text_to_speech(mocker):
|
||||
# mock
|
||||
config = Config.default()
|
||||
config.IFLYTEK_API_KEY = None
|
||||
config.IFLYTEK_API_SECRET = None
|
||||
config.IFLYTEK_APP_ID = None
|
||||
config.iflytek_api_key = None
|
||||
config.iflytek_api_secret = None
|
||||
config.iflytek_app_id = None
|
||||
mock_result = mocker.Mock()
|
||||
mock_result.audio_data = b"mock audio data"
|
||||
mock_result.reason = ResultReason.SynthesizingAudioCompleted
|
||||
|
|
@ -32,11 +32,11 @@ async def test_azure_text_to_speech(mocker):
|
|||
mocker.patch.object(S3, "cache", return_value="http://mock.s3.com/1.wav")
|
||||
|
||||
# Prerequisites
|
||||
assert not config.IFLYTEK_APP_ID
|
||||
assert not config.IFLYTEK_API_KEY
|
||||
assert not config.IFLYTEK_API_SECRET
|
||||
assert config.AZURE_TTS_SUBSCRIPTION_KEY and config.AZURE_TTS_SUBSCRIPTION_KEY != "YOUR_API_KEY"
|
||||
assert config.AZURE_TTS_REGION
|
||||
assert not config.iflytek_app_id
|
||||
assert not config.iflytek_api_key
|
||||
assert not config.iflytek_api_secret
|
||||
assert config.azure_tts_subscription_key and config.azure_tts_subscription_key != "YOUR_API_KEY"
|
||||
assert config.azure_tts_region
|
||||
|
||||
config.copy()
|
||||
# test azure
|
||||
|
|
@ -48,8 +48,8 @@ async def test_azure_text_to_speech(mocker):
|
|||
async def test_iflytek_text_to_speech(mocker):
|
||||
# mock
|
||||
config = Config.default()
|
||||
config.AZURE_TTS_SUBSCRIPTION_KEY = None
|
||||
config.AZURE_TTS_REGION = None
|
||||
config.azure_tts_subscription_key = None
|
||||
config.azure_tts_region = None
|
||||
mocker.patch.object(IFlyTekTTS, "synthesize_speech", return_value=None)
|
||||
mock_data = mocker.AsyncMock()
|
||||
mock_data.read.return_value = b"mock iflytek"
|
||||
|
|
@ -58,11 +58,11 @@ async def test_iflytek_text_to_speech(mocker):
|
|||
mocker.patch.object(S3, "cache", return_value="http://mock.s3.com/1.mp3")
|
||||
|
||||
# Prerequisites
|
||||
assert config.IFLYTEK_APP_ID
|
||||
assert config.IFLYTEK_API_KEY
|
||||
assert config.IFLYTEK_API_SECRET
|
||||
assert not config.AZURE_TTS_SUBSCRIPTION_KEY or config.AZURE_TTS_SUBSCRIPTION_KEY == "YOUR_API_KEY"
|
||||
assert not config.AZURE_TTS_REGION
|
||||
assert config.iflytek_app_id
|
||||
assert config.iflytek_api_key
|
||||
assert config.iflytek_api_secret
|
||||
assert not config.azure_tts_subscription_key or config.azure_tts_subscription_key == "YOUR_API_KEY"
|
||||
assert not config.azure_tts_region
|
||||
|
||||
# test azure
|
||||
data = await text_to_speech("panda emoji", config=config)
|
||||
|
|
|
|||
47
tests/metagpt/strategy/test_solver.py
Normal file
47
tests/metagpt/strategy/test_solver.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2024/1/31 13:54
|
||||
@Author : alexanderwu
|
||||
@File : test_solver.py
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from metagpt.actions.action_graph import ActionGraph
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.strategy.search_space import SearchSpace
|
||||
from metagpt.strategy.solver import NaiveSolver
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_solver():
|
||||
from metagpt.actions.write_prd_an import (
|
||||
COMPETITIVE_ANALYSIS,
|
||||
ISSUE_TYPE,
|
||||
PRODUCT_GOALS,
|
||||
REQUIREMENT_POOL,
|
||||
)
|
||||
|
||||
graph = ActionGraph()
|
||||
graph.add_node(ISSUE_TYPE)
|
||||
graph.add_node(PRODUCT_GOALS)
|
||||
graph.add_node(COMPETITIVE_ANALYSIS)
|
||||
graph.add_node(REQUIREMENT_POOL)
|
||||
graph.add_edge(ISSUE_TYPE, PRODUCT_GOALS)
|
||||
graph.add_edge(PRODUCT_GOALS, COMPETITIVE_ANALYSIS)
|
||||
graph.add_edge(PRODUCT_GOALS, REQUIREMENT_POOL)
|
||||
graph.add_edge(COMPETITIVE_ANALYSIS, REQUIREMENT_POOL)
|
||||
search_space = SearchSpace()
|
||||
llm = LLM()
|
||||
context = "Create a 2048 game"
|
||||
solver = NaiveSolver(graph, search_space, llm, context)
|
||||
await solver.solve()
|
||||
|
||||
print("## graph.nodes")
|
||||
print(graph.nodes)
|
||||
for k, v in graph.nodes.items():
|
||||
print(f"{v.key} | prevs: {[i.key for i in v.prevs]} | nexts: {[i.key for i in v.nexts]}")
|
||||
|
||||
assert len(graph.nodes) == 4
|
||||
assert len(graph.execution_order) == 4
|
||||
assert graph.execution_order == [ISSUE_TYPE.key, PRODUCT_GOALS.key, COMPETITIVE_ANALYSIS.key, REQUIREMENT_POOL.key]
|
||||
|
|
@ -14,7 +14,7 @@ from typer.testing import CliRunner
|
|||
|
||||
from metagpt.const import TEST_DATA_PATH
|
||||
from metagpt.logs import logger
|
||||
from metagpt.startup import app
|
||||
from metagpt.software_company import app
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
"""
|
||||
@Time : 2023/5/15 11:40
|
||||
@Author : alexanderwu
|
||||
@File : test_startup.py
|
||||
@File : test_software_company.py
|
||||
"""
|
||||
import pytest
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from metagpt.logs import logger
|
||||
from metagpt.startup import app
|
||||
from metagpt.software_company import app
|
||||
from metagpt.team import Team
|
||||
|
||||
runner = CliRunner()
|
||||
|
|
@ -23,7 +23,7 @@ async def test_empty_team(new_filename):
|
|||
logger.info(history)
|
||||
|
||||
|
||||
def test_startup(new_filename):
|
||||
def test_software_company(new_filename):
|
||||
args = ["Make a cli snake game"]
|
||||
result = runner.invoke(app, args)
|
||||
logger.info(result)
|
||||
|
|
@ -28,10 +28,10 @@ async def test_azure_tts(mocker):
|
|||
mocker.patch.object(Path, "exists", return_value=True)
|
||||
|
||||
# Prerequisites
|
||||
assert config.AZURE_TTS_SUBSCRIPTION_KEY and config.AZURE_TTS_SUBSCRIPTION_KEY != "YOUR_API_KEY"
|
||||
assert config.AZURE_TTS_REGION
|
||||
assert config.azure_tts_subscription_key and config.azure_tts_subscription_key != "YOUR_API_KEY"
|
||||
assert config.azure_tts_region
|
||||
|
||||
azure_tts = AzureTTS(subscription_key=config.AZURE_TTS_SUBSCRIPTION_KEY, region=config.AZURE_TTS_REGION)
|
||||
azure_tts = AzureTTS(subscription_key=config.azure_tts_subscription_key, region=config.azure_tts_region)
|
||||
text = """
|
||||
女儿看见父亲走了进来,问道:
|
||||
<mstts:express-as role="YoungAdultFemale" style="calm">
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ from metagpt.tools.iflytek_tts import IFlyTekTTS, oas3_iflytek_tts
|
|||
async def test_iflytek_tts(mocker):
|
||||
# mock
|
||||
config = Config.default()
|
||||
config.AZURE_TTS_SUBSCRIPTION_KEY = None
|
||||
config.AZURE_TTS_REGION = None
|
||||
config.azure_tts_subscription_key = None
|
||||
config.azure_tts_region = None
|
||||
mocker.patch.object(IFlyTekTTS, "synthesize_speech", return_value=None)
|
||||
mock_data = mocker.AsyncMock()
|
||||
mock_data.read.return_value = b"mock iflytek"
|
||||
|
|
@ -24,15 +24,15 @@ async def test_iflytek_tts(mocker):
|
|||
mock_reader.return_value.__aenter__.return_value = mock_data
|
||||
|
||||
# Prerequisites
|
||||
assert config.IFLYTEK_APP_ID
|
||||
assert config.IFLYTEK_API_KEY
|
||||
assert config.IFLYTEK_API_SECRET
|
||||
assert config.iflytek_app_id
|
||||
assert config.iflytek_api_key
|
||||
assert config.iflytek_api_secret
|
||||
|
||||
result = await oas3_iflytek_tts(
|
||||
text="你好,hello",
|
||||
app_id=config.IFLYTEK_APP_ID,
|
||||
api_key=config.IFLYTEK_API_KEY,
|
||||
api_secret=config.IFLYTEK_API_SECRET,
|
||||
app_id=config.iflytek_app_id,
|
||||
api_key=config.iflytek_api_key,
|
||||
api_secret=config.iflytek_api_secret,
|
||||
)
|
||||
assert result
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ async def test_draw(mocker):
|
|||
mock_post.return_value.__aenter__.return_value = mock_response
|
||||
|
||||
# Prerequisites
|
||||
assert config.METAGPT_TEXT_TO_IMAGE_MODEL_URL
|
||||
assert config.metagpt_tti_url
|
||||
|
||||
binary_data = await oas3_metagpt_text_to_image("Panda emoji")
|
||||
assert binary_data
|
||||
|
|
|
|||
|
|
@ -8,6 +8,17 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from openai.resources.chat.completions import AsyncCompletions
|
||||
from openai.types import CompletionUsage
|
||||
from openai.types.chat.chat_completion import (
|
||||
ChatCompletion,
|
||||
ChatCompletionMessage,
|
||||
Choice,
|
||||
)
|
||||
from openai.types.chat.chat_completion_message_tool_call import (
|
||||
ChatCompletionMessageToolCall,
|
||||
Function,
|
||||
)
|
||||
|
||||
from metagpt.config2 import config
|
||||
from metagpt.const import API_QUESTIONS_PATH, UT_PY_PATH
|
||||
|
|
@ -16,7 +27,43 @@ from metagpt.tools.ut_writer import YFT_PROMPT_PREFIX, UTGenerator
|
|||
|
||||
class TestUTWriter:
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_to_ut_sample(self):
|
||||
async def test_api_to_ut_sample(self, mocker):
|
||||
async def mock_create(*args, **kwargs):
|
||||
return ChatCompletion(
|
||||
id="chatcmpl-8n5fAd21w2J1IIFkI4qxWlNfM7QRC",
|
||||
choices=[
|
||||
Choice(
|
||||
finish_reason="stop",
|
||||
index=0,
|
||||
logprobs=None,
|
||||
message=ChatCompletionMessage(
|
||||
content=None,
|
||||
role="assistant",
|
||||
function_call=None,
|
||||
tool_calls=[
|
||||
ChatCompletionMessageToolCall(
|
||||
id="call_EjjmIY7GMspHu3r9mx8gPA2k",
|
||||
function=Function(
|
||||
arguments='{"code":"import string\\nimport random\\n\\ndef random_string'
|
||||
"(length=10):\\n return ''.join(random.choice(string.ascii_"
|
||||
'lowercase) for i in range(length))"}',
|
||||
name="execute",
|
||||
),
|
||||
type="function",
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
created=1706710532,
|
||||
model="gpt-3.5-turbo-1106",
|
||||
object="chat.completion",
|
||||
system_fingerprint="fp_04f9a1eebf",
|
||||
usage=CompletionUsage(completion_tokens=35, prompt_tokens=1982, total_tokens=2017),
|
||||
)
|
||||
|
||||
mocker.patch.object(AsyncCompletions, "create", mock_create)
|
||||
|
||||
# Prerequisites
|
||||
swagger_file = Path(__file__).parent / "../../data/ut_writer/yft_swaggerApi.json"
|
||||
assert swagger_file.exists()
|
||||
|
|
|
|||
|
|
@ -141,6 +141,32 @@ def test_repair_json_format():
|
|||
output = repair_llm_raw_output(output=raw_output, req_keys=[None], repair_type=RepairType.JSON)
|
||||
assert output == target_output
|
||||
|
||||
raw_output = """
|
||||
{
|
||||
"Language": "en_us", // define language
|
||||
"Programming Language": "Python" # define code language
|
||||
}
|
||||
"""
|
||||
target_output = """{
|
||||
"Language": "en_us",
|
||||
"Programming Language": "Python"
|
||||
}"""
|
||||
output = repair_llm_raw_output(output=raw_output, req_keys=[None], repair_type=RepairType.JSON)
|
||||
assert output == target_output
|
||||
|
||||
raw_output = """
|
||||
{
|
||||
"Language": "#en_us#", // define language
|
||||
"Programming Language": "//Python # Code // Language//" # define code language
|
||||
}
|
||||
"""
|
||||
target_output = """{
|
||||
"Language": "#en_us#",
|
||||
"Programming Language": "//Python # Code // Language//"
|
||||
}"""
|
||||
output = repair_llm_raw_output(output=raw_output, req_keys=[None], repair_type=RepairType.JSON)
|
||||
assert output == target_output
|
||||
|
||||
|
||||
def test_repair_invalid_json():
|
||||
from metagpt.utils.repair_llm_raw_output import repair_invalid_json
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue