mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-11 15:15:18 +02:00
Merge branch 'main' into code_interpreter
This commit is contained in:
commit
38f21137ec
146 changed files with 4466 additions and 1375 deletions
|
|
@ -12,6 +12,7 @@ import logging
|
|||
import os
|
||||
import re
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
import aiohttp.web
|
||||
|
|
@ -270,3 +271,11 @@ def mermaid_mocker(aiohttp_mocker, mermaid_rsp_cache):
|
|||
aiohttp_mocker.rsp_cache = mermaid_rsp_cache
|
||||
aiohttp_mocker.check_funcs = check_funcs
|
||||
yield check_funcs
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def git_dir():
|
||||
"""Fixture to get the unittest directory."""
|
||||
git_dir = Path(__file__).parent / f"unittest/{uuid.uuid4().hex}"
|
||||
git_dir.mkdir(parents=True, exist_ok=True)
|
||||
return git_dir
|
||||
|
|
|
|||
1
tests/data/graph_db/networkx.class_view.json
Normal file
1
tests/data/graph_db/networkx.class_view.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
tests/data/graph_db/networkx.sequence_view.json
Normal file
1
tests/data/graph_db/networkx.sequence_view.json
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
|
@ -149,7 +149,7 @@ sequenceDiagram
|
|||
|
||||
The requirement analysis suggests the need for a clean and intuitive interface. Since we are using a command-line interface, we need to ensure that the text-based UI is as user-friendly as possible. Further clarification on whether a graphical user interface (GUI) is expected in the future would be helpful for planning the extendability of the game."""
|
||||
|
||||
TASKS_SAMPLE = """
|
||||
TASK_SAMPLE = """
|
||||
## Required Python packages
|
||||
|
||||
- random==2.2.1
|
||||
|
|
@ -345,7 +345,7 @@ REFINED_DESIGN_JSON = {
|
|||
"Anything UNCLEAR": "",
|
||||
}
|
||||
|
||||
REFINED_TASKS_JSON = {
|
||||
REFINED_TASK_JSON = {
|
||||
"Required Python packages": ["random==2.2.1", "Tkinter==8.6"],
|
||||
"Required Other language third-party packages": ["No third-party dependencies required"],
|
||||
"Refined Logic Analysis": [
|
||||
|
|
@ -373,7 +373,14 @@ REFINED_TASKS_JSON = {
|
|||
}
|
||||
|
||||
CODE_PLAN_AND_CHANGE_SAMPLE = {
|
||||
"Code Plan And Change": '\n1. Plan for gui.py: Develop the GUI using Tkinter to replace the command-line interface. Start by setting up the main window and event handling. Then, add widgets for displaying the game status, results, and feedback. Implement interactive elements for difficulty selection and visualize the guess history. Finally, create animations for guess feedback and ensure responsiveness across different screen sizes.\n```python\nclass GUI:\n- pass\n+ def __init__(self):\n+ self.setup_window()\n+\n+ def setup_window(self):\n+ # Initialize the main window using Tkinter\n+ pass\n+\n+ def bind_events(self):\n+ # Bind button clicks and other events\n+ pass\n+\n+ def update_feedback(self, message: str):\n+ # Update the feedback label with the given message\n+ pass\n+\n+ def update_attempts(self, attempts: int):\n+ # Update the attempts label with the number of attempts\n+ pass\n+\n+ def update_history(self, history: list):\n+ # Update the history view with the list of past guesses\n+ pass\n+\n+ def show_difficulty_selector(self):\n+ # Show buttons or a dropdown for difficulty selection\n+ pass\n+\n+ def animate_guess_result(self, correct: bool):\n+ # Trigger an animation for correct or incorrect guesses\n+ pass\n```\n\n2. Plan for main.py: Modify the main.py to initialize the GUI and start the event-driven game loop. Ensure that the GUI is the primary interface for user interaction.\n```python\nclass Main:\n def main(self):\n- user_interface = UI()\n- user_interface.start()\n+ graphical_user_interface = GUI()\n+ graphical_user_interface.setup_window()\n+ graphical_user_interface.bind_events()\n+ # Start the Tkinter main loop\n+ pass\n\n if __name__ == "__main__":\n main_instance = Main()\n main_instance.main()\n```\n\n3. Plan for ui.py: Refactor ui.py to work with the new GUI class. Remove command-line interactions and delegate display and input tasks to the GUI.\n```python\nclass UI:\n- def display_message(self, message: str):\n- print(message)\n+\n+ def display_message(self, message: str):\n+ # This method will now pass the message to the GUI to display\n+ pass\n\n- def get_user_input(self, prompt: str) -> str:\n- return input(prompt)\n+\n+ def get_user_input(self, prompt: str) -> str:\n+ # This method will now trigger the GUI to get user input\n+ pass\n\n- def show_attempts(self, attempts: int):\n- print(f"Number of attempts: {attempts}")\n+\n+ def show_attempts(self, attempts: int):\n+ # This method will now update the GUI with the number of attempts\n+ pass\n\n- def show_history(self, history: list):\n- print("Guess history:")\n- for guess in history:\n- print(guess)\n+\n+ def show_history(self, history: list):\n+ # This method will now update the GUI with the guess history\n+ pass\n```\n\n4. Plan for game.py: Ensure game.py remains mostly unchanged as it contains the core game logic. However, make minor adjustments if necessary to integrate with the new GUI.\n```python\nclass Game:\n # No changes required for now\n```\n'
|
||||
"Development Plan": [
|
||||
"Develop the GUI using Tkinter to replace the command-line interface. Start by setting up the main window and event handling. Then, add widgets for displaying the game status, results, and feedback. Implement interactive elements for difficulty selection and visualize the guess history. Finally, create animations for guess feedback and ensure responsiveness across different screen sizes.",
|
||||
"Modify the main.py to initialize the GUI and start the event-driven game loop. Ensure that the GUI is the primary interface for user interaction.",
|
||||
],
|
||||
"Incremental Change": [
|
||||
"""```diff\nclass GUI:\n- pass\n+ def __init__(self):\n+ self.setup_window()\n+\n+ def setup_window(self):\n+ # Initialize the main window using Tkinter\n+ pass\n+\n+ def bind_events(self):\n+ # Bind button clicks and other events\n+ pass\n+\n+ def update_feedback(self, message: str):\n+ # Update the feedback label with the given message\n+ pass\n+\n+ def update_attempts(self, attempts: int):\n+ # Update the attempts label with the number of attempts\n+ pass\n+\n+ def update_history(self, history: list):\n+ # Update the history view with the list of past guesses\n+ pass\n+\n+ def show_difficulty_selector(self):\n+ # Show buttons or a dropdown for difficulty selection\n+ pass\n+\n+ def animate_guess_result(self, correct: bool):\n+ # Trigger an animation for correct or incorrect guesses\n+ pass\n```""",
|
||||
"""```diff\nclass Main:\n def main(self):\n- user_interface = UI()\n- user_interface.start()\n+ graphical_user_interface = GUI()\n+ graphical_user_interface.setup_window()\n+ graphical_user_interface.bind_events()\n+ # Start the Tkinter main loop\n+ pass\n\n if __name__ == "__main__":\n main_instance = Main()\n main_instance.main()\n```\n\n3. Plan for ui.py: Refactor ui.py to work with the new GUI class. Remove command-line interactions and delegate display and input tasks to the GUI.\n```python\nclass UI:\n- def display_message(self, message: str):\n- print(message)\n+\n+ def display_message(self, message: str):\n+ # This method will now pass the message to the GUI to display\n+ pass\n\n- def get_user_input(self, prompt: str) -> str:\n- return input(prompt)\n+\n+ def get_user_input(self, prompt: str) -> str:\n+ # This method will now trigger the GUI to get user input\n+ pass\n\n- def show_attempts(self, attempts: int):\n- print(f"Number of attempts: {attempts}")\n+\n+ def show_attempts(self, attempts: int):\n+ # This method will now update the GUI with the number of attempts\n+ pass\n\n- def show_history(self, history: list):\n- print("Guess history:")\n- for guess in history:\n- print(guess)\n+\n+ def show_history(self, history: list):\n+ # This method will now update the GUI with the guess history\n+ pass\n```\n\n4. Plan for game.py: Ensure game.py remains mostly unchanged as it contains the core game logic. However, make minor adjustments if necessary to integrate with the new GUI.\n```python\nclass Game:\n # No changes required for now\n```\n""",
|
||||
],
|
||||
}
|
||||
|
||||
REFINED_CODE_INPUT_SAMPLE = """
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.mi.ask_review import AskReview
|
||||
from metagpt.actions.di.ask_review import AskReview
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions.mi.debug_code import DebugCode
|
||||
from metagpt.actions.di.debug_code import DebugCode
|
||||
from metagpt.schema import Message
|
||||
|
||||
ErrorStr = """Tested passed:
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.mi.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.actions.di.execute_nb_code import ExecuteNbCode
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.mi.ml_action import WriteCodeWithToolsML
|
||||
from metagpt.actions.di.ml_action import WriteCodeWithToolsML
|
||||
from metagpt.schema import Plan, Task
|
||||
|
||||
|
||||
|
|
@ -2,8 +2,8 @@ import asyncio
|
|||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions.mi.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.actions.mi.write_analysis_code import (
|
||||
from metagpt.actions.di.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.actions.di.write_analysis_code import (
|
||||
WriteCodeWithoutTools,
|
||||
WriteCodeWithTools,
|
||||
)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.mi.write_plan import (
|
||||
from metagpt.actions.di.write_plan import (
|
||||
Plan,
|
||||
Task,
|
||||
WritePlan,
|
||||
|
|
@ -37,7 +37,7 @@ DESIGN = {
|
|||
}
|
||||
|
||||
|
||||
TASKS = {
|
||||
TASK = {
|
||||
"Required Python packages": ["pygame==2.0.1"],
|
||||
"Required Other language third-party packages": ["No third-party dependencies required"],
|
||||
"Logic Analysis": [
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.design_api import WriteDesign
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import Message
|
||||
from tests.data.incremental_dev_project.mock import DESIGN_SAMPLE, REFINED_PRD_JSON
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -25,3 +27,16 @@ async def test_design_api(context):
|
|||
logger.info(result)
|
||||
|
||||
assert result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refined_design_api(context):
|
||||
await context.repo.docs.prd.save(filename="1.txt", content=str(REFINED_PRD_JSON))
|
||||
await context.repo.docs.system_design.save(filename="1.txt", content=DESIGN_SAMPLE)
|
||||
|
||||
design_api = WriteDesign(context=context, llm=LLM())
|
||||
|
||||
result = await design_api.run(Message(content="", instruct_content=None))
|
||||
logger.info(result)
|
||||
|
||||
assert result
|
||||
|
|
|
|||
|
|
@ -9,13 +9,19 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.project_management import WriteTasks
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import Message
|
||||
from tests.data.incremental_dev_project.mock import (
|
||||
REFINED_DESIGN_JSON,
|
||||
REFINED_PRD_JSON,
|
||||
TASK_SAMPLE,
|
||||
)
|
||||
from tests.metagpt.actions.mock_json import DESIGN, PRD
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_design_api(context):
|
||||
async def test_task(context):
|
||||
await context.repo.docs.prd.save("1.txt", content=str(PRD))
|
||||
await context.repo.docs.system_design.save("1.txt", content=str(DESIGN))
|
||||
logger.info(context.git_repo)
|
||||
|
|
@ -26,3 +32,19 @@ async def test_design_api(context):
|
|||
logger.info(result)
|
||||
|
||||
assert result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refined_task(context):
|
||||
await context.repo.docs.prd.save("2.txt", content=str(REFINED_PRD_JSON))
|
||||
await context.repo.docs.system_design.save("2.txt", content=str(REFINED_DESIGN_JSON))
|
||||
await context.repo.docs.task.save("2.txt", content=TASK_SAMPLE)
|
||||
|
||||
logger.info(context.git_repo)
|
||||
|
||||
action = WriteTasks(context=context, llm=LLM())
|
||||
|
||||
result = await action.run(Message(content="", instruct_content=None))
|
||||
logger.info(result)
|
||||
|
||||
assert result
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@ from openai._models import BaseModel
|
|||
|
||||
from metagpt.actions.action_node import ActionNode, dict_to_markdown
|
||||
from metagpt.actions.project_management import NEW_REQ_TEMPLATE
|
||||
from metagpt.actions.project_management_an import REFINED_PM_NODE
|
||||
from metagpt.actions.project_management_an import PM_NODE, REFINED_PM_NODE
|
||||
from metagpt.llm import LLM
|
||||
from tests.data.incremental_dev_project.mock import (
|
||||
REFINED_DESIGN_JSON,
|
||||
REFINED_TASKS_JSON,
|
||||
TASKS_SAMPLE,
|
||||
REFINED_TASK_JSON,
|
||||
TASK_SAMPLE,
|
||||
)
|
||||
from tests.metagpt.actions.mock_json import TASK
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
|
|
@ -24,20 +25,40 @@ def llm():
|
|||
return LLM()
|
||||
|
||||
|
||||
def mock_refined_tasks_json():
|
||||
return REFINED_TASKS_JSON
|
||||
def mock_refined_task_json():
|
||||
return REFINED_TASK_JSON
|
||||
|
||||
|
||||
def mock_task_json():
|
||||
return TASK
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_project_management_an(mocker):
|
||||
root = ActionNode.from_children(
|
||||
"ProjectManagement", [ActionNode(key="", expected_type=str, instruction="", example="")]
|
||||
)
|
||||
root.instruct_content = BaseModel()
|
||||
root.instruct_content.model_dump = mock_task_json
|
||||
mocker.patch("metagpt.actions.project_management_an.PM_NODE.fill", return_value=root)
|
||||
|
||||
node = await PM_NODE.fill(dict_to_markdown(REFINED_DESIGN_JSON), llm)
|
||||
|
||||
assert "Logic Analysis" in node.instruct_content.model_dump()
|
||||
assert "Task list" in node.instruct_content.model_dump()
|
||||
assert "Shared Knowledge" in node.instruct_content.model_dump()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_project_management_an_inc(mocker):
|
||||
root = ActionNode.from_children(
|
||||
"RefinedProjectManagement", [ActionNode(key="", expected_type=str, instruction="", example="")]
|
||||
)
|
||||
root.instruct_content = BaseModel()
|
||||
root.instruct_content.model_dump = mock_refined_tasks_json
|
||||
root.instruct_content.model_dump = mock_refined_task_json
|
||||
mocker.patch("metagpt.actions.project_management_an.REFINED_PM_NODE.fill", return_value=root)
|
||||
|
||||
prompt = NEW_REQ_TEMPLATE.format(old_task=TASKS_SAMPLE, context=dict_to_markdown(REFINED_DESIGN_JSON))
|
||||
prompt = NEW_REQ_TEMPLATE.format(old_task=TASK_SAMPLE, context=dict_to_markdown(REFINED_DESIGN_JSON))
|
||||
node = await REFINED_PM_NODE.fill(prompt, llm)
|
||||
|
||||
assert "Refined Logic Analysis" in node.instruct_content.model_dump()
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ from metagpt.actions.rebuild_class_view import RebuildClassView
|
|||
from metagpt.llm import LLM
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
@pytest.mark.asyncio
|
||||
async def test_rebuild(context):
|
||||
action = RebuildClassView(
|
||||
|
|
@ -24,6 +23,8 @@ async def test_rebuild(context):
|
|||
context=context,
|
||||
)
|
||||
await action.run()
|
||||
rows = await action.graph_db.select()
|
||||
assert rows
|
||||
assert context.repo.docs.graph_repo.changed_files
|
||||
|
||||
|
||||
|
|
@ -46,6 +47,12 @@ def test_align_path(path, direction, diff, want):
|
|||
("/Users/x/github/MetaGPT/metagpt", "/Users/x/github/MetaGPT/metagpt", "=", "."),
|
||||
("/Users/x/github/MetaGPT", "/Users/x/github/MetaGPT/metagpt", "-", "metagpt"),
|
||||
("/Users/x/github/MetaGPT/metagpt", "/Users/x/github/MetaGPT", "+", "metagpt"),
|
||||
(
|
||||
"/Users/x/github/MetaGPT-env/lib/python3.9/site-packages/moviepy",
|
||||
"/Users/x/github/MetaGPT-env/lib/python3.9/site-packages/",
|
||||
"+",
|
||||
"moviepy",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_diff_path(path_root, package_root, want_direction, want_diff):
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
@Time : 2024/1/4
|
||||
@Author : mashenquan
|
||||
@File : test_rebuild_sequence_view.py
|
||||
@Desc : Unit tests for reconstructing the sequence diagram from a source code project.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -14,25 +15,40 @@ from metagpt.const import GRAPH_REPO_FILE_REPO
|
|||
from metagpt.llm import LLM
|
||||
from metagpt.utils.common import aread
|
||||
from metagpt.utils.git_repository import ChangeType
|
||||
from metagpt.utils.graph_repository import SPO
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skip
|
||||
async def test_rebuild(context):
|
||||
async def test_rebuild(context, mocker):
|
||||
# Mock
|
||||
data = await aread(filename=Path(__file__).parent / "../../data/graph_db/networkx.json")
|
||||
data = await aread(filename=Path(__file__).parent / "../../data/graph_db/networkx.class_view.json")
|
||||
graph_db_filename = Path(context.repo.workdir.name).with_suffix(".json")
|
||||
await context.repo.docs.graph_repo.save(filename=str(graph_db_filename), content=data)
|
||||
context.git_repo.add_change({f"{GRAPH_REPO_FILE_REPO}/{graph_db_filename}": ChangeType.UNTRACTED})
|
||||
context.git_repo.commit("commit1")
|
||||
# mock_spo = SPO(
|
||||
# subject="metagpt/startup.py:__name__:__main__",
|
||||
# predicate="has_page_info",
|
||||
# object_='{"lineno":78,"end_lineno":79,"type_name":"ast.If","tokens":["__name__","__main__"],"properties":{}}',
|
||||
# )
|
||||
mock_spo = SPO(
|
||||
subject="metagpt/management/skill_manager.py:__name__:__main__",
|
||||
predicate="has_page_info",
|
||||
object_='{"lineno":113,"end_lineno":116,"type_name":"ast.If","tokens":["__name__","__main__"],"properties":{}}',
|
||||
)
|
||||
mocker.patch.object(RebuildSequenceView, "_search_main_entry", return_value=[mock_spo])
|
||||
|
||||
action = RebuildSequenceView(
|
||||
name="RedBean",
|
||||
i_context=str(Path(__file__).parent.parent.parent.parent / "metagpt"),
|
||||
i_context=str(
|
||||
Path(__file__).parent.parent.parent.parent / "metagpt/management/skill_manager.py:__name__:__main__"
|
||||
),
|
||||
llm=LLM(),
|
||||
context=context,
|
||||
)
|
||||
await action.run()
|
||||
rows = await action.graph_db.select()
|
||||
assert rows
|
||||
assert context.repo.docs.graph_repo.changed_files
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,13 @@
|
|||
@File : test_summarize_code.py
|
||||
@Modifiled By: mashenquan, 2023-12-6. Unit test for summarize_code.py
|
||||
"""
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions.summarize_code import SummarizeCode
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import CodeSummarizeContext
|
||||
from tests.mock.mock_llm import MockLLM
|
||||
|
||||
DESIGN_CONTENT = """
|
||||
{"Implementation approach": "To develop this snake game, we will use the Python language and choose the Pygame library. Pygame is an open-source Python module collection specifically designed for writing video games. It provides functionalities such as displaying images and playing sounds, making it suitable for creating intuitive and responsive user interfaces. We will ensure efficient game logic to prevent any delays during gameplay. The scoring system will be simple, with the snake gaining points for each food it eats. We will use Pygame's event handling system to implement pause and resume functionality, as well as high-score tracking. The difficulty will increase by speeding up the snake's movement. In the initial version, we will focus on single-player mode and consider adding multiplayer mode and customizable skins in future updates. Based on the new requirement, we will also add a moving obstacle that appears randomly. If the snake eats this obstacle, the game will end. If the snake does not eat the obstacle, it will disappear after 5 seconds. For this, we need to add mechanisms for obstacle generation, movement, and disappearance in the game logic.", "Project_name": "snake_game", "File list": ["main.py", "game.py", "snake.py", "food.py", "obstacle.py", "scoreboard.py", "constants.py", "assets/styles.css", "assets/index.html"], "Data structures and interfaces": "```mermaid\n classDiagram\n class Game{\n +int score\n +int speed\n +bool game_over\n +bool paused\n +Snake snake\n +Food food\n +Obstacle obstacle\n +Scoreboard scoreboard\n +start_game() void\n +pause_game() void\n +resume_game() void\n +end_game() void\n +increase_difficulty() void\n +update() void\n +render() void\n Game()\n }\n class Snake{\n +list body_parts\n +str direction\n +bool grow\n +move() void\n +grow() void\n +check_collision() bool\n Snake()\n }\n class Food{\n +tuple position\n +spawn() void\n Food()\n }\n class Obstacle{\n +tuple position\n +int lifetime\n +bool active\n +spawn() void\n +move() void\n +check_collision() bool\n +disappear() void\n Obstacle()\n }\n class Scoreboard{\n +int high_score\n +update_score(int) void\n +reset_score() void\n +load_high_score() void\n +save_high_score() void\n Scoreboard()\n }\n class Constants{\n }\n Game \"1\" -- \"1\" Snake: has\n Game \"1\" -- \"1\" Food: has\n Game \"1\" -- \"1\" Obstacle: has\n Game \"1\" -- \"1\" Scoreboard: has\n ```", "Program call flow": "```sequenceDiagram\n participant M as Main\n participant G as Game\n participant S as Snake\n participant F as Food\n participant O as Obstacle\n participant SB as Scoreboard\n M->>G: start_game()\n loop game loop\n G->>S: move()\n G->>S: check_collision()\n G->>F: spawn()\n G->>O: spawn()\n G->>O: move()\n G->>O: check_collision()\n G->>O: disappear()\n G->>SB: update_score(score)\n G->>G: update()\n G->>G: render()\n alt if paused\n M->>G: pause_game()\n M->>G: resume_game()\n end\n alt if game_over\n G->>M: end_game()\n end\n end\n```", "Anything UNCLEAR": "There is no need for further clarification as the requirements are already clear."}
|
||||
|
|
@ -175,13 +174,87 @@ class Snake:
|
|||
|
||||
"""
|
||||
|
||||
mock_rsp = """
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Game{
|
||||
+int score
|
||||
+int level
|
||||
+Snake snake
|
||||
+Food food
|
||||
+start_game() void
|
||||
+initialize_game() void
|
||||
+game_loop() void
|
||||
+update() void
|
||||
+draw() void
|
||||
+handle_events() void
|
||||
+check_collision() void
|
||||
+increase_score() void
|
||||
+increase_level() void
|
||||
+game_over() void
|
||||
Game()
|
||||
}
|
||||
class Snake{
|
||||
+list body
|
||||
+tuple direction
|
||||
+move() void
|
||||
+change_direction(direction: str) void
|
||||
+grow() void
|
||||
+get_head() tuple
|
||||
+get_body() list
|
||||
Snake()
|
||||
}
|
||||
class Food{
|
||||
+tuple position
|
||||
+generate() void
|
||||
+get_position() tuple
|
||||
Food()
|
||||
}
|
||||
Game "1" -- "1" Snake: has
|
||||
Game "1" -- "1" Food: has
|
||||
```
|
||||
|
||||
```sequenceDiagram
|
||||
participant M as Main
|
||||
participant G as Game
|
||||
participant S as Snake
|
||||
participant F as Food
|
||||
M->>G: start_game()
|
||||
G->>G: initialize_game()
|
||||
G->>G: game_loop()
|
||||
G->>S: move()
|
||||
G->>S: change_direction()
|
||||
G->>S: grow()
|
||||
G->>F: generate()
|
||||
S->>S: move()
|
||||
S->>S: change_direction()
|
||||
S->>S: grow()
|
||||
F->>F: generate()
|
||||
```
|
||||
|
||||
## Summary
|
||||
The code consists of the main game logic, including the Game, Snake, and Food classes. The game loop is responsible for updating and drawing the game elements, handling events, checking collisions, and managing the game state. The Snake class handles the movement, growth, and direction changes of the snake, while the Food class is responsible for generating and tracking the position of food items.
|
||||
|
||||
## TODOs
|
||||
- Modify 'game.py' to add the implementation of obstacle handling and interaction with the game loop.
|
||||
- Implement 'obstacle.py' to include the methods for spawning, moving, and disappearing of obstacles, as well as collision detection with the snake.
|
||||
- Update 'main.py' to initialize the obstacle and incorporate it into the game loop.
|
||||
- Update the mermaid call flow diagram to include the interaction with the obstacle.
|
||||
|
||||
```python
|
||||
{
|
||||
"files_to_modify": {
|
||||
"game.py": "Add obstacle handling and interaction with the game loop",
|
||||
"obstacle.py": "Implement obstacle class with necessary methods",
|
||||
"main.py": "Initialize the obstacle and incorporate it into the game loop"
|
||||
}
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
@pytest.mark.asyncio
|
||||
async def test_summarize_code(context):
|
||||
git_dir = Path(__file__).parent / f"unittest/{uuid.uuid4().hex}"
|
||||
git_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
async def test_summarize_code(context, mocker):
|
||||
context.src_workspace = context.git_repo.workdir / "src"
|
||||
await context.repo.docs.system_design.save(filename="1.json", content=DESIGN_CONTENT)
|
||||
await context.repo.docs.task.save(filename="1.json", content=TASK_CONTENT)
|
||||
|
|
@ -190,6 +263,7 @@ async def test_summarize_code(context):
|
|||
await context.repo.srcs.save(filename="game.py", content=GAME_PY)
|
||||
await context.repo.srcs.save(filename="main.py", content=MAIN_PY)
|
||||
await context.repo.srcs.save(filename="snake.py", content=SNAKE_PY)
|
||||
mocker.patch.object(MockLLM, "_mock_rsp", return_value=mock_rsp)
|
||||
|
||||
all_files = context.repo.srcs.all_files
|
||||
summarization_context = CodeSummarizeContext(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
@File : test_write_code.py
|
||||
@Modifiled By: mashenquan, 2023-12-6. According to RFC 135
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
|
@ -14,10 +14,27 @@ import pytest
|
|||
from metagpt.actions.write_code import WriteCode
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import CodingContext, Document
|
||||
from metagpt.utils.common import aread
|
||||
from metagpt.utils.common import CodeParser, aread
|
||||
from tests.data.incremental_dev_project.mock import (
|
||||
CODE_PLAN_AND_CHANGE_SAMPLE,
|
||||
REFINED_CODE_INPUT_SAMPLE,
|
||||
REFINED_DESIGN_JSON,
|
||||
REFINED_TASK_JSON,
|
||||
)
|
||||
from tests.metagpt.actions.mock_markdown import TASKS_2, WRITE_CODE_PROMPT_SAMPLE
|
||||
|
||||
|
||||
def setup_inc_workdir(context, inc: bool = False):
|
||||
"""setup incremental workdir for testing"""
|
||||
context.src_workspace = context.git_repo.workdir / "src"
|
||||
if inc:
|
||||
context.config.inc = inc
|
||||
context.repo.old_workspace = context.repo.git_repo.workdir / "old"
|
||||
context.config.project_path = "old"
|
||||
|
||||
return context
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_code(context):
|
||||
# Prerequisites
|
||||
|
|
@ -81,5 +98,66 @@ async def test_write_code_deps(context):
|
|||
assert rsp.code_doc.content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_refined_code(context, git_dir):
|
||||
# Prerequisites
|
||||
context = setup_inc_workdir(context, inc=True)
|
||||
await context.repo.docs.system_design.save(filename="1.json", content=json.dumps(REFINED_DESIGN_JSON))
|
||||
await context.repo.docs.task.save(filename="1.json", content=json.dumps(REFINED_TASK_JSON))
|
||||
await context.repo.docs.code_plan_and_change.save(
|
||||
filename="1.json", content=json.dumps(CODE_PLAN_AND_CHANGE_SAMPLE)
|
||||
)
|
||||
|
||||
# old_workspace contains the legacy code
|
||||
await context.repo.with_src_path(context.repo.old_workspace).srcs.save(
|
||||
filename="game.py", content=CodeParser.parse_code(block="", text=REFINED_CODE_INPUT_SAMPLE)
|
||||
)
|
||||
|
||||
ccontext = CodingContext(
|
||||
filename="game.py",
|
||||
design_doc=await context.repo.docs.system_design.get(filename="1.json"),
|
||||
task_doc=await context.repo.docs.task.get(filename="1.json"),
|
||||
code_plan_and_change_doc=await context.repo.docs.code_plan_and_change.get(filename="1.json"),
|
||||
code_doc=Document(filename="game.py", content="", root_path="src"),
|
||||
)
|
||||
coding_doc = Document(root_path="src", filename="game.py", content=ccontext.json())
|
||||
|
||||
action = WriteCode(i_context=coding_doc, context=context)
|
||||
rsp = await action.run()
|
||||
assert rsp
|
||||
assert rsp.code_doc.content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_codes(context):
|
||||
# Prerequisites
|
||||
context = setup_inc_workdir(context, inc=True)
|
||||
for filename in ["game.py", "ui.py"]:
|
||||
await context.repo.with_src_path(context.src_workspace).srcs.save(
|
||||
filename=filename, content=f"# {filename}\nnew code ..."
|
||||
)
|
||||
await context.repo.with_src_path(context.repo.old_workspace).srcs.save(
|
||||
filename=filename, content=f"# {filename}\nlegacy code ..."
|
||||
)
|
||||
|
||||
await context.repo.with_src_path(context.repo.old_workspace).srcs.save(
|
||||
filename="gui.py", content="# gui.py\nlegacy code ..."
|
||||
)
|
||||
await context.repo.with_src_path(context.repo.old_workspace).srcs.save(
|
||||
filename="main.py", content='# main.py\nif __name__ == "__main__":\n main()'
|
||||
)
|
||||
task_doc = Document(filename="1.json", content=json.dumps(REFINED_TASK_JSON))
|
||||
|
||||
context.repo = context.repo.with_src_path(context.src_workspace)
|
||||
# Ready to write gui.py
|
||||
codes = await WriteCode.get_codes(task_doc=task_doc, exclude="gui.py", project_repo=context.repo)
|
||||
codes_inc = await WriteCode.get_codes(task_doc=task_doc, exclude="gui.py", project_repo=context.repo, use_inc=True)
|
||||
|
||||
logger.info(codes)
|
||||
logger.info(codes_inc)
|
||||
assert codes
|
||||
assert codes_inc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
@Author : mannaandpoem
|
||||
@File : test_write_code_plan_and_change_an.py
|
||||
"""
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from openai._models import BaseModel
|
||||
|
||||
|
|
@ -14,15 +16,21 @@ from metagpt.actions.write_code_plan_and_change_an import (
|
|||
REFINED_TEMPLATE,
|
||||
WriteCodePlanAndChange,
|
||||
)
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import CodePlanAndChangeContext
|
||||
from metagpt.utils.common import CodeParser
|
||||
from tests.data.incremental_dev_project.mock import (
|
||||
CODE_PLAN_AND_CHANGE_SAMPLE,
|
||||
DESIGN_SAMPLE,
|
||||
NEW_REQUIREMENT_SAMPLE,
|
||||
REFINED_CODE_INPUT_SAMPLE,
|
||||
REFINED_CODE_SAMPLE,
|
||||
TASKS_SAMPLE,
|
||||
REFINED_DESIGN_JSON,
|
||||
REFINED_PRD_JSON,
|
||||
REFINED_TASK_JSON,
|
||||
TASK_SAMPLE,
|
||||
)
|
||||
from tests.metagpt.actions.test_write_code import setup_inc_workdir
|
||||
|
||||
|
||||
def mock_code_plan_and_change():
|
||||
|
|
@ -30,27 +38,35 @@ def mock_code_plan_and_change():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_code_plan_and_change_an(mocker):
|
||||
async def test_write_code_plan_and_change_an(mocker, context, git_dir):
|
||||
context = setup_inc_workdir(context, inc=True)
|
||||
await context.repo.docs.prd.save(filename="2.json", content=json.dumps(REFINED_PRD_JSON))
|
||||
await context.repo.docs.system_design.save(filename="2.json", content=json.dumps(REFINED_DESIGN_JSON))
|
||||
await context.repo.docs.task.save(filename="2.json", content=json.dumps(REFINED_TASK_JSON))
|
||||
|
||||
await context.repo.with_src_path(context.repo.old_workspace).srcs.save(
|
||||
filename="game.py", content=CodeParser.parse_code(block="", text=REFINED_CODE_INPUT_SAMPLE)
|
||||
)
|
||||
|
||||
root = ActionNode.from_children(
|
||||
"WriteCodePlanAndChange", [ActionNode(key="", expected_type=str, instruction="", example="")]
|
||||
)
|
||||
root.instruct_content = BaseModel()
|
||||
root.instruct_content.model_dump = mock_code_plan_and_change
|
||||
mocker.patch("metagpt.actions.write_code_plan_and_change_an.WriteCodePlanAndChange.run", return_value=root)
|
||||
|
||||
requirement = "New requirement"
|
||||
prd_filename = "prd.md"
|
||||
design_filename = "design.md"
|
||||
task_filename = "task.md"
|
||||
code_plan_and_change_context = CodePlanAndChangeContext(
|
||||
requirement=requirement,
|
||||
prd_filename=prd_filename,
|
||||
design_filename=design_filename,
|
||||
task_filename=task_filename,
|
||||
mocker.patch(
|
||||
"metagpt.actions.write_code_plan_and_change_an.WRITE_CODE_PLAN_AND_CHANGE_NODE.fill", return_value=root
|
||||
)
|
||||
node = await WriteCodePlanAndChange(i_context=code_plan_and_change_context).run()
|
||||
|
||||
assert "Code Plan And Change" in node.instruct_content.model_dump()
|
||||
code_plan_and_change_context = CodePlanAndChangeContext(
|
||||
requirement="New requirement",
|
||||
prd_filename="2.json",
|
||||
design_filename="2.json",
|
||||
task_filename="2.json",
|
||||
)
|
||||
node = await WriteCodePlanAndChange(i_context=code_plan_and_change_context, context=context).run()
|
||||
|
||||
assert "Development Plan" in node.instruct_content.model_dump()
|
||||
assert "Incremental Change" in node.instruct_content.model_dump()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -60,7 +76,7 @@ async def test_refine_code(mocker):
|
|||
user_requirement=NEW_REQUIREMENT_SAMPLE,
|
||||
code_plan_and_change=CODE_PLAN_AND_CHANGE_SAMPLE,
|
||||
design=DESIGN_SAMPLE,
|
||||
task=TASKS_SAMPLE,
|
||||
task=TASK_SAMPLE,
|
||||
code=REFINED_CODE_INPUT_SAMPLE,
|
||||
logs="",
|
||||
feedback="",
|
||||
|
|
@ -69,3 +85,25 @@ async def test_refine_code(mocker):
|
|||
)
|
||||
code = await WriteCode().write_code(prompt=prompt)
|
||||
assert "def" in code
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_old_code(context, git_dir):
|
||||
context = setup_inc_workdir(context, inc=True)
|
||||
await context.repo.with_src_path(context.repo.old_workspace).srcs.save(
|
||||
filename="game.py", content=REFINED_CODE_INPUT_SAMPLE
|
||||
)
|
||||
|
||||
code_plan_and_change_context = CodePlanAndChangeContext(
|
||||
requirement="New requirement",
|
||||
prd_filename="1.json",
|
||||
design_filename="1.json",
|
||||
task_filename="1.json",
|
||||
)
|
||||
action = WriteCodePlanAndChange(context=context, i_context=code_plan_and_change_context)
|
||||
|
||||
old_codes = await action.get_old_codes()
|
||||
logger.info(old_codes)
|
||||
|
||||
assert "def" in old_codes
|
||||
assert "class" in old_codes
|
||||
|
|
|
|||
|
|
@ -32,5 +32,28 @@ def add(a, b):
|
|||
print(f"输出内容: {captured.out}")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_code_review_inc(capfd, context):
|
||||
context.src_workspace = context.repo.workdir / "srcs"
|
||||
context.config.inc = True
|
||||
code = """
|
||||
def add(a, b):
|
||||
return a +
|
||||
"""
|
||||
code_plan_and_change = """
|
||||
def add(a, b):
|
||||
- return a +
|
||||
+ return a + b
|
||||
"""
|
||||
coding_context = CodingContext(
|
||||
filename="math.py",
|
||||
design_doc=Document(content="编写一个从a加b的函数,返回a+b"),
|
||||
code_doc=Document(content=code),
|
||||
code_plan_and_change_doc=Document(content=code_plan_and_change),
|
||||
)
|
||||
|
||||
await WriteCodeReview(i_context=coding_context, context=context).run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
@File : test_write_prd.py
|
||||
@Modified By: mashenquan, 2023-11-1. According to Chapter 2.2.1 and 2.2.2 of RFC 116, replace `handle` with `run`.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions import UserRequirement, WritePRD
|
||||
|
|
@ -15,6 +16,8 @@ from metagpt.roles.product_manager import ProductManager
|
|||
from metagpt.roles.role import RoleReactMode
|
||||
from metagpt.schema import Message
|
||||
from metagpt.utils.common import any_to_str
|
||||
from tests.data.incremental_dev_project.mock import NEW_REQUIREMENT_SAMPLE, PRD_SAMPLE
|
||||
from tests.metagpt.actions.test_write_code import setup_inc_workdir
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -34,5 +37,41 @@ async def test_write_prd(new_filename, context):
|
|||
assert product_manager.context.repo.docs.prd.changed_files
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_prd_inc(new_filename, context, git_dir):
|
||||
context = setup_inc_workdir(context, inc=True)
|
||||
await context.repo.docs.prd.save("1.txt", PRD_SAMPLE)
|
||||
await context.repo.docs.save(filename=REQUIREMENT_FILENAME, content=NEW_REQUIREMENT_SAMPLE)
|
||||
|
||||
action = WritePRD(context=context)
|
||||
prd = await action.run(Message(content=NEW_REQUIREMENT_SAMPLE, instruct_content=None))
|
||||
logger.info(NEW_REQUIREMENT_SAMPLE)
|
||||
logger.info(prd)
|
||||
|
||||
# Assert the prd is not None or empty
|
||||
assert prd is not None
|
||||
assert prd.content != ""
|
||||
assert "Refined Requirements" in prd.content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fix_debug(new_filename, context, git_dir):
|
||||
context.src_workspace = context.git_repo.workdir / context.git_repo.workdir.name
|
||||
|
||||
await context.repo.with_src_path(context.src_workspace).srcs.save(
|
||||
filename="main.py", content='if __name__ == "__main__":\nmain()'
|
||||
)
|
||||
requirements = "Please fix the bug in the code."
|
||||
await context.repo.docs.save(filename=REQUIREMENT_FILENAME, content=requirements)
|
||||
action = WritePRD(context=context)
|
||||
|
||||
prd = await action.run(Message(content=requirements, instruct_content=None))
|
||||
logger.info(prd)
|
||||
|
||||
# Assert the prd is not None or empty
|
||||
assert prd is not None
|
||||
assert prd.content != ""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
@File : test_faiss_store.py
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from metagpt.const import EXAMPLE_PATH
|
||||
|
|
@ -14,8 +17,17 @@ from metagpt.logs import logger
|
|||
from metagpt.roles import Sales
|
||||
|
||||
|
||||
def mock_openai_embed_documents(self, texts: list[str], chunk_size: Optional[int] = 0) -> list[list[float]]:
|
||||
num = len(texts)
|
||||
embeds = np.random.randint(1, 100, size=(num, 1536)) # 1536: openai embedding dim
|
||||
embeds = (embeds - embeds.mean(axis=0)) / (embeds.std(axis=0))
|
||||
return embeds
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_json():
|
||||
async def test_search_json(mocker):
|
||||
mocker.patch("langchain_community.embeddings.openai.OpenAIEmbeddings.embed_documents", mock_openai_embed_documents)
|
||||
|
||||
store = FaissStore(EXAMPLE_PATH / "example.json")
|
||||
role = Sales(profile="Sales", store=store)
|
||||
query = "Which facial cleanser is good for oily skin?"
|
||||
|
|
@ -24,7 +36,9 @@ async def test_search_json():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_xlsx():
|
||||
async def test_search_xlsx(mocker):
|
||||
mocker.patch("langchain_community.embeddings.openai.OpenAIEmbeddings.embed_documents", mock_openai_embed_documents)
|
||||
|
||||
store = FaissStore(EXAMPLE_PATH / "example.xlsx")
|
||||
role = Sales(profile="Sales", store=store)
|
||||
query = "Which facial cleanser is good for oily skin?"
|
||||
|
|
@ -33,7 +47,9 @@ async def test_search_xlsx():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write():
|
||||
async def test_write(mocker):
|
||||
mocker.patch("langchain_community.embeddings.openai.OpenAIEmbeddings.embed_documents", mock_openai_embed_documents)
|
||||
|
||||
store = FaissStore(EXAMPLE_PATH / "example.xlsx", meta_col="Answer", content_col="Question")
|
||||
_faiss_store = store.write()
|
||||
assert _faiss_store.docstore
|
||||
|
|
|
|||
33
tests/metagpt/memory/mock_text_embed.py
Normal file
33
tests/metagpt/memory/mock_text_embed.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc :
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
|
||||
dim = 1536 # openai embedding dim
|
||||
|
||||
text_embed_arr = [
|
||||
{"text": "Write a cli snake game", "embed": np.zeros(shape=[1, dim])}, # mock data, same as below
|
||||
{"text": "Write a game of cli snake", "embed": np.zeros(shape=[1, dim])},
|
||||
{"text": "Write a 2048 web game", "embed": np.ones(shape=[1, dim])},
|
||||
{"text": "Write a Battle City", "embed": np.ones(shape=[1, dim])},
|
||||
{
|
||||
"text": "The user has requested the creation of a command-line interface (CLI) snake game",
|
||||
"embed": np.zeros(shape=[1, dim]),
|
||||
},
|
||||
{"text": "The request is command-line interface (CLI) snake game", "embed": np.zeros(shape=[1, dim])},
|
||||
{
|
||||
"text": "Incorporate basic features of a snake game such as scoring and increasing difficulty",
|
||||
"embed": np.ones(shape=[1, dim]),
|
||||
},
|
||||
]
|
||||
|
||||
text_idx_dict = {item["text"]: idx for idx, item in enumerate(text_embed_arr)}
|
||||
|
||||
|
||||
def mock_openai_embed_documents(self, texts: list[str], chunk_size: Optional[int] = 0) -> list[list[float]]:
|
||||
idx = text_idx_dict.get(texts[0])
|
||||
embed = text_embed_arr[idx].get("embed")
|
||||
return embed
|
||||
|
|
@ -4,20 +4,22 @@
|
|||
@Desc : unittest of `metagpt/memory/longterm_memory.py`
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions import UserRequirement
|
||||
from metagpt.config2 import config
|
||||
from metagpt.memory.longterm_memory import LongTermMemory
|
||||
from metagpt.roles.role import RoleContext
|
||||
from metagpt.schema import Message
|
||||
|
||||
os.environ.setdefault("OPENAI_API_KEY", config.get_openai_llm().api_key)
|
||||
from tests.metagpt.memory.mock_text_embed import (
|
||||
mock_openai_embed_documents,
|
||||
text_embed_arr,
|
||||
)
|
||||
|
||||
|
||||
def test_ltm_search():
|
||||
def test_ltm_search(mocker):
|
||||
mocker.patch("langchain_community.embeddings.openai.OpenAIEmbeddings.embed_documents", mock_openai_embed_documents)
|
||||
|
||||
role_id = "UTUserLtm(Product Manager)"
|
||||
from metagpt.environment import Environment
|
||||
|
||||
|
|
@ -27,20 +29,20 @@ def test_ltm_search():
|
|||
ltm = LongTermMemory()
|
||||
ltm.recover_memory(role_id, rc)
|
||||
|
||||
idea = "Write a cli snake game"
|
||||
idea = text_embed_arr[0].get("text", "Write a cli snake game")
|
||||
message = Message(role="User", content=idea, cause_by=UserRequirement)
|
||||
news = ltm.find_news([message])
|
||||
assert len(news) == 1
|
||||
ltm.add(message)
|
||||
|
||||
sim_idea = "Write a game of cli snake"
|
||||
sim_idea = text_embed_arr[1].get("text", "Write a game of cli snake")
|
||||
|
||||
sim_message = Message(role="User", content=sim_idea, cause_by=UserRequirement)
|
||||
news = ltm.find_news([sim_message])
|
||||
assert len(news) == 0
|
||||
ltm.add(sim_message)
|
||||
|
||||
new_idea = "Write a 2048 web game"
|
||||
new_idea = text_embed_arr[2].get("text", "Write a 2048 web game")
|
||||
new_message = Message(role="User", content=new_idea, cause_by=UserRequirement)
|
||||
news = ltm.find_news([new_message])
|
||||
assert len(news) == 1
|
||||
|
|
@ -56,7 +58,7 @@ def test_ltm_search():
|
|||
news = ltm_new.find_news([sim_message])
|
||||
assert len(news) == 0
|
||||
|
||||
new_idea = "Write a Battle City"
|
||||
new_idea = text_embed_arr[3].get("text", "Write a Battle City")
|
||||
new_message = Message(role="User", content=new_idea, cause_by=UserRequirement)
|
||||
news = ltm_new.find_news([new_message])
|
||||
assert len(news) == 1
|
||||
|
|
|
|||
|
|
@ -4,23 +4,25 @@
|
|||
@Desc : the unittests of metagpt/memory/memory_storage.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from metagpt.actions import UserRequirement, WritePRD
|
||||
from metagpt.actions.action_node import ActionNode
|
||||
from metagpt.config2 import config
|
||||
from metagpt.const import DATA_PATH
|
||||
from metagpt.memory.memory_storage import MemoryStorage
|
||||
from metagpt.schema import Message
|
||||
|
||||
os.environ.setdefault("OPENAI_API_KEY", config.get_openai_llm().api_key)
|
||||
from tests.metagpt.memory.mock_text_embed import (
|
||||
mock_openai_embed_documents,
|
||||
text_embed_arr,
|
||||
)
|
||||
|
||||
|
||||
def test_idea_message():
|
||||
idea = "Write a cli snake game"
|
||||
def test_idea_message(mocker):
|
||||
mocker.patch("langchain_community.embeddings.openai.OpenAIEmbeddings.embed_documents", mock_openai_embed_documents)
|
||||
|
||||
idea = text_embed_arr[0].get("text", "Write a cli snake game")
|
||||
role_id = "UTUser1(Product Manager)"
|
||||
message = Message(role="User", content=idea, cause_by=UserRequirement)
|
||||
|
||||
|
|
@ -33,12 +35,12 @@ def test_idea_message():
|
|||
memory_storage.add(message)
|
||||
assert memory_storage.is_initialized is True
|
||||
|
||||
sim_idea = "Write a game of cli snake"
|
||||
sim_idea = text_embed_arr[1].get("text", "Write a game of cli snake")
|
||||
sim_message = Message(role="User", content=sim_idea, cause_by=UserRequirement)
|
||||
new_messages = memory_storage.search_dissimilar(sim_message)
|
||||
assert len(new_messages) == 0 # similar, return []
|
||||
|
||||
new_idea = "Write a 2048 web game"
|
||||
new_idea = text_embed_arr[2].get("text", "Write a 2048 web game")
|
||||
new_message = Message(role="User", content=new_idea, cause_by=UserRequirement)
|
||||
new_messages = memory_storage.search_dissimilar(new_message)
|
||||
assert new_messages[0].content == message.content
|
||||
|
|
@ -47,13 +49,17 @@ def test_idea_message():
|
|||
assert memory_storage.is_initialized is False
|
||||
|
||||
|
||||
def test_actionout_message():
|
||||
def test_actionout_message(mocker):
|
||||
mocker.patch("langchain_community.embeddings.openai.OpenAIEmbeddings.embed_documents", mock_openai_embed_documents)
|
||||
|
||||
out_mapping = {"field1": (str, ...), "field2": (List[str], ...)}
|
||||
out_data = {"field1": "field1 value", "field2": ["field2 value1", "field2 value2"]}
|
||||
ic_obj = ActionNode.create_model_class("prd", out_mapping)
|
||||
|
||||
role_id = "UTUser2(Architect)"
|
||||
content = "The user has requested the creation of a command-line interface (CLI) snake game"
|
||||
content = text_embed_arr[4].get(
|
||||
"text", "The user has requested the creation of a command-line interface (CLI) snake game"
|
||||
)
|
||||
message = Message(
|
||||
content=content, instruct_content=ic_obj(**out_data), role="user", cause_by=WritePRD
|
||||
) # WritePRD as test action
|
||||
|
|
@ -67,12 +73,14 @@ def test_actionout_message():
|
|||
memory_storage.add(message)
|
||||
assert memory_storage.is_initialized is True
|
||||
|
||||
sim_conent = "The request is command-line interface (CLI) snake game"
|
||||
sim_conent = text_embed_arr[5].get("text", "The request is command-line interface (CLI) snake game")
|
||||
sim_message = Message(content=sim_conent, instruct_content=ic_obj(**out_data), role="user", cause_by=WritePRD)
|
||||
new_messages = memory_storage.search_dissimilar(sim_message)
|
||||
assert len(new_messages) == 0 # similar, return []
|
||||
|
||||
new_conent = "Incorporate basic features of a snake game such as scoring and increasing difficulty"
|
||||
new_conent = text_embed_arr[6].get(
|
||||
"text", "Incorporate basic features of a snake game such as scoring and increasing difficulty"
|
||||
)
|
||||
new_message = Message(content=new_conent, instruct_content=ic_obj(**out_data), role="user", cause_by=WritePRD)
|
||||
new_messages = memory_storage.search_dissimilar(new_message)
|
||||
assert new_messages[0].content == message.content
|
||||
|
|
|
|||
|
|
@ -42,3 +42,21 @@ mock_llm_config_zhipu = LLMConfig(
|
|||
model="mock_zhipu_model",
|
||||
proxy="http://localhost:8080",
|
||||
)
|
||||
|
||||
|
||||
mock_llm_config_spark = LLMConfig(
|
||||
api_type="spark",
|
||||
app_id="xxx",
|
||||
api_key="xxx",
|
||||
api_secret="xxx",
|
||||
domain="generalv2",
|
||||
base_url="wss://spark-api.xf-yun.com/v3.1/chat",
|
||||
)
|
||||
|
||||
mock_llm_config_qianfan = LLMConfig(api_type="qianfan", access_key="xxx", secret_key="xxx", model="ERNIE-Bot-turbo")
|
||||
|
||||
mock_llm_config_dashscope = LLMConfig(api_type="dashscope", api_key="xxx", model="qwen-max")
|
||||
|
||||
mock_llm_config_anthropic = LLMConfig(
|
||||
api_type="anthropic", api_key="xxx", base_url="https://api.anthropic.com", model="claude-3-opus-20240229"
|
||||
)
|
||||
|
|
|
|||
185
tests/metagpt/provider/req_resp_const.py
Normal file
185
tests/metagpt/provider/req_resp_const.py
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc : default request & response data for provider unittest
|
||||
|
||||
|
||||
from anthropic.types import (
|
||||
ContentBlock,
|
||||
ContentBlockDeltaEvent,
|
||||
Message,
|
||||
MessageStartEvent,
|
||||
TextDelta,
|
||||
)
|
||||
from anthropic.types import Usage as AnthropicUsage
|
||||
from dashscope.api_entities.dashscope_response import (
|
||||
DashScopeAPIResponse,
|
||||
GenerationOutput,
|
||||
GenerationResponse,
|
||||
GenerationUsage,
|
||||
)
|
||||
from openai.types.chat.chat_completion import (
|
||||
ChatCompletion,
|
||||
ChatCompletionMessage,
|
||||
Choice,
|
||||
)
|
||||
from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
|
||||
from openai.types.chat.chat_completion_chunk import Choice as AChoice
|
||||
from openai.types.chat.chat_completion_chunk import ChoiceDelta
|
||||
from openai.types.completion_usage import CompletionUsage
|
||||
from qianfan.resources.typing import QfResponse
|
||||
|
||||
from metagpt.provider.base_llm import BaseLLM
|
||||
|
||||
prompt = "who are you?"
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
|
||||
resp_cont_tmpl = "I'm {name}"
|
||||
default_resp_cont = resp_cont_tmpl.format(name="GPT")
|
||||
|
||||
|
||||
# part of whole ChatCompletion of openai like structure
|
||||
def get_part_chat_completion(name: str) -> dict:
|
||||
part_chat_completion = {
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": resp_cont_tmpl.format(name=name),
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {"completion_tokens": 22, "prompt_tokens": 19, "total_tokens": 41},
|
||||
}
|
||||
return part_chat_completion
|
||||
|
||||
|
||||
def get_openai_chat_completion(name: str) -> ChatCompletion:
|
||||
openai_chat_completion = ChatCompletion(
|
||||
id="cmpl-a6652c1bb181caae8dd19ad8",
|
||||
model="xx/xxx",
|
||||
object="chat.completion",
|
||||
created=1703300855,
|
||||
choices=[
|
||||
Choice(
|
||||
finish_reason="stop",
|
||||
index=0,
|
||||
message=ChatCompletionMessage(role="assistant", content=resp_cont_tmpl.format(name=name)),
|
||||
logprobs=None,
|
||||
)
|
||||
],
|
||||
usage=CompletionUsage(completion_tokens=110, prompt_tokens=92, total_tokens=202),
|
||||
)
|
||||
return openai_chat_completion
|
||||
|
||||
|
||||
def get_openai_chat_completion_chunk(name: str, usage_as_dict: bool = False) -> ChatCompletionChunk:
|
||||
usage = CompletionUsage(completion_tokens=110, prompt_tokens=92, total_tokens=202)
|
||||
usage = usage if not usage_as_dict else usage.model_dump()
|
||||
openai_chat_completion_chunk = ChatCompletionChunk(
|
||||
id="cmpl-a6652c1bb181caae8dd19ad8",
|
||||
model="xx/xxx",
|
||||
object="chat.completion.chunk",
|
||||
created=1703300855,
|
||||
choices=[
|
||||
AChoice(
|
||||
delta=ChoiceDelta(role="assistant", content=resp_cont_tmpl.format(name=name)),
|
||||
finish_reason="stop",
|
||||
index=0,
|
||||
logprobs=None,
|
||||
)
|
||||
],
|
||||
usage=usage,
|
||||
)
|
||||
return openai_chat_completion_chunk
|
||||
|
||||
|
||||
# For gemini
|
||||
gemini_messages = [{"role": "user", "parts": prompt}]
|
||||
|
||||
|
||||
# For QianFan
|
||||
qf_jsonbody_dict = {
|
||||
"id": "as-4v1h587fyv",
|
||||
"object": "chat.completion",
|
||||
"created": 1695021339,
|
||||
"result": "",
|
||||
"is_truncated": False,
|
||||
"need_clear_history": False,
|
||||
"usage": {"prompt_tokens": 7, "completion_tokens": 15, "total_tokens": 22},
|
||||
}
|
||||
|
||||
|
||||
def get_qianfan_response(name: str) -> QfResponse:
|
||||
qf_jsonbody_dict["result"] = resp_cont_tmpl.format(name=name)
|
||||
return QfResponse(code=200, body=qf_jsonbody_dict)
|
||||
|
||||
|
||||
# For DashScope
|
||||
def get_dashscope_response(name: str) -> GenerationResponse:
|
||||
return GenerationResponse.from_api_response(
|
||||
DashScopeAPIResponse(
|
||||
status_code=200,
|
||||
output=GenerationOutput(
|
||||
**{
|
||||
"text": "",
|
||||
"finish_reason": "",
|
||||
"choices": [
|
||||
{
|
||||
"finish_reason": "stop",
|
||||
"message": {"role": "assistant", "content": resp_cont_tmpl.format(name=name)},
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
usage=GenerationUsage(**{"input_tokens": 12, "output_tokens": 98, "total_tokens": 110}),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# For Anthropic
|
||||
def get_anthropic_response(name: str, stream: bool = False) -> Message:
|
||||
if stream:
|
||||
return [
|
||||
MessageStartEvent(
|
||||
message=Message(
|
||||
id="xxx",
|
||||
model=name,
|
||||
role="assistant",
|
||||
type="message",
|
||||
content=[ContentBlock(text="", type="text")],
|
||||
usage=AnthropicUsage(input_tokens=10, output_tokens=10),
|
||||
),
|
||||
type="message_start",
|
||||
),
|
||||
ContentBlockDeltaEvent(
|
||||
index=0,
|
||||
delta=TextDelta(text=resp_cont_tmpl.format(name=name), type="text_delta"),
|
||||
type="content_block_delta",
|
||||
),
|
||||
]
|
||||
else:
|
||||
return Message(
|
||||
id="xxx",
|
||||
model=name,
|
||||
role="assistant",
|
||||
type="message",
|
||||
content=[ContentBlock(text=resp_cont_tmpl.format(name=name), type="text")],
|
||||
usage=AnthropicUsage(input_tokens=10, output_tokens=10),
|
||||
)
|
||||
|
||||
|
||||
# For llm general chat functions call
|
||||
async def llm_general_chat_funcs_test(llm: BaseLLM, prompt: str, messages: list[dict], resp_cont: str):
|
||||
resp = await llm.aask(prompt, stream=False)
|
||||
assert resp == resp_cont
|
||||
|
||||
resp = await llm.aask(prompt)
|
||||
assert resp == resp_cont
|
||||
|
||||
resp = await llm.acompletion_text(messages, stream=False)
|
||||
assert resp == resp_cont
|
||||
|
||||
resp = await llm.acompletion_text(messages, stream=True)
|
||||
assert resp == resp_cont
|
||||
|
|
@ -2,31 +2,45 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Desc : the unittest of Claude2
|
||||
|
||||
|
||||
import pytest
|
||||
from anthropic.resources.completions import Completion
|
||||
|
||||
from metagpt.provider.anthropic_api import Claude2
|
||||
from tests.metagpt.provider.mock_llm_config import mock_llm_config
|
||||
from metagpt.provider.anthropic_api import AnthropicLLM
|
||||
from tests.metagpt.provider.mock_llm_config import mock_llm_config_anthropic
|
||||
from tests.metagpt.provider.req_resp_const import (
|
||||
get_anthropic_response,
|
||||
llm_general_chat_funcs_test,
|
||||
messages,
|
||||
prompt,
|
||||
resp_cont_tmpl,
|
||||
)
|
||||
|
||||
prompt = "who are you"
|
||||
resp = "I'am Claude2"
|
||||
name = "claude-3-opus-20240229"
|
||||
resp_cont = resp_cont_tmpl.format(name=name)
|
||||
|
||||
|
||||
def mock_anthropic_completions_create(self, model: str, prompt: str, max_tokens_to_sample: int) -> Completion:
|
||||
return Completion(id="xx", completion=resp, model="claude-2", stop_reason="stop_sequence", type="completion")
|
||||
async def mock_anthropic_messages_create(
|
||||
self, messages: list[dict], model: str, stream: bool = True, max_tokens: int = None, system: str = None
|
||||
) -> Completion:
|
||||
if stream:
|
||||
|
||||
async def aresp_iterator():
|
||||
resps = get_anthropic_response(name, stream=True)
|
||||
for resp in resps:
|
||||
yield resp
|
||||
|
||||
async def mock_anthropic_acompletions_create(self, model: str, prompt: str, max_tokens_to_sample: int) -> Completion:
|
||||
return Completion(id="xx", completion=resp, model="claude-2", stop_reason="stop_sequence", type="completion")
|
||||
|
||||
|
||||
def test_claude2_ask(mocker):
|
||||
mocker.patch("anthropic.resources.completions.Completions.create", mock_anthropic_completions_create)
|
||||
assert resp == Claude2(mock_llm_config).ask(prompt)
|
||||
return aresp_iterator()
|
||||
else:
|
||||
return get_anthropic_response(name)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_claude2_aask(mocker):
|
||||
mocker.patch("anthropic.resources.completions.AsyncCompletions.create", mock_anthropic_acompletions_create)
|
||||
assert resp == await Claude2(mock_llm_config).aask(prompt)
|
||||
async def test_anthropic_acompletion(mocker):
|
||||
mocker.patch("anthropic.resources.messages.AsyncMessages.create", mock_anthropic_messages_create)
|
||||
|
||||
anthropic_llm = AnthropicLLM(mock_llm_config_anthropic)
|
||||
|
||||
resp = await anthropic_llm.acompletion(messages)
|
||||
assert resp.content[0].text == resp_cont
|
||||
|
||||
await llm_general_chat_funcs_test(anthropic_llm, prompt, messages, resp_cont)
|
||||
|
|
|
|||
|
|
@ -11,21 +11,13 @@ import pytest
|
|||
from metagpt.configs.llm_config import LLMConfig
|
||||
from metagpt.provider.base_llm import BaseLLM
|
||||
from metagpt.schema import Message
|
||||
from tests.metagpt.provider.req_resp_const import (
|
||||
default_resp_cont,
|
||||
get_part_chat_completion,
|
||||
prompt,
|
||||
)
|
||||
|
||||
default_chat_resp = {
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "I'am GPT",
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
]
|
||||
}
|
||||
prompt_msg = "who are you"
|
||||
resp_content = default_chat_resp["choices"][0]["message"]["content"]
|
||||
name = "GPT"
|
||||
|
||||
|
||||
class MockBaseLLM(BaseLLM):
|
||||
|
|
@ -33,16 +25,19 @@ class MockBaseLLM(BaseLLM):
|
|||
pass
|
||||
|
||||
def completion(self, messages: list[dict], timeout=3):
|
||||
return default_chat_resp
|
||||
return get_part_chat_completion(name)
|
||||
|
||||
async def _achat_completion(self, messages: list[dict], timeout=3):
|
||||
pass
|
||||
|
||||
async def acompletion(self, messages: list[dict], timeout=3):
|
||||
return default_chat_resp
|
||||
return get_part_chat_completion(name)
|
||||
|
||||
async def _achat_completion_stream(self, messages: list[dict], timeout: int = 3) -> str:
|
||||
pass
|
||||
|
||||
async def acompletion_text(self, messages: list[dict], stream=False, timeout=3) -> str:
|
||||
return resp_content
|
||||
|
||||
async def close(self):
|
||||
return default_chat_resp
|
||||
return default_resp_cont
|
||||
|
||||
|
||||
def test_base_llm():
|
||||
|
|
@ -86,25 +81,25 @@ def test_base_llm():
|
|||
choice_text = base_llm.get_choice_text(openai_funccall_resp)
|
||||
assert choice_text == openai_funccall_resp["choices"][0]["message"]["content"]
|
||||
|
||||
# resp = base_llm.ask(prompt_msg)
|
||||
# assert resp == resp_content
|
||||
# resp = base_llm.ask(prompt)
|
||||
# assert resp == default_resp_cont
|
||||
|
||||
# resp = base_llm.ask_batch([prompt_msg])
|
||||
# assert resp == resp_content
|
||||
# resp = base_llm.ask_batch([prompt])
|
||||
# assert resp == default_resp_cont
|
||||
|
||||
# resp = base_llm.ask_code([prompt_msg])
|
||||
# assert resp == resp_content
|
||||
# resp = base_llm.ask_code([prompt])
|
||||
# assert resp == default_resp_cont
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_base_llm():
|
||||
base_llm = MockBaseLLM()
|
||||
|
||||
resp = await base_llm.aask(prompt_msg)
|
||||
assert resp == resp_content
|
||||
resp = await base_llm.aask(prompt)
|
||||
assert resp == default_resp_cont
|
||||
|
||||
resp = await base_llm.aask_batch([prompt_msg])
|
||||
assert resp == resp_content
|
||||
resp = await base_llm.aask_batch([prompt])
|
||||
assert resp == default_resp_cont
|
||||
|
||||
# resp = await base_llm.aask_code([prompt_msg])
|
||||
# assert resp == resp_content
|
||||
# resp = await base_llm.aask_code([prompt])
|
||||
# assert resp == default_resp_cont
|
||||
|
|
|
|||
73
tests/metagpt/provider/test_dashscope_api.py
Normal file
73
tests/metagpt/provider/test_dashscope_api.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc : the unittest of DashScopeLLM
|
||||
|
||||
from typing import AsyncGenerator, Union
|
||||
|
||||
import pytest
|
||||
from dashscope.api_entities.dashscope_response import GenerationResponse
|
||||
|
||||
from metagpt.provider.dashscope_api import DashScopeLLM
|
||||
from tests.metagpt.provider.mock_llm_config import mock_llm_config_dashscope
|
||||
from tests.metagpt.provider.req_resp_const import (
|
||||
get_dashscope_response,
|
||||
llm_general_chat_funcs_test,
|
||||
messages,
|
||||
prompt,
|
||||
resp_cont_tmpl,
|
||||
)
|
||||
|
||||
name = "qwen-max"
|
||||
resp_cont = resp_cont_tmpl.format(name=name)
|
||||
|
||||
|
||||
@classmethod
|
||||
def mock_dashscope_call(
|
||||
cls,
|
||||
messages: list[dict],
|
||||
model: str,
|
||||
api_key: str,
|
||||
result_format: str,
|
||||
incremental_output: bool = True,
|
||||
stream: bool = False,
|
||||
) -> GenerationResponse:
|
||||
return get_dashscope_response(name)
|
||||
|
||||
|
||||
@classmethod
|
||||
async def mock_dashscope_acall(
|
||||
cls,
|
||||
messages: list[dict],
|
||||
model: str,
|
||||
api_key: str,
|
||||
result_format: str,
|
||||
incremental_output: bool = True,
|
||||
stream: bool = False,
|
||||
) -> Union[AsyncGenerator[GenerationResponse, None], GenerationResponse]:
|
||||
resps = [get_dashscope_response(name)]
|
||||
|
||||
if stream:
|
||||
|
||||
async def aresp_iterator(resps: list[GenerationResponse]):
|
||||
for resp in resps:
|
||||
yield resp
|
||||
|
||||
return aresp_iterator(resps)
|
||||
else:
|
||||
return resps[0]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dashscope_acompletion(mocker):
|
||||
mocker.patch("dashscope.aigc.generation.Generation.call", mock_dashscope_call)
|
||||
mocker.patch("metagpt.provider.dashscope_api.AGeneration.acall", mock_dashscope_acall)
|
||||
|
||||
dashscope_llm = DashScopeLLM(mock_llm_config_dashscope)
|
||||
|
||||
resp = dashscope_llm.completion(messages)
|
||||
assert resp.choices[0]["message"]["content"] == resp_cont
|
||||
|
||||
resp = await dashscope_llm.acompletion(messages)
|
||||
assert resp.choices[0]["message"]["content"] == resp_cont
|
||||
|
||||
await llm_general_chat_funcs_test(dashscope_llm, prompt, messages, resp_cont)
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc : the unittest of fireworks api
|
||||
|
||||
import pytest
|
||||
from openai.types.chat.chat_completion import (
|
||||
ChatCompletion,
|
||||
ChatCompletionMessage,
|
||||
Choice,
|
||||
)
|
||||
from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
|
||||
from openai.types.chat.chat_completion_chunk import Choice as AChoice
|
||||
from openai.types.chat.chat_completion_chunk import ChoiceDelta
|
||||
from openai.types.completion_usage import CompletionUsage
|
||||
|
||||
from metagpt.provider.fireworks_api import (
|
||||
MODEL_GRADE_TOKEN_COSTS,
|
||||
FireworksCostManager,
|
||||
FireworksLLM,
|
||||
)
|
||||
from metagpt.utils.cost_manager import Costs
|
||||
from tests.metagpt.provider.mock_llm_config import mock_llm_config
|
||||
|
||||
resp_content = "I'm fireworks"
|
||||
default_resp = ChatCompletion(
|
||||
id="cmpl-a6652c1bb181caae8dd19ad8",
|
||||
model="accounts/fireworks/models/llama-v2-13b-chat",
|
||||
object="chat.completion",
|
||||
created=1703300855,
|
||||
choices=[
|
||||
Choice(
|
||||
finish_reason="stop",
|
||||
index=0,
|
||||
message=ChatCompletionMessage(role="assistant", content=resp_content),
|
||||
logprobs=None,
|
||||
)
|
||||
],
|
||||
usage=CompletionUsage(completion_tokens=110, prompt_tokens=92, total_tokens=202),
|
||||
)
|
||||
|
||||
default_resp_chunk = ChatCompletionChunk(
|
||||
id=default_resp.id,
|
||||
model=default_resp.model,
|
||||
object="chat.completion.chunk",
|
||||
created=default_resp.created,
|
||||
choices=[
|
||||
AChoice(
|
||||
delta=ChoiceDelta(content=resp_content, role="assistant"),
|
||||
finish_reason="stop",
|
||||
index=0,
|
||||
logprobs=None,
|
||||
)
|
||||
],
|
||||
usage=dict(default_resp.usage),
|
||||
)
|
||||
|
||||
prompt_msg = "who are you"
|
||||
messages = [{"role": "user", "content": prompt_msg}]
|
||||
|
||||
|
||||
def test_fireworks_costmanager():
|
||||
cost_manager = FireworksCostManager()
|
||||
assert MODEL_GRADE_TOKEN_COSTS["-1"] == cost_manager.model_grade_token_costs("test")
|
||||
assert MODEL_GRADE_TOKEN_COSTS["-1"] == cost_manager.model_grade_token_costs("xxx-81b-chat")
|
||||
assert MODEL_GRADE_TOKEN_COSTS["16"] == cost_manager.model_grade_token_costs("llama-v2-13b-chat")
|
||||
assert MODEL_GRADE_TOKEN_COSTS["16"] == cost_manager.model_grade_token_costs("xxx-15.5b-chat")
|
||||
assert MODEL_GRADE_TOKEN_COSTS["16"] == cost_manager.model_grade_token_costs("xxx-16b-chat")
|
||||
assert MODEL_GRADE_TOKEN_COSTS["80"] == cost_manager.model_grade_token_costs("xxx-80b-chat")
|
||||
assert MODEL_GRADE_TOKEN_COSTS["mixtral-8x7b"] == cost_manager.model_grade_token_costs("mixtral-8x7b-chat")
|
||||
|
||||
cost_manager.update_cost(prompt_tokens=500000, completion_tokens=500000, model="llama-v2-13b-chat")
|
||||
assert cost_manager.total_cost == 0.5
|
||||
|
||||
|
||||
async def mock_openai_acompletions_create(self, stream: bool = False, **kwargs) -> ChatCompletionChunk:
|
||||
if stream:
|
||||
|
||||
class Iterator(object):
|
||||
async def __aiter__(self):
|
||||
yield default_resp_chunk
|
||||
|
||||
return Iterator()
|
||||
else:
|
||||
return default_resp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fireworks_acompletion(mocker):
|
||||
mocker.patch("openai.resources.chat.completions.AsyncCompletions.create", mock_openai_acompletions_create)
|
||||
|
||||
fireworks_gpt = FireworksLLM(mock_llm_config)
|
||||
fireworks_gpt.model = "llama-v2-13b-chat"
|
||||
|
||||
fireworks_gpt._update_costs(
|
||||
usage=CompletionUsage(prompt_tokens=500000, completion_tokens=500000, total_tokens=1000000)
|
||||
)
|
||||
assert fireworks_gpt.get_costs() == Costs(
|
||||
total_prompt_tokens=500000, total_completion_tokens=500000, total_cost=0.5, total_budget=0
|
||||
)
|
||||
|
||||
resp = await fireworks_gpt.acompletion(messages)
|
||||
assert resp.choices[0].message.content in resp_content
|
||||
|
||||
resp = await fireworks_gpt.aask(prompt_msg, stream=False)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await fireworks_gpt.acompletion_text(messages, stream=False)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await fireworks_gpt.acompletion_text(messages, stream=True)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await fireworks_gpt.aask(prompt_msg)
|
||||
assert resp == resp_content
|
||||
|
|
@ -11,6 +11,12 @@ from google.generativeai.types import content_types
|
|||
|
||||
from metagpt.provider.google_gemini_api import GeminiLLM
|
||||
from tests.metagpt.provider.mock_llm_config import mock_llm_config
|
||||
from tests.metagpt.provider.req_resp_const import (
|
||||
gemini_messages,
|
||||
llm_general_chat_funcs_test,
|
||||
prompt,
|
||||
resp_cont_tmpl,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -18,10 +24,8 @@ class MockGeminiResponse(ABC):
|
|||
text: str
|
||||
|
||||
|
||||
prompt_msg = "who are you"
|
||||
messages = [{"role": "user", "parts": prompt_msg}]
|
||||
resp_content = "I'm gemini from google"
|
||||
default_resp = MockGeminiResponse(text=resp_content)
|
||||
resp_cont = resp_cont_tmpl.format(name="gemini")
|
||||
default_resp = MockGeminiResponse(text=resp_cont)
|
||||
|
||||
|
||||
def mock_gemini_count_tokens(self, contents: content_types.ContentsType) -> glm.CountTokensResponse:
|
||||
|
|
@ -60,28 +64,18 @@ async def test_gemini_acompletion(mocker):
|
|||
mock_gemini_generate_content_async,
|
||||
)
|
||||
|
||||
gemini_gpt = GeminiLLM(mock_llm_config)
|
||||
gemini_llm = GeminiLLM(mock_llm_config)
|
||||
|
||||
assert gemini_gpt._user_msg(prompt_msg) == {"role": "user", "parts": [prompt_msg]}
|
||||
assert gemini_gpt._assistant_msg(prompt_msg) == {"role": "model", "parts": [prompt_msg]}
|
||||
assert gemini_llm._user_msg(prompt) == {"role": "user", "parts": [prompt]}
|
||||
assert gemini_llm._assistant_msg(prompt) == {"role": "model", "parts": [prompt]}
|
||||
|
||||
usage = gemini_gpt.get_usage(messages, resp_content)
|
||||
usage = gemini_llm.get_usage(gemini_messages, resp_cont)
|
||||
assert usage == {"prompt_tokens": 20, "completion_tokens": 20}
|
||||
|
||||
resp = gemini_gpt.completion(messages)
|
||||
resp = gemini_llm.completion(gemini_messages)
|
||||
assert resp == default_resp
|
||||
|
||||
resp = await gemini_gpt.acompletion(messages)
|
||||
resp = await gemini_llm.acompletion(gemini_messages)
|
||||
assert resp.text == default_resp.text
|
||||
|
||||
resp = await gemini_gpt.aask(prompt_msg, stream=False)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await gemini_gpt.acompletion_text(messages, stream=False)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await gemini_gpt.acompletion_text(messages, stream=True)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await gemini_gpt.aask(prompt_msg)
|
||||
assert resp == resp_content
|
||||
await llm_general_chat_funcs_test(gemini_llm, prompt, gemini_messages, resp_cont)
|
||||
|
|
|
|||
|
|
@ -9,12 +9,15 @@ import pytest
|
|||
|
||||
from metagpt.provider.ollama_api import OllamaLLM
|
||||
from tests.metagpt.provider.mock_llm_config import mock_llm_config
|
||||
from tests.metagpt.provider.req_resp_const import (
|
||||
llm_general_chat_funcs_test,
|
||||
messages,
|
||||
prompt,
|
||||
resp_cont_tmpl,
|
||||
)
|
||||
|
||||
prompt_msg = "who are you"
|
||||
messages = [{"role": "user", "content": prompt_msg}]
|
||||
|
||||
resp_content = "I'm ollama"
|
||||
default_resp = {"message": {"role": "assistant", "content": resp_content}}
|
||||
resp_cont = resp_cont_tmpl.format(name="ollama")
|
||||
default_resp = {"message": {"role": "assistant", "content": resp_cont}}
|
||||
|
||||
|
||||
async def mock_ollama_arequest(self, stream: bool = False, **kwargs) -> Tuple[Any, Any, bool]:
|
||||
|
|
@ -41,19 +44,12 @@ async def mock_ollama_arequest(self, stream: bool = False, **kwargs) -> Tuple[An
|
|||
async def test_gemini_acompletion(mocker):
|
||||
mocker.patch("metagpt.provider.general_api_requestor.GeneralAPIRequestor.arequest", mock_ollama_arequest)
|
||||
|
||||
ollama_gpt = OllamaLLM(mock_llm_config)
|
||||
ollama_llm = OllamaLLM(mock_llm_config)
|
||||
|
||||
resp = await ollama_gpt.acompletion(messages)
|
||||
resp = await ollama_llm.acompletion(messages)
|
||||
assert resp["message"]["content"] == default_resp["message"]["content"]
|
||||
|
||||
resp = await ollama_gpt.aask(prompt_msg, stream=False)
|
||||
assert resp == resp_content
|
||||
resp = await ollama_llm.aask(prompt, stream=False)
|
||||
assert resp == resp_cont
|
||||
|
||||
resp = await ollama_gpt.acompletion_text(messages, stream=False)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await ollama_gpt.acompletion_text(messages, stream=True)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await ollama_gpt.aask(prompt_msg)
|
||||
assert resp == resp_content
|
||||
await llm_general_chat_funcs_test(ollama_llm, prompt, messages, resp_cont)
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc :
|
||||
|
||||
import pytest
|
||||
from openai.types.chat.chat_completion import (
|
||||
ChatCompletion,
|
||||
ChatCompletionMessage,
|
||||
Choice,
|
||||
)
|
||||
from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
|
||||
from openai.types.chat.chat_completion_chunk import Choice as AChoice
|
||||
from openai.types.chat.chat_completion_chunk import ChoiceDelta
|
||||
from openai.types.completion_usage import CompletionUsage
|
||||
|
||||
from metagpt.provider.open_llm_api import OpenLLM
|
||||
from metagpt.utils.cost_manager import Costs
|
||||
from tests.metagpt.provider.mock_llm_config import mock_llm_config
|
||||
|
||||
resp_content = "I'm llama2"
|
||||
default_resp = ChatCompletion(
|
||||
id="cmpl-a6652c1bb181caae8dd19ad8",
|
||||
model="llama-v2-13b-chat",
|
||||
object="chat.completion",
|
||||
created=1703302755,
|
||||
choices=[
|
||||
Choice(
|
||||
finish_reason="stop",
|
||||
index=0,
|
||||
message=ChatCompletionMessage(role="assistant", content=resp_content),
|
||||
logprobs=None,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
default_resp_chunk = ChatCompletionChunk(
|
||||
id=default_resp.id,
|
||||
model=default_resp.model,
|
||||
object="chat.completion.chunk",
|
||||
created=default_resp.created,
|
||||
choices=[
|
||||
AChoice(
|
||||
delta=ChoiceDelta(content=resp_content, role="assistant"),
|
||||
finish_reason="stop",
|
||||
index=0,
|
||||
logprobs=None,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
prompt_msg = "who are you"
|
||||
messages = [{"role": "user", "content": prompt_msg}]
|
||||
|
||||
|
||||
async def mock_openai_acompletions_create(self, stream: bool = False, **kwargs) -> ChatCompletionChunk:
|
||||
if stream:
|
||||
|
||||
class Iterator(object):
|
||||
async def __aiter__(self):
|
||||
yield default_resp_chunk
|
||||
|
||||
return Iterator()
|
||||
else:
|
||||
return default_resp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_openllm_acompletion(mocker):
|
||||
mocker.patch("openai.resources.chat.completions.AsyncCompletions.create", mock_openai_acompletions_create)
|
||||
|
||||
openllm_gpt = OpenLLM(mock_llm_config)
|
||||
openllm_gpt.model = "llama-v2-13b-chat"
|
||||
|
||||
openllm_gpt._update_costs(usage=CompletionUsage(prompt_tokens=100, completion_tokens=100, total_tokens=200))
|
||||
assert openllm_gpt.get_costs() == Costs(
|
||||
total_prompt_tokens=100, total_completion_tokens=100, total_cost=0, total_budget=0
|
||||
)
|
||||
|
||||
resp = await openllm_gpt.acompletion(messages)
|
||||
assert resp.choices[0].message.content in resp_content
|
||||
|
||||
resp = await openllm_gpt.aask(prompt_msg, stream=False)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await openllm_gpt.acompletion_text(messages, stream=False)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await openllm_gpt.acompletion_text(messages, stream=True)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await openllm_gpt.aask(prompt_msg)
|
||||
assert resp == resp_content
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import pytest
|
||||
from openai.types.chat import (
|
||||
ChatCompletion,
|
||||
ChatCompletionChunk,
|
||||
ChatCompletionMessage,
|
||||
ChatCompletionMessageToolCall,
|
||||
)
|
||||
from openai.types.chat.chat_completion import Choice
|
||||
from openai.types.chat.chat_completion import Choice, CompletionUsage
|
||||
from openai.types.chat.chat_completion_message_tool_call import Function
|
||||
from PIL import Image
|
||||
|
||||
|
|
@ -16,6 +17,22 @@ from tests.metagpt.provider.mock_llm_config import (
|
|||
mock_llm_config,
|
||||
mock_llm_config_proxy,
|
||||
)
|
||||
from tests.metagpt.provider.req_resp_const import (
|
||||
get_openai_chat_completion,
|
||||
get_openai_chat_completion_chunk,
|
||||
llm_general_chat_funcs_test,
|
||||
messages,
|
||||
prompt,
|
||||
resp_cont_tmpl,
|
||||
)
|
||||
|
||||
name = "AI assistant"
|
||||
resp_cont = resp_cont_tmpl.format(name=name)
|
||||
default_resp = get_openai_chat_completion(name)
|
||||
|
||||
default_resp_chunk = get_openai_chat_completion_chunk(name, usage_as_dict=True)
|
||||
|
||||
usage = CompletionUsage(completion_tokens=110, prompt_tokens=92, total_tokens=202)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -121,3 +138,29 @@ async def test_gen_image():
|
|||
|
||||
images: list[Image] = await llm.gen_image(model=model, prompt=prompt, resp_format="b64_json")
|
||||
assert images[0].size == (1024, 1024)
|
||||
|
||||
|
||||
async def mock_openai_acompletions_create(self, stream: bool = False, **kwargs) -> ChatCompletionChunk:
|
||||
if stream:
|
||||
|
||||
class Iterator(object):
|
||||
async def __aiter__(self):
|
||||
yield default_resp_chunk
|
||||
|
||||
return Iterator()
|
||||
else:
|
||||
return default_resp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_openai_acompletion(mocker):
|
||||
mocker.patch("openai.resources.chat.completions.AsyncCompletions.create", mock_openai_acompletions_create)
|
||||
|
||||
llm = OpenAILLM(mock_llm_config)
|
||||
|
||||
resp = await llm.acompletion(messages)
|
||||
assert resp.choices[0].finish_reason == "stop"
|
||||
assert resp.choices[0].message.content == resp_cont
|
||||
assert resp.usage == usage
|
||||
|
||||
await llm_general_chat_funcs_test(llm, prompt, messages, resp_cont)
|
||||
|
|
|
|||
56
tests/metagpt/provider/test_qianfan_api.py
Normal file
56
tests/metagpt/provider/test_qianfan_api.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc : the unittest of qianfan api
|
||||
|
||||
from typing import AsyncIterator, Union
|
||||
|
||||
import pytest
|
||||
from qianfan.resources.typing import JsonBody, QfResponse
|
||||
|
||||
from metagpt.provider.qianfan_api import QianFanLLM
|
||||
from tests.metagpt.provider.mock_llm_config import mock_llm_config_qianfan
|
||||
from tests.metagpt.provider.req_resp_const import (
|
||||
get_qianfan_response,
|
||||
llm_general_chat_funcs_test,
|
||||
messages,
|
||||
prompt,
|
||||
resp_cont_tmpl,
|
||||
)
|
||||
|
||||
name = "ERNIE-Bot-turbo"
|
||||
resp_cont = resp_cont_tmpl.format(name=name)
|
||||
|
||||
|
||||
def mock_qianfan_do(self, messages: list[dict], model: str, stream: bool = False, system: str = None) -> QfResponse:
|
||||
return get_qianfan_response(name=name)
|
||||
|
||||
|
||||
async def mock_qianfan_ado(
|
||||
self, messages: list[dict], model: str, stream: bool = True, system: str = None
|
||||
) -> Union[QfResponse, AsyncIterator[QfResponse]]:
|
||||
resps = [get_qianfan_response(name=name)]
|
||||
if stream:
|
||||
|
||||
async def aresp_iterator(resps: list[JsonBody]):
|
||||
for resp in resps:
|
||||
yield resp
|
||||
|
||||
return aresp_iterator(resps)
|
||||
else:
|
||||
return resps[0]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_qianfan_acompletion(mocker):
|
||||
mocker.patch("qianfan.resources.llm.chat_completion.ChatCompletion.do", mock_qianfan_do)
|
||||
mocker.patch("qianfan.resources.llm.chat_completion.ChatCompletion.ado", mock_qianfan_ado)
|
||||
|
||||
qianfan_llm = QianFanLLM(mock_llm_config_qianfan)
|
||||
|
||||
resp = qianfan_llm.completion(messages)
|
||||
assert resp.get("result") == resp_cont
|
||||
|
||||
resp = await qianfan_llm.acompletion(messages)
|
||||
assert resp.get("result") == resp_cont
|
||||
|
||||
await llm_general_chat_funcs_test(qianfan_llm, prompt, messages, resp_cont)
|
||||
|
|
@ -4,12 +4,18 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from metagpt.config2 import Config
|
||||
from metagpt.provider.spark_api import GetMessageFromWeb, SparkLLM
|
||||
from tests.metagpt.provider.mock_llm_config import mock_llm_config
|
||||
from tests.metagpt.provider.mock_llm_config import (
|
||||
mock_llm_config,
|
||||
mock_llm_config_spark,
|
||||
)
|
||||
from tests.metagpt.provider.req_resp_const import (
|
||||
llm_general_chat_funcs_test,
|
||||
prompt,
|
||||
resp_cont_tmpl,
|
||||
)
|
||||
|
||||
prompt_msg = "who are you"
|
||||
resp_content = "I'm Spark"
|
||||
resp_cont = resp_cont_tmpl.format(name="Spark")
|
||||
|
||||
|
||||
class MockWebSocketApp(object):
|
||||
|
|
@ -23,7 +29,7 @@ class MockWebSocketApp(object):
|
|||
def test_get_msg_from_web(mocker):
|
||||
mocker.patch("websocket.WebSocketApp", MockWebSocketApp)
|
||||
|
||||
get_msg_from_web = GetMessageFromWeb(prompt_msg, mock_llm_config)
|
||||
get_msg_from_web = GetMessageFromWeb(prompt, mock_llm_config)
|
||||
assert get_msg_from_web.gen_params()["parameter"]["chat"]["domain"] == "mock_domain"
|
||||
|
||||
ret = get_msg_from_web.run()
|
||||
|
|
@ -31,34 +37,26 @@ def test_get_msg_from_web(mocker):
|
|||
|
||||
|
||||
def mock_spark_get_msg_from_web_run(self) -> str:
|
||||
return resp_content
|
||||
return resp_cont
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_spark_aask():
|
||||
llm = SparkLLM(Config.from_home("spark.yaml").llm)
|
||||
async def test_spark_aask(mocker):
|
||||
mocker.patch("metagpt.provider.spark_api.GetMessageFromWeb.run", mock_spark_get_msg_from_web_run)
|
||||
|
||||
llm = SparkLLM(mock_llm_config_spark)
|
||||
|
||||
resp = await llm.aask("Hello!")
|
||||
print(resp)
|
||||
assert resp == resp_cont
|
||||
|
||||
|
||||
@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(mock_llm_config)
|
||||
spark_llm = SparkLLM(mock_llm_config)
|
||||
|
||||
resp = await spark_gpt.acompletion([])
|
||||
assert resp == resp_content
|
||||
resp = await spark_llm.acompletion([])
|
||||
assert resp == resp_cont
|
||||
|
||||
resp = await spark_gpt.aask(prompt_msg, stream=False)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await spark_gpt.acompletion_text([], stream=False)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await spark_gpt.acompletion_text([], stream=True)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await spark_gpt.aask(prompt_msg)
|
||||
assert resp == resp_content
|
||||
await llm_general_chat_funcs_test(spark_llm, prompt, prompt, resp_cont)
|
||||
|
|
|
|||
|
|
@ -6,22 +6,24 @@ import pytest
|
|||
|
||||
from metagpt.provider.zhipuai_api import ZhiPuAILLM
|
||||
from tests.metagpt.provider.mock_llm_config import mock_llm_config_zhipu
|
||||
from tests.metagpt.provider.req_resp_const import (
|
||||
get_part_chat_completion,
|
||||
llm_general_chat_funcs_test,
|
||||
messages,
|
||||
prompt,
|
||||
resp_cont_tmpl,
|
||||
)
|
||||
|
||||
prompt_msg = "who are you"
|
||||
messages = [{"role": "user", "content": prompt_msg}]
|
||||
|
||||
resp_content = "I'm chatglm-turbo"
|
||||
default_resp = {
|
||||
"choices": [{"finish_reason": "stop", "index": 0, "message": {"content": resp_content, "role": "assistant"}}],
|
||||
"usage": {"completion_tokens": 22, "prompt_tokens": 19, "total_tokens": 41},
|
||||
}
|
||||
name = "ChatGLM-4"
|
||||
resp_cont = resp_cont_tmpl.format(name=name)
|
||||
default_resp = get_part_chat_completion(name)
|
||||
|
||||
|
||||
async def mock_zhipuai_acreate_stream(self, **kwargs):
|
||||
class MockResponse(object):
|
||||
async def _aread(self):
|
||||
class Iterator(object):
|
||||
events = [{"choices": [{"index": 0, "delta": {"content": resp_content, "role": "assistant"}}]}]
|
||||
events = [{"choices": [{"index": 0, "delta": {"content": resp_cont, "role": "assistant"}}]}]
|
||||
|
||||
async def __aiter__(self):
|
||||
for event in self.events:
|
||||
|
|
@ -46,22 +48,12 @@ async def test_zhipuai_acompletion(mocker):
|
|||
mocker.patch("metagpt.provider.zhipuai.zhipu_model_api.ZhiPuModelAPI.acreate", mock_zhipuai_acreate)
|
||||
mocker.patch("metagpt.provider.zhipuai.zhipu_model_api.ZhiPuModelAPI.acreate_stream", mock_zhipuai_acreate_stream)
|
||||
|
||||
zhipu_gpt = ZhiPuAILLM(mock_llm_config_zhipu)
|
||||
zhipu_llm = ZhiPuAILLM(mock_llm_config_zhipu)
|
||||
|
||||
resp = await zhipu_gpt.acompletion(messages)
|
||||
assert resp["choices"][0]["message"]["content"] == resp_content
|
||||
resp = await zhipu_llm.acompletion(messages)
|
||||
assert resp["choices"][0]["message"]["content"] == resp_cont
|
||||
|
||||
resp = await zhipu_gpt.aask(prompt_msg, stream=False)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await zhipu_gpt.acompletion_text(messages, stream=False)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await zhipu_gpt.acompletion_text(messages, stream=True)
|
||||
assert resp == resp_content
|
||||
|
||||
resp = await zhipu_gpt.aask(prompt_msg)
|
||||
assert resp == resp_content
|
||||
await llm_general_chat_funcs_test(zhipu_llm, prompt, messages, resp_cont)
|
||||
|
||||
|
||||
def test_zhipuai_proxy():
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.logs import logger
|
||||
from metagpt.roles.mi.interpreter import Interpreter
|
||||
from metagpt.roles.di.data_interpreter import DataInterpreter
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("auto_run", [(True), (False)])
|
||||
async def test_interpreter(mocker, auto_run):
|
||||
mocker.patch("metagpt.actions.mi.execute_nb_code.ExecuteNbCode.run", return_value=("a successful run", True))
|
||||
mocker.patch("metagpt.actions.di.execute_nb_code.ExecuteNbCode.run", return_value=("a successful run", True))
|
||||
mocker.patch("builtins.input", return_value="confirm")
|
||||
|
||||
requirement = "Run data analysis on sklearn Iris dataset, include a plot"
|
||||
tools = []
|
||||
|
||||
mi = Interpreter(auto_run=auto_run, use_tools=True, tools=tools)
|
||||
rsp = await mi.run(requirement)
|
||||
di = DataInterpreter(auto_run=auto_run, use_tools=True, tools=tools)
|
||||
rsp = await di.run(requirement)
|
||||
logger.info(rsp)
|
||||
assert len(rsp.content) > 0
|
||||
|
||||
finished_tasks = mi.planner.plan.get_finished_tasks()
|
||||
finished_tasks = di.planner.plan.get_finished_tasks()
|
||||
assert len(finished_tasks) > 0
|
||||
assert len(finished_tasks[0].code) > 0 # check one task to see if code is recorded
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.mi.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.actions.di.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.logs import logger
|
||||
from metagpt.roles.mi.ml_engineer import MLEngineer
|
||||
from metagpt.roles.di.ml_engineer import MLEngineer
|
||||
from metagpt.schema import Message, Plan, Task
|
||||
from metagpt.tools.tool_type import ToolType
|
||||
from tests.metagpt.actions.mi.test_debug_code import CODE, DebugContext, ErrorStr
|
||||
from tests.metagpt.actions.di.test_debug_code import CODE, DebugContext, ErrorStr
|
||||
|
||||
|
||||
def test_mle_init():
|
||||
|
|
@ -30,6 +30,17 @@ async def test_run(mocker, context):
|
|||
language: str
|
||||
agent_description: str
|
||||
cause_by: str
|
||||
agent_skills: list
|
||||
|
||||
agent_skills = [
|
||||
{"id": 1, "name": "text_to_speech", "type": "builtin", "config": {}, "enabled": True},
|
||||
{"id": 2, "name": "text_to_image", "type": "builtin", "config": {}, "enabled": True},
|
||||
{"id": 3, "name": "ai_call", "type": "builtin", "config": {}, "enabled": True},
|
||||
{"id": 3, "name": "data_analysis", "type": "builtin", "config": {}, "enabled": True},
|
||||
{"id": 5, "name": "crawler", "type": "builtin", "config": {"engine": "ddg"}, "enabled": True},
|
||||
{"id": 6, "name": "knowledge", "type": "builtin", "config": {}, "enabled": True},
|
||||
{"id": 6, "name": "web_search", "type": "builtin", "config": {}, "enabled": True},
|
||||
]
|
||||
|
||||
inputs = [
|
||||
{
|
||||
|
|
@ -48,6 +59,7 @@ async def test_run(mocker, context):
|
|||
"language": "English",
|
||||
"agent_description": "chatterbox",
|
||||
"cause_by": any_to_str(TalkAction),
|
||||
"agent_skills": [],
|
||||
},
|
||||
{
|
||||
"memory": {
|
||||
|
|
@ -65,24 +77,16 @@ async def test_run(mocker, context):
|
|||
"language": "English",
|
||||
"agent_description": "painter",
|
||||
"cause_by": any_to_str(SkillAction),
|
||||
"agent_skills": agent_skills,
|
||||
},
|
||||
]
|
||||
agent_skills = [
|
||||
{"id": 1, "name": "text_to_speech", "type": "builtin", "config": {}, "enabled": True},
|
||||
{"id": 2, "name": "text_to_image", "type": "builtin", "config": {}, "enabled": True},
|
||||
{"id": 3, "name": "ai_call", "type": "builtin", "config": {}, "enabled": True},
|
||||
{"id": 3, "name": "data_analysis", "type": "builtin", "config": {}, "enabled": True},
|
||||
{"id": 5, "name": "crawler", "type": "builtin", "config": {"engine": "ddg"}, "enabled": True},
|
||||
{"id": 6, "name": "knowledge", "type": "builtin", "config": {}, "enabled": True},
|
||||
{"id": 6, "name": "web_search", "type": "builtin", "config": {}, "enabled": True},
|
||||
]
|
||||
|
||||
for i in inputs:
|
||||
seed = Input(**i)
|
||||
role = Assistant(language="Chinese", context=context)
|
||||
role.context.kwargs.language = seed.language
|
||||
role.context.kwargs.agent_description = seed.agent_description
|
||||
role.context.kwargs.agent_skills = agent_skills
|
||||
role.context.kwargs.agent_skills = seed.agent_skills
|
||||
|
||||
role.memory = seed.memory # Restore historical conversation content.
|
||||
while True:
|
||||
|
|
|
|||
|
|
@ -142,6 +142,9 @@ def check_or_create_base_tag(project_path):
|
|||
# Initialize a Git repository
|
||||
subprocess.run(["git", "init"], check=True)
|
||||
|
||||
# Check if the .gitignore exists. If it doesn't exist, create .gitignore and add the comment
|
||||
subprocess.run(f"echo # Ignore these files or directories > {'.gitignore'}", shell=True)
|
||||
|
||||
# Check if the 'base' tag exists
|
||||
check_base_tag_cmd = ["git", "show-ref", "--verify", "--quiet", "refs/tags/base"]
|
||||
if subprocess.run(check_base_tag_cmd).returncode == 0:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
from pathlib import Path
|
||||
from pprint import pformat
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.const import METAGPT_ROOT
|
||||
from metagpt.logs import logger
|
||||
from metagpt.repo_parser import RepoParser
|
||||
from metagpt.repo_parser import DotClassAttribute, DotClassMethod, DotReturn, RepoParser
|
||||
|
||||
|
||||
def test_repo_parser():
|
||||
|
|
@ -23,3 +25,140 @@ def test_error():
|
|||
"""_parse_file should return empty list when file not existed"""
|
||||
rsp = RepoParser._parse_file(Path("test_not_existed_file.py"))
|
||||
assert rsp == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("v", "name", "type_", "default_", "compositions"),
|
||||
[
|
||||
("children : dict[str, 'ActionNode']", "children", "dict[str,ActionNode]", "", ["ActionNode"]),
|
||||
("context : str", "context", "str", "", []),
|
||||
("example", "example", "", "", []),
|
||||
("expected_type : Type", "expected_type", "Type", "", ["Type"]),
|
||||
("args : Optional[Dict]", "args", "Optional[Dict]", "", []),
|
||||
("rsp : Optional[Message] = Message.Default", "rsp", "Optional[Message]", "Message.Default", ["Message"]),
|
||||
(
|
||||
"browser : Literal['chrome', 'firefox', 'edge', 'ie']",
|
||||
"browser",
|
||||
"Literal['chrome','firefox','edge','ie']",
|
||||
"",
|
||||
[],
|
||||
),
|
||||
(
|
||||
"browser : Dict[ Message, Literal['chrome', 'firefox', 'edge', 'ie'] ]",
|
||||
"browser",
|
||||
"Dict[Message,Literal['chrome','firefox','edge','ie']]",
|
||||
"",
|
||||
["Message"],
|
||||
),
|
||||
("attributes : List[ClassAttribute]", "attributes", "List[ClassAttribute]", "", ["ClassAttribute"]),
|
||||
("attributes = []", "attributes", "", "[]", []),
|
||||
(
|
||||
"request_timeout: Optional[Union[float, Tuple[float, float]]]",
|
||||
"request_timeout",
|
||||
"Optional[Union[float,Tuple[float,float]]]",
|
||||
"",
|
||||
[],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_parse_member(v, name, type_, default_, compositions):
|
||||
attr = DotClassAttribute.parse(v)
|
||||
assert name == attr.name
|
||||
assert type_ == attr.type_
|
||||
assert default_ == attr.default_
|
||||
assert compositions == attr.compositions
|
||||
assert v == attr.description
|
||||
|
||||
json_data = attr.model_dump_json()
|
||||
v = DotClassAttribute.model_validate_json(json_data)
|
||||
assert v == attr
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("line", "package_name", "info"),
|
||||
[
|
||||
(
|
||||
'"metagpt.roles.architect.Architect" [color="black", fontcolor="black", label=<{Architect|constraints : str<br ALIGN="LEFT"/>goal : str<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>profile : str<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];',
|
||||
"metagpt.roles.architect.Architect",
|
||||
"Architect|constraints : str\ngoal : str\nname : str\nprofile : str\n|",
|
||||
),
|
||||
(
|
||||
'"metagpt.actions.skill_action.ArgumentsParingAction" [color="black", fontcolor="black", label=<{ArgumentsParingAction|args : Optional[Dict]<br ALIGN="LEFT"/>ask : str<br ALIGN="LEFT"/>prompt<br ALIGN="LEFT"/>rsp : Optional[Message]<br ALIGN="LEFT"/>skill<br ALIGN="LEFT"/>|parse_arguments(skill_name, txt): dict<br ALIGN="LEFT"/>run(with_message): Message<br ALIGN="LEFT"/>}>, shape="record", style="solid"];',
|
||||
"metagpt.actions.skill_action.ArgumentsParingAction",
|
||||
"ArgumentsParingAction|args : Optional[Dict]\nask : str\nprompt\nrsp : Optional[Message]\nskill\n|parse_arguments(skill_name, txt): dict\nrun(with_message): Message\n",
|
||||
),
|
||||
(
|
||||
'"metagpt.strategy.base.BaseEvaluator" [color="black", fontcolor="black", label=<{BaseEvaluator|<br ALIGN="LEFT"/>|<I>status_verify</I>()<br ALIGN="LEFT"/>}>, shape="record", style="solid"];',
|
||||
"metagpt.strategy.base.BaseEvaluator",
|
||||
"BaseEvaluator|\n|<I>status_verify</I>()\n",
|
||||
),
|
||||
(
|
||||
'"metagpt.configs.browser_config.BrowserConfig" [color="black", fontcolor="black", label=<{BrowserConfig|browser : Literal[\'chrome\', \'firefox\', \'edge\', \'ie\']<br ALIGN="LEFT"/>driver : Literal[\'chromium\', \'firefox\', \'webkit\']<br ALIGN="LEFT"/>engine<br ALIGN="LEFT"/>path : str<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];',
|
||||
"metagpt.configs.browser_config.BrowserConfig",
|
||||
"BrowserConfig|browser : Literal['chrome', 'firefox', 'edge', 'ie']\ndriver : Literal['chromium', 'firefox', 'webkit']\nengine\npath : str\n|",
|
||||
),
|
||||
(
|
||||
'"metagpt.tools.search_engine_serpapi.SerpAPIWrapper" [color="black", fontcolor="black", label=<{SerpAPIWrapper|aiosession : Optional[aiohttp.ClientSession]<br ALIGN="LEFT"/>model_config<br ALIGN="LEFT"/>params : dict<br ALIGN="LEFT"/>search_engine : Optional[Any]<br ALIGN="LEFT"/>serpapi_api_key : Optional[str]<br ALIGN="LEFT"/>|check_serpapi_api_key(val: str)<br ALIGN="LEFT"/>get_params(query: str): Dict[str, str]<br ALIGN="LEFT"/>results(query: str, max_results: int): dict<br ALIGN="LEFT"/>run(query, max_results: int, as_string: bool): str<br ALIGN="LEFT"/>}>, shape="record", style="solid"];',
|
||||
"metagpt.tools.search_engine_serpapi.SerpAPIWrapper",
|
||||
"SerpAPIWrapper|aiosession : Optional[aiohttp.ClientSession]\nmodel_config\nparams : dict\nsearch_engine : Optional[Any]\nserpapi_api_key : Optional[str]\n|check_serpapi_api_key(val: str)\nget_params(query: str): Dict[str, str]\nresults(query: str, max_results: int): dict\nrun(query, max_results: int, as_string: bool): str\n",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_split_class_line(line, package_name, info):
|
||||
p, i = RepoParser._split_class_line(line)
|
||||
assert p == package_name
|
||||
assert i == info
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("v", "name", "args", "return_args"),
|
||||
[
|
||||
(
|
||||
"<I>arequest</I>(method, url, params, headers, files, stream: Literal[True], request_id: Optional[str], request_timeout: Optional[Union[float, Tuple[float, float]]]): Tuple[AsyncGenerator[OpenAIResponse, None], bool, str]",
|
||||
"arequest",
|
||||
[
|
||||
DotClassAttribute(name="method", description="method"),
|
||||
DotClassAttribute(name="url", description="url"),
|
||||
DotClassAttribute(name="params", description="params"),
|
||||
DotClassAttribute(name="headers", description="headers"),
|
||||
DotClassAttribute(name="files", description="files"),
|
||||
DotClassAttribute(name="stream", type_="Literal[True]", description="stream: Literal[True]"),
|
||||
DotClassAttribute(name="request_id", type_="Optional[str]", description="request_id: Optional[str]"),
|
||||
DotClassAttribute(
|
||||
name="request_timeout",
|
||||
type_="Optional[Union[float,Tuple[float,float]]]",
|
||||
description="request_timeout: Optional[Union[float, Tuple[float, float]]]",
|
||||
),
|
||||
],
|
||||
DotReturn(
|
||||
type_="Tuple[AsyncGenerator[OpenAIResponse,None],bool,str]",
|
||||
compositions=["AsyncGenerator", "OpenAIResponse"],
|
||||
description="Tuple[AsyncGenerator[OpenAIResponse, None], bool, str]",
|
||||
),
|
||||
),
|
||||
(
|
||||
"<I>update</I>(subject: str, predicate: str, object_: str)",
|
||||
"update",
|
||||
[
|
||||
DotClassAttribute(name="subject", type_="str", description="subject: str"),
|
||||
DotClassAttribute(name="predicate", type_="str", description="predicate: str"),
|
||||
DotClassAttribute(name="object_", type_="str", description="object_: str"),
|
||||
],
|
||||
DotReturn(description=""),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_parse_method(v, name, args, return_args):
|
||||
method = DotClassMethod.parse(v)
|
||||
assert method.name == name
|
||||
assert method.args == args
|
||||
assert method.return_args == return_args
|
||||
assert method.description == v
|
||||
|
||||
json_data = method.model_dump_json()
|
||||
v = DotClassMethod.model_validate_json(json_data)
|
||||
assert v == method
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ from metagpt.actions.write_code import WriteCode
|
|||
from metagpt.const import SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO
|
||||
from metagpt.schema import (
|
||||
AIMessage,
|
||||
ClassAttribute,
|
||||
ClassMethod,
|
||||
ClassView,
|
||||
CodeSummarizeContext,
|
||||
Document,
|
||||
Message,
|
||||
|
|
@ -28,6 +25,9 @@ from metagpt.schema import (
|
|||
Plan,
|
||||
SystemMessage,
|
||||
Task,
|
||||
UMLClassAttribute,
|
||||
UMLClassMethod,
|
||||
UMLClassView,
|
||||
UserMessage,
|
||||
)
|
||||
from metagpt.utils.common import any_to_str
|
||||
|
|
@ -159,27 +159,26 @@ def test_CodeSummarizeContext(file_list, want):
|
|||
|
||||
|
||||
def test_class_view():
|
||||
attr_a = ClassAttribute(name="a", value_type="int", default_value="0", visibility="+", abstraction=True)
|
||||
assert attr_a.get_mermaid(align=1) == "\t+int a=0*"
|
||||
attr_b = ClassAttribute(name="b", value_type="str", default_value="0", visibility="#", static=True)
|
||||
assert attr_b.get_mermaid(align=0) == '#str b="0"$'
|
||||
class_view = ClassView(name="A")
|
||||
attr_a = UMLClassAttribute(name="a", value_type="int", default_value="0", visibility="+")
|
||||
assert attr_a.get_mermaid(align=1) == "\t+int a=0"
|
||||
attr_b = UMLClassAttribute(name="b", value_type="str", default_value="0", visibility="#")
|
||||
assert attr_b.get_mermaid(align=0) == '#str b="0"'
|
||||
class_view = UMLClassView(name="A")
|
||||
class_view.attributes = [attr_a, attr_b]
|
||||
|
||||
method_a = ClassMethod(name="run", visibility="+", abstraction=True)
|
||||
assert method_a.get_mermaid(align=1) == "\t+run()*"
|
||||
method_b = ClassMethod(
|
||||
method_a = UMLClassMethod(name="run", visibility="+")
|
||||
assert method_a.get_mermaid(align=1) == "\t+run()"
|
||||
method_b = UMLClassMethod(
|
||||
name="_test",
|
||||
visibility="#",
|
||||
static=True,
|
||||
args=[ClassAttribute(name="a", value_type="str"), ClassAttribute(name="b", value_type="int")],
|
||||
args=[UMLClassAttribute(name="a", value_type="str"), UMLClassAttribute(name="b", value_type="int")],
|
||||
return_type="str",
|
||||
)
|
||||
assert method_b.get_mermaid(align=0) == "#_test(str a,int b):str$"
|
||||
assert method_b.get_mermaid(align=0) == "#_test(str a,int b) str"
|
||||
class_view.methods = [method_a, method_b]
|
||||
assert (
|
||||
class_view.get_mermaid(align=0)
|
||||
== 'class A{\n\t+int a=0*\n\t#str b="0"$\n\t+run()*\n\t#_test(str a,int b):str$\n}\n'
|
||||
== 'class A{\n\t+int a=0\n\t#str b="0"\n\t+run()\n\t#_test(str a,int b) str\n}\n'
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ class TestGetProjectRoot:
|
|||
],
|
||||
)
|
||||
def test_split_namespace(self, val, want):
|
||||
res = split_namespace(val)
|
||||
res = split_namespace(val, maxsplit=-1)
|
||||
assert res == want
|
||||
|
||||
def test_read_json_file(self):
|
||||
|
|
|
|||
|
|
@ -211,6 +211,11 @@ value
|
|||
output = repair_invalid_json(output, "Expecting ',' delimiter: line 4 column 1")
|
||||
assert output == target_output
|
||||
|
||||
raw_output = '{"key": "url "http" \\"https\\" "}'
|
||||
target_output = '{"key": "url \\"http\\" \\"https\\" "}'
|
||||
output = repair_invalid_json(raw_output, "Expecting ',' delimiter: line 1 column 15 (char 14)")
|
||||
assert output == target_output
|
||||
|
||||
|
||||
def test_retry_parse_json_text():
|
||||
from metagpt.utils.repair_llm_raw_output import retry_parse_json_text
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import aiofiles
|
|||
import pytest
|
||||
|
||||
from metagpt.config2 import Config
|
||||
from metagpt.configs.s3_config import S3Config
|
||||
from metagpt.utils.common import aread
|
||||
from metagpt.utils.s3 import S3
|
||||
|
||||
|
|
@ -30,6 +31,14 @@ async def test_s3(mocker):
|
|||
mock_client.__aenter__.return_value = mock_client
|
||||
mock_client.__aexit__.return_value = None
|
||||
mocker.patch.object(aioboto3.Session, "client", return_value=mock_client)
|
||||
mock_config = mocker.Mock()
|
||||
mock_config.s3 = S3Config(
|
||||
access_key="mock_access_key",
|
||||
secret_key="mock_secret_key",
|
||||
endpoint="http://mock.endpoint",
|
||||
bucket="mock_bucket",
|
||||
)
|
||||
mocker.patch.object(Config, "default", return_value=mock_config)
|
||||
|
||||
# Prerequisites
|
||||
s3 = Config.default().s3
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import nbformat
|
||||
import pytest
|
||||
|
||||
from metagpt.actions.mi.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.actions.di.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.utils.common import read_json_file
|
||||
from metagpt.utils.save_code import DATA_PATH, save_code_file
|
||||
|
||||
|
|
|
|||
45
tests/metagpt/utils/test_visual_graph_repo.py
Normal file
45
tests/metagpt/utils/test_visual_graph_repo.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2024/1/4
|
||||
@Author : mashenquan
|
||||
@File : test_visual_graph_repo.py
|
||||
@Desc : Unit tests for testing and demonstrating the usage of VisualDiGraphRepo.
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.utils.common import remove_affix, split_namespace
|
||||
from metagpt.utils.visual_graph_repo import VisualDiGraphRepo
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_visual_di_graph_repo(context, mocker):
|
||||
filename = Path(__file__).parent / "../../data/graph_db/networkx.sequence_view.json"
|
||||
repo = await VisualDiGraphRepo.load_from(filename=filename)
|
||||
|
||||
class_view = await repo.get_mermaid_class_view()
|
||||
assert class_view
|
||||
await context.repo.resources.graph_repo.save(filename="class_view.md", content=f"```mermaid\n{class_view}\n```\n")
|
||||
|
||||
sequence_views = await repo.get_mermaid_sequence_views()
|
||||
assert sequence_views
|
||||
for ns, sqv in sequence_views:
|
||||
filename = re.sub(r"[:/\\\.]+", "_", ns) + ".sequence_view.md"
|
||||
sqv = sqv.strip(" `")
|
||||
await context.repo.resources.graph_repo.save(filename=filename, content=f"```mermaid\n{sqv}\n```\n")
|
||||
|
||||
sequence_view_vers = await repo.get_mermaid_sequence_view_versions()
|
||||
assert sequence_view_vers
|
||||
for ns, sqv in sequence_view_vers:
|
||||
ver, sqv = split_namespace(sqv)
|
||||
filename = re.sub(r"[:/\\\.]+", "_", ns) + f".{ver}.sequence_view_ver.md"
|
||||
sqv = remove_affix(sqv).strip(" `")
|
||||
await context.repo.resources.graph_repo.save(filename=filename, content=f"```mermaid\n{sqv}\n```\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
@ -3,7 +3,7 @@ from typing import Optional, Union
|
|||
|
||||
from metagpt.config2 import config
|
||||
from metagpt.configs.llm_config import LLMType
|
||||
from metagpt.logs import log_llm_stream, logger
|
||||
from metagpt.logs import logger
|
||||
from metagpt.provider.azure_openai_api import AzureOpenAILLM
|
||||
from metagpt.provider.openai_api import OpenAILLM
|
||||
from metagpt.schema import Message
|
||||
|
|
@ -24,17 +24,8 @@ class MockLLM(OriginalLLM):
|
|||
async def acompletion_text(self, messages: list[dict], stream=False, timeout=3) -> str:
|
||||
"""Overwrite original acompletion_text to cancel retry"""
|
||||
if stream:
|
||||
resp = self._achat_completion_stream(messages, timeout=timeout)
|
||||
|
||||
collected_messages = []
|
||||
async for i in resp:
|
||||
log_llm_stream(i)
|
||||
collected_messages.append(i)
|
||||
|
||||
full_reply_content = "".join(collected_messages)
|
||||
usage = self._calc_usage(messages, full_reply_content)
|
||||
self._update_costs(usage)
|
||||
return full_reply_content
|
||||
resp = await self._achat_completion_stream(messages, timeout=timeout)
|
||||
return resp
|
||||
|
||||
rsp = await self._achat_completion(messages, timeout=timeout)
|
||||
return self.get_choice_text(rsp)
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
llm:
|
||||
api_type: "spark"
|
||||
app_id: "xxx"
|
||||
api_key: "xxx"
|
||||
api_secret: "xxx"
|
||||
domain: "generalv2"
|
||||
base_url: "wss://spark-api.xf-yun.com/v3.1/chat"
|
||||
Loading…
Add table
Add a link
Reference in a new issue