mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-05-09 07:42:38 +02:00
Merge branch 'main' into incremental_development
# Conflicts: # metagpt/schema.py
This commit is contained in:
commit
e1b783ca14
127 changed files with 2346 additions and 648 deletions
|
|
@ -11,7 +11,7 @@ import json
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
from typing import Optional
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -19,49 +19,13 @@ from metagpt.config import CONFIG, Config
|
|||
from metagpt.const import DEFAULT_WORKSPACE_ROOT, TEST_DATA_PATH
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.logs import logger
|
||||
from metagpt.provider.openai_api import OpenAILLM
|
||||
from metagpt.utils.git_repository import GitRepository
|
||||
from tests.mock.mock_llm import MockLLM
|
||||
|
||||
|
||||
class MockLLM(OpenAILLM):
|
||||
rsp_cache: dict = {}
|
||||
|
||||
async def original_aask(
|
||||
self,
|
||||
msg: str,
|
||||
system_msgs: Optional[list[str]] = None,
|
||||
format_msgs: Optional[list[dict[str, str]]] = None,
|
||||
timeout=3,
|
||||
stream=True,
|
||||
):
|
||||
"""A copy of metagpt.provider.base_llm.BaseLLM.aask, we can't use super().aask because it will be mocked"""
|
||||
if system_msgs:
|
||||
message = self._system_msgs(system_msgs)
|
||||
else:
|
||||
message = [self._default_system_msg()] if self.use_system_prompt else []
|
||||
if format_msgs:
|
||||
message.extend(format_msgs)
|
||||
message.append(self._user_msg(msg))
|
||||
rsp = await self.acompletion_text(message, stream=stream, timeout=timeout)
|
||||
return rsp
|
||||
|
||||
async def aask(
|
||||
self,
|
||||
msg: str,
|
||||
system_msgs: Optional[list[str]] = None,
|
||||
format_msgs: Optional[list[dict[str, str]]] = None,
|
||||
timeout=3,
|
||||
stream=True,
|
||||
) -> str:
|
||||
if msg not in self.rsp_cache:
|
||||
# Call the original unmocked method
|
||||
rsp = await self.original_aask(msg, system_msgs, format_msgs, timeout, stream)
|
||||
logger.info(f"Added '{rsp[:20]}' ... to response cache")
|
||||
self.rsp_cache[msg] = rsp
|
||||
return rsp
|
||||
else:
|
||||
logger.info("Use response cache")
|
||||
return self.rsp_cache[msg]
|
||||
RSP_CACHE_NEW = {} # used globally for producing new and useful only response cache
|
||||
ALLOW_OPENAI_API_CALL = int(
|
||||
os.environ.get("ALLOW_OPENAI_API_CALL", 1)
|
||||
) # NOTE: should change to default 0 (False) once mock is complete
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
|
@ -75,16 +39,37 @@ def rsp_cache():
|
|||
else:
|
||||
rsp_cache_json = {}
|
||||
yield rsp_cache_json
|
||||
with open(new_rsp_cache_file_path, "w") as f2:
|
||||
with open(rsp_cache_file_path, "w") as f2:
|
||||
json.dump(rsp_cache_json, f2, indent=4, ensure_ascii=False)
|
||||
with open(new_rsp_cache_file_path, "w") as f2:
|
||||
json.dump(RSP_CACHE_NEW, f2, indent=4, ensure_ascii=False)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def llm_mock(rsp_cache, mocker):
|
||||
llm = MockLLM()
|
||||
# Hook to capture the test result
|
||||
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
||||
def pytest_runtest_makereport(item, call):
|
||||
outcome = yield
|
||||
rep = outcome.get_result()
|
||||
if rep.when == "call":
|
||||
item.test_outcome = rep
|
||||
|
||||
|
||||
@pytest.fixture(scope="function", autouse=True)
|
||||
def llm_mock(rsp_cache, mocker, request):
|
||||
llm = MockLLM(allow_open_api_call=ALLOW_OPENAI_API_CALL)
|
||||
llm.rsp_cache = rsp_cache
|
||||
mocker.patch("metagpt.provider.base_llm.BaseLLM.aask", llm.aask)
|
||||
mocker.patch("metagpt.provider.base_llm.BaseLLM.aask_batch", llm.aask_batch)
|
||||
yield mocker
|
||||
if hasattr(request.node, "test_outcome") and request.node.test_outcome.passed:
|
||||
if llm.rsp_candidates:
|
||||
for rsp_candidate in llm.rsp_candidates:
|
||||
cand_key = list(rsp_candidate.keys())[0]
|
||||
cand_value = list(rsp_candidate.values())[0]
|
||||
if cand_key not in llm.rsp_cache:
|
||||
logger.info(f"Added '{cand_key[:100]} ... -> {cand_value[:20]} ...' to response cache")
|
||||
llm.rsp_cache.update(rsp_candidate)
|
||||
RSP_CACHE_NEW.update(rsp_candidate)
|
||||
|
||||
|
||||
class Context:
|
||||
|
|
@ -111,7 +96,7 @@ def llm_api():
|
|||
logger.info("Tearing down the test")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@pytest.fixture
|
||||
def proxy():
|
||||
pattern = re.compile(
|
||||
rb"(?P<method>[a-zA-Z]+) (?P<uri>(\w+://)?(?P<host>[^\s\'\"<>\[\]{}|/:]+)(:(?P<port>\d+))?[^\s\'\"<>\[\]{}|]*) "
|
||||
|
|
@ -135,8 +120,11 @@ def proxy():
|
|||
remote_writer.write(data)
|
||||
await asyncio.gather(pipe(reader, remote_writer), pipe(remote_reader, writer))
|
||||
|
||||
server = asyncio.get_event_loop().run_until_complete(asyncio.start_server(handle_client, "127.0.0.1", 0))
|
||||
return "http://{}:{}".format(*server.sockets[0].getsockname())
|
||||
async def proxy_func():
|
||||
server = await asyncio.start_server(handle_client, "127.0.0.1", 0)
|
||||
return server, "http://{}:{}".format(*server.sockets[0].getsockname())
|
||||
|
||||
return proxy_func()
|
||||
|
||||
|
||||
# see https://github.com/Delgan/loguru/issues/59#issuecomment-466591978
|
||||
|
|
@ -151,9 +139,9 @@ def loguru_caplog(caplog):
|
|||
|
||||
|
||||
# init & dispose git repo
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
@pytest.fixture(scope="function", autouse=True)
|
||||
def setup_and_teardown_git_repo(request):
|
||||
CONFIG.git_repo = GitRepository(local_path=DEFAULT_WORKSPACE_ROOT / "unittest")
|
||||
CONFIG.git_repo = GitRepository(local_path=DEFAULT_WORKSPACE_ROOT / f"unittest/{uuid.uuid4().hex}")
|
||||
CONFIG.git_reinit = True
|
||||
|
||||
# Destroy git repo at the end of the test session.
|
||||
|
|
@ -167,3 +155,48 @@ def setup_and_teardown_git_repo(request):
|
|||
@pytest.fixture(scope="session", autouse=True)
|
||||
def init_config():
|
||||
Config()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def new_filename(mocker):
|
||||
# NOTE: Mock new filename to make reproducible llm aask, should consider changing after implementing requirement segmentation
|
||||
mocker.patch("metagpt.utils.file_repository.FileRepository.new_filename", lambda: "20240101")
|
||||
yield mocker
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def aiohttp_mocker(mocker):
|
||||
class MockAioResponse:
|
||||
async def json(self, *args, **kwargs):
|
||||
return self._json
|
||||
|
||||
def set_json(self, json):
|
||||
self._json = json
|
||||
|
||||
response = MockAioResponse()
|
||||
|
||||
class MockCTXMng:
|
||||
async def __aenter__(self):
|
||||
return response
|
||||
|
||||
async def __aexit__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def __await__(self):
|
||||
yield
|
||||
return response
|
||||
|
||||
def mock_request(self, method, url, **kwargs):
|
||||
return MockCTXMng()
|
||||
|
||||
def wrap(method):
|
||||
def run(self, url, **kwargs):
|
||||
return mock_request(self, method, url, **kwargs)
|
||||
|
||||
return run
|
||||
|
||||
mocker.patch("aiohttp.ClientSession.request", mock_request)
|
||||
for i in ["get", "post", "delete", "patch"]:
|
||||
mocker.patch(f"aiohttp.ClientSession.{i}", wrap(i))
|
||||
|
||||
yield response
|
||||
|
|
|
|||
1
tests/data/graph_db/networkx.json
Normal file
1
tests/data/graph_db/networkx.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
258
tests/data/search/serpapi-metagpt-4.json
Normal file
258
tests/data/search/serpapi-metagpt-4.json
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
{
|
||||
"search_metadata": {
|
||||
"id": "65952b400ead410fae1f548f",
|
||||
"status": "Success",
|
||||
"json_endpoint": "https://serpapi.com/searches/f3454e001dacdae1/65952b400ead410fae1f548f.json",
|
||||
"created_at": "2024-01-03 09:39:12 UTC",
|
||||
"processed_at": "2024-01-03 09:39:12 UTC",
|
||||
"google_url": "https://www.google.com/search?q=metagpt&oq=metagpt&hl=en&gl=us&num=8&sourceid=chrome&ie=UTF-8",
|
||||
"raw_html_file": "https://serpapi.com/searches/f3454e001dacdae1/65952b400ead410fae1f548f.html",
|
||||
"total_time_taken": 1.78
|
||||
},
|
||||
"search_parameters": {
|
||||
"engine": "google",
|
||||
"q": "metagpt",
|
||||
"google_domain": "google.com",
|
||||
"hl": "en",
|
||||
"gl": "us",
|
||||
"num": "8",
|
||||
"device": "desktop"
|
||||
},
|
||||
"search_information": {
|
||||
"query_displayed": "metagpt",
|
||||
"total_results": 110000,
|
||||
"time_taken_displayed": 0.3,
|
||||
"menu_items": [
|
||||
{
|
||||
"position": 1,
|
||||
"title": "News",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=metagpt&tbm=nws&source=lnms&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ0pQJegQIDRAB",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&tbm=nws"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"title": "Images",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=metagpt&tbm=isch&source=lnms&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ0pQJegQIDBAB",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google_images&gl=us&google_domain=google.com&hl=en&q=metagpt"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"title": "Perspectives",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=metagpt&uds=AMIYvT-LYN0C-KgfpAf4hDGmHUqYzPt2YD2Sjup6GzZxffnKpRHzrkDtH-YMw_l16Rw3319fYKZIWOgxIizOkCn4WaiWmK--Gd_KWgcdk2AGw9K3og-5w2Q&udm=4&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQs6gLegQICxAB",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"title": "Download",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=MetaGPT+download&uds=AMIYvT-5zq-IxPfUvCGLrNgPl7Seu8ODWYIoXhisgEvQZV3Y8pl5TzJLGfCHEIw7og1p8xJsV4GDoO9mlugZYdQpedp8elSjLy5ABJfq6NUCY0MAtXsFqu8&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQxKsJegQIChAB&ictx=0",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=MetaGPT+download"
|
||||
}
|
||||
],
|
||||
"organic_results_state": "Results for exact spelling"
|
||||
},
|
||||
"inline_videos": [
|
||||
{
|
||||
"position": 1,
|
||||
"title": "How To Install MetaGPT - Build A Startup With One Prompt!!",
|
||||
"link": "https://www.youtube.com/watch?v=uT75J_KG_aY",
|
||||
"thumbnail": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/a0db2f9f70f02dd11e3d3d4154df9fd65b46b2fbf4804f7038c9ce99c8efea1c.jpeg",
|
||||
"channel": "Matthew Berman",
|
||||
"duration": "6:36",
|
||||
"platform": "YouTube",
|
||||
"date": "Aug 14, 2023"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"title": "MetaGPT HUGE Update: Autonomous AI Agents with ...",
|
||||
"link": "https://www.youtube.com/watch?v=Xyws6iI-eH8",
|
||||
"thumbnail": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/a0db2f9f70f02dd1d578e6031265d66299cf6aecd327454cdf67b92808f3dd86.jpeg",
|
||||
"channel": "WorldofAI",
|
||||
"duration": "11:38",
|
||||
"platform": "YouTube",
|
||||
"date": "1 week ago"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"title": "\ud83d\ude80 MetaGPT Setup: Launch a Startup with One \u270d\ufe0f Prompt!",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao",
|
||||
"thumbnail": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/a0db2f9f70f02dd1c5666bd22292fdc357357dac89294aabb55ebea0a40ce322.jpeg",
|
||||
"channel": "Prompt Engineering",
|
||||
"duration": "14:15",
|
||||
"platform": "YouTube",
|
||||
"date": "Sep 4, 2023",
|
||||
"key_moments": [
|
||||
{
|
||||
"time": "00:00",
|
||||
"title": "Intro",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao&t=0",
|
||||
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQW-YKGXQDHplRpEDgL5Q-HlJ8HggTw_ghp_KWPh8xUcQ&s"
|
||||
},
|
||||
{
|
||||
"time": "00:12",
|
||||
"title": "What is MetaGPT",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao&t=12",
|
||||
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRJ4RRAXOG6yvGPYqkuj5cMoiyYdAN6g7E3VU04SA3P7w&s"
|
||||
},
|
||||
{
|
||||
"time": "01:06",
|
||||
"title": "Setup",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao&t=66",
|
||||
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTDlJBrAtfBkC8zI9wY4dOqVIaNFbjcYSZr4M1ZnD7RSw&s"
|
||||
},
|
||||
{
|
||||
"time": "05:23",
|
||||
"title": "Changing configuration",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao&t=323",
|
||||
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT8MbsIRVXJy__UE4ba0FoCTMGfrykasHm3UGvSzMQAtQ&s"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"organic_results": [
|
||||
{
|
||||
"position": 1,
|
||||
"title": "geekan/MetaGPT: \ud83c\udf1f The Multi-Agent Framework",
|
||||
"link": "https://github.com/geekan/MetaGPT",
|
||||
"redirect_link": "https://www.google.comhttps://github.com/geekan/MetaGPT",
|
||||
"displayed_link": "https://github.com \u203a geekan \u203a MetaGPT",
|
||||
"favicon": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/f37f87ccfb08b6fc2fe7e2076c022e7690f9b18357b8e5feb75a30ffbaaabfb1.png",
|
||||
"snippet": "MetaGPT takes a one line requirement as input and outputs user stories / competitive analysis / requirements / data structures / APIs / documents, etc.",
|
||||
"snippet_highlighted_words": [
|
||||
"MetaGPT"
|
||||
],
|
||||
"sitelinks": {
|
||||
"inline": [
|
||||
{
|
||||
"title": "Roadmap",
|
||||
"link": "https://github.com/geekan/MetaGPT/blob/main/docs/ROADMAP.md"
|
||||
},
|
||||
{
|
||||
"title": "README.md",
|
||||
"link": "https://github.com/geekan/MetaGPT/blob/main/README.md"
|
||||
},
|
||||
{
|
||||
"title": "Issues",
|
||||
"link": "https://github.com/geekan/MetaGPT/issues"
|
||||
},
|
||||
{
|
||||
"title": "Actions",
|
||||
"link": "https://github.com/geekan/MetaGPT/actions"
|
||||
}
|
||||
]
|
||||
},
|
||||
"source": "GitHub"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"title": "MetaGPT: Meta Programming for A Multi-Agent ...",
|
||||
"link": "https://arxiv.org/abs/2308.00352",
|
||||
"redirect_link": "https://www.google.comhttps://arxiv.org/abs/2308.00352",
|
||||
"displayed_link": "https://arxiv.org \u203a cs",
|
||||
"favicon": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/f37f87ccfb08b6fc2fe7e2076c022e76592372342f3f5dd76573e051b50f1bce.png",
|
||||
"author": "by S Hong",
|
||||
"cited_by": "Cited by 53",
|
||||
"extracted_cited_by": 53,
|
||||
"date": "2023",
|
||||
"snippet": "Abstract:Remarkable progress has been made on automated problem solving through societies of agents based on large language models (LLMs).",
|
||||
"source": "arXiv"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"title": "MetaGPT: a Multi-Agent Framework to Automate Your ...",
|
||||
"link": "https://medium.datadriveninvestor.com/metagpt-a-multi-agent-framework-to-automate-your-software-company-4b6ae747cc36",
|
||||
"redirect_link": "https://www.google.comhttps://medium.datadriveninvestor.com/metagpt-a-multi-agent-framework-to-automate-your-software-company-4b6ae747cc36",
|
||||
"displayed_link": "https://medium.datadriveninvestor.com \u203a metagpt-a-...",
|
||||
"favicon": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/f37f87ccfb08b6fc2fe7e2076c022e76e8319069677ee18a99026fb1e05709cf.png",
|
||||
"snippet": "MetaGPT is about to reach 10000 stars on Github. It's a Multi-Agent Framework that can behave as an engineer, product manager, architect, project managers.",
|
||||
"snippet_highlighted_words": [
|
||||
"MetaGPT"
|
||||
],
|
||||
"source": "DataDrivenInvestor"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"title": "MetaGPT: Complete Guide to the Best AI Agent Available ...",
|
||||
"link": "https://www.unite.ai/metagpt-complete-guide-to-the-best-ai-agent-available-right-now/",
|
||||
"redirect_link": "https://www.google.comhttps://www.unite.ai/metagpt-complete-guide-to-the-best-ai-agent-available-right-now/",
|
||||
"displayed_link": "https://www.unite.ai \u203a metagpt-complete-guide-to-the-...",
|
||||
"favicon": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/f37f87ccfb08b6fc2fe7e2076c022e76334a7b2eeab09f16973a82a209ee6339.png",
|
||||
"date": "Sep 11, 2023",
|
||||
"snippet": "Discover why MetaGPT outperforms AutoGPT, BabyAgi, and other AI agents in complex coding tasks. Our in-depth article guides you through the ...",
|
||||
"snippet_highlighted_words": [
|
||||
"MetaGPT"
|
||||
],
|
||||
"source": "Unite.AI"
|
||||
}
|
||||
],
|
||||
"related_searches": [
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "metagpt online",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=MetaGPT+online&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAglEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=MetaGPT+online"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "metagpt paper",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=MetaGPT+paper&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgoEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=MetaGPT+paper"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "Metagpt review",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=Metagpt+review&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgrEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=Metagpt+review"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "Metagpt download",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=Metagpt+download&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgpEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=Metagpt+download"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "metagpt ai",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=MetaGPT+AI&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgeEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=MetaGPT+AI"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "metagpt github",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=Metagpt+github&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgfEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=Metagpt+github"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "metagpt reddit",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=MetaGPT+Reddit&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgnEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=MetaGPT+Reddit"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "how to use metagpt",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=How+to+use+MetaGPT&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgqEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=How+to+use+MetaGPT"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current": 1,
|
||||
"next": "https://www.google.com/search?q=metagpt&oq=metagpt&hl=en&gl=us&num=8&start=8&sourceid=chrome&ie=UTF-8",
|
||||
"other_pages": {
|
||||
"2": "https://www.google.com/search?q=metagpt&oq=metagpt&hl=en&gl=us&num=8&start=8&sourceid=chrome&ie=UTF-8",
|
||||
"3": "https://www.google.com/search?q=metagpt&oq=metagpt&hl=en&gl=us&num=8&start=16&sourceid=chrome&ie=UTF-8",
|
||||
"4": "https://www.google.com/search?q=metagpt&oq=metagpt&hl=en&gl=us&num=8&start=24&sourceid=chrome&ie=UTF-8",
|
||||
"5": "https://www.google.com/search?q=metagpt&oq=metagpt&hl=en&gl=us&num=8&start=32&sourceid=chrome&ie=UTF-8"
|
||||
}
|
||||
},
|
||||
"serpapi_pagination": {
|
||||
"current": 1,
|
||||
"next_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&start=8",
|
||||
"next": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&start=8",
|
||||
"other_pages": {
|
||||
"2": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&start=8",
|
||||
"3": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&start=16",
|
||||
"4": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&start=24",
|
||||
"5": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&start=32"
|
||||
}
|
||||
}
|
||||
}
|
||||
350
tests/data/search/serpapi-metagpt-8.json
Normal file
350
tests/data/search/serpapi-metagpt-8.json
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
{
|
||||
"search_metadata": {
|
||||
"id": "65952b400ead410fae1f548f",
|
||||
"status": "Success",
|
||||
"json_endpoint": "https://serpapi.com/searches/f3454e001dacdae1/65952b400ead410fae1f548f.json",
|
||||
"created_at": "2024-01-03 09:39:12 UTC",
|
||||
"processed_at": "2024-01-03 09:39:12 UTC",
|
||||
"google_url": "https://www.google.com/search?q=metagpt&oq=metagpt&hl=en&gl=us&num=8&sourceid=chrome&ie=UTF-8",
|
||||
"raw_html_file": "https://serpapi.com/searches/f3454e001dacdae1/65952b400ead410fae1f548f.html",
|
||||
"total_time_taken": 1.78
|
||||
},
|
||||
"search_parameters": {
|
||||
"engine": "google",
|
||||
"q": "metagpt",
|
||||
"google_domain": "google.com",
|
||||
"hl": "en",
|
||||
"gl": "us",
|
||||
"num": "8",
|
||||
"device": "desktop"
|
||||
},
|
||||
"search_information": {
|
||||
"query_displayed": "metagpt",
|
||||
"total_results": 110000,
|
||||
"time_taken_displayed": 0.3,
|
||||
"menu_items": [
|
||||
{
|
||||
"position": 1,
|
||||
"title": "News",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=metagpt&tbm=nws&source=lnms&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ0pQJegQIDRAB",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&tbm=nws"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"title": "Images",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=metagpt&tbm=isch&source=lnms&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ0pQJegQIDBAB",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google_images&gl=us&google_domain=google.com&hl=en&q=metagpt"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"title": "Perspectives",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=metagpt&uds=AMIYvT-LYN0C-KgfpAf4hDGmHUqYzPt2YD2Sjup6GzZxffnKpRHzrkDtH-YMw_l16Rw3319fYKZIWOgxIizOkCn4WaiWmK--Gd_KWgcdk2AGw9K3og-5w2Q&udm=4&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQs6gLegQICxAB",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"title": "Download",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=MetaGPT+download&uds=AMIYvT-5zq-IxPfUvCGLrNgPl7Seu8ODWYIoXhisgEvQZV3Y8pl5TzJLGfCHEIw7og1p8xJsV4GDoO9mlugZYdQpedp8elSjLy5ABJfq6NUCY0MAtXsFqu8&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQxKsJegQIChAB&ictx=0",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=MetaGPT+download"
|
||||
},
|
||||
{
|
||||
"position": 5,
|
||||
"title": "Videos",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=metagpt&tbm=vid&source=lnms&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ0pQJegQINxAB",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google_videos&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt"
|
||||
},
|
||||
{
|
||||
"position": 6,
|
||||
"title": "Shopping",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=metagpt&tbm=shop&source=lnms",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google_shopping&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt"
|
||||
},
|
||||
{
|
||||
"position": 7,
|
||||
"title": "Review",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=MetaGPT+review&uds=AMIYvT9VP83904q4-J94lPXwCEnwL3j5QAtL1fmmW1S1R5RgwRLmxvuFVQ7OcN0dFbrjXQkUwlZlHOt9GNXyfomxI6gDvZxA6gokeHbKUq_anMgIkmFv3IY&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQxKsJegQIOhAB&ictx=0",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=MetaGPT+review"
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"title": "Online",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=MetaGPT+online&uds=AMIYvT8Ap1YYLsvgKVUJMi_v4l0FNZz9UYjvpQyVx07CgVk-hay-mNemgcUIz5ipc8mmv44wplpB3umGIvKSQMEgsHCY8aTWe6FLDtUjGT9hv-pihBT6dYw&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQxKsJegQIOxAB&ictx=0",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=MetaGPT+online"
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"title": "App",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=Metagpt+app&uds=AMIYvT_YL6Iqd-0G_f_v9e2v-JybHFZesGv-WkSjqZQUhGvjb7qTf3NoIkE_8qY5quBbzv_GSlurBfqWahyxbnyVMX5mlfpqn-U3E-KHZ3PAJcM8mO6MflU&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQxKsJegQIORAB&ictx=0",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=Metagpt+app"
|
||||
}
|
||||
],
|
||||
"organic_results_state": "Results for exact spelling"
|
||||
},
|
||||
"inline_videos": [
|
||||
{
|
||||
"position": 1,
|
||||
"title": "How To Install MetaGPT - Build A Startup With One Prompt!!",
|
||||
"link": "https://www.youtube.com/watch?v=uT75J_KG_aY",
|
||||
"thumbnail": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/a0db2f9f70f02dd11e3d3d4154df9fd65b46b2fbf4804f7038c9ce99c8efea1c.jpeg",
|
||||
"channel": "Matthew Berman",
|
||||
"duration": "6:36",
|
||||
"platform": "YouTube",
|
||||
"date": "Aug 14, 2023"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"title": "MetaGPT HUGE Update: Autonomous AI Agents with ...",
|
||||
"link": "https://www.youtube.com/watch?v=Xyws6iI-eH8",
|
||||
"thumbnail": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/a0db2f9f70f02dd1d578e6031265d66299cf6aecd327454cdf67b92808f3dd86.jpeg",
|
||||
"channel": "WorldofAI",
|
||||
"duration": "11:38",
|
||||
"platform": "YouTube",
|
||||
"date": "1 week ago"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"title": "\ud83d\ude80 MetaGPT Setup: Launch a Startup with One \u270d\ufe0f Prompt!",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao",
|
||||
"thumbnail": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/a0db2f9f70f02dd1c5666bd22292fdc357357dac89294aabb55ebea0a40ce322.jpeg",
|
||||
"channel": "Prompt Engineering",
|
||||
"duration": "14:15",
|
||||
"platform": "YouTube",
|
||||
"date": "Sep 4, 2023",
|
||||
"key_moments": [
|
||||
{
|
||||
"time": "00:00",
|
||||
"title": "Intro",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao&t=0",
|
||||
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQW-YKGXQDHplRpEDgL5Q-HlJ8HggTw_ghp_KWPh8xUcQ&s"
|
||||
},
|
||||
{
|
||||
"time": "00:12",
|
||||
"title": "What is MetaGPT",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao&t=12",
|
||||
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRJ4RRAXOG6yvGPYqkuj5cMoiyYdAN6g7E3VU04SA3P7w&s"
|
||||
},
|
||||
{
|
||||
"time": "01:06",
|
||||
"title": "Setup",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao&t=66",
|
||||
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTDlJBrAtfBkC8zI9wY4dOqVIaNFbjcYSZr4M1ZnD7RSw&s"
|
||||
},
|
||||
{
|
||||
"time": "05:23",
|
||||
"title": "Changing configuration",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao&t=323",
|
||||
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT8MbsIRVXJy__UE4ba0FoCTMGfrykasHm3UGvSzMQAtQ&s"
|
||||
},
|
||||
{
|
||||
"time": "06:35",
|
||||
"title": "How to Run",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao&t=395",
|
||||
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRuX6mOUVQVRzvnkOPYNcDpcazRC1QGeHhZh-Az9btUNA&s"
|
||||
},
|
||||
{
|
||||
"time": "09:02",
|
||||
"title": "What outputs to expect",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao&t=542",
|
||||
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTFnNqvPfGrPnKJTJ1iOHGSNp6sVR5jn0Zy5N2JSGfeEQ&s"
|
||||
},
|
||||
{
|
||||
"time": "10:45",
|
||||
"title": "Generated Design Documents",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao&t=645",
|
||||
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSN3I0gxudI4Mew93w_tw34HmWREz5XX8ArebReM3Y2_g&s"
|
||||
},
|
||||
{
|
||||
"time": "12:25",
|
||||
"title": "Run the created code base",
|
||||
"link": "https://www.youtube.com/watch?v=nqZlTV_L6Ao&t=745",
|
||||
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQLBx5bgKZ2Gqsu-PsIXuvtM0SBmHvBCndmKtresgqFCg&s"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"organic_results": [
|
||||
{
|
||||
"position": 1,
|
||||
"title": "geekan/MetaGPT: \ud83c\udf1f The Multi-Agent Framework",
|
||||
"link": "https://github.com/geekan/MetaGPT",
|
||||
"redirect_link": "https://www.google.comhttps://github.com/geekan/MetaGPT",
|
||||
"displayed_link": "https://github.com \u203a geekan \u203a MetaGPT",
|
||||
"favicon": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/f37f87ccfb08b6fc2fe7e2076c022e7690f9b18357b8e5feb75a30ffbaaabfb1.png",
|
||||
"snippet": "MetaGPT takes a one line requirement as input and outputs user stories / competitive analysis / requirements / data structures / APIs / documents, etc.",
|
||||
"snippet_highlighted_words": [
|
||||
"MetaGPT"
|
||||
],
|
||||
"sitelinks": {
|
||||
"inline": [
|
||||
{
|
||||
"title": "Roadmap",
|
||||
"link": "https://github.com/geekan/MetaGPT/blob/main/docs/ROADMAP.md"
|
||||
},
|
||||
{
|
||||
"title": "README.md",
|
||||
"link": "https://github.com/geekan/MetaGPT/blob/main/README.md"
|
||||
},
|
||||
{
|
||||
"title": "Issues",
|
||||
"link": "https://github.com/geekan/MetaGPT/issues"
|
||||
},
|
||||
{
|
||||
"title": "Actions",
|
||||
"link": "https://github.com/geekan/MetaGPT/actions"
|
||||
}
|
||||
]
|
||||
},
|
||||
"source": "GitHub"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"title": "MetaGPT: Meta Programming for A Multi-Agent ...",
|
||||
"link": "https://arxiv.org/abs/2308.00352",
|
||||
"redirect_link": "https://www.google.comhttps://arxiv.org/abs/2308.00352",
|
||||
"displayed_link": "https://arxiv.org \u203a cs",
|
||||
"favicon": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/f37f87ccfb08b6fc2fe7e2076c022e76592372342f3f5dd76573e051b50f1bce.png",
|
||||
"author": "by S Hong",
|
||||
"cited_by": "Cited by 53",
|
||||
"extracted_cited_by": 53,
|
||||
"date": "2023",
|
||||
"snippet": "Abstract:Remarkable progress has been made on automated problem solving through societies of agents based on large language models (LLMs).",
|
||||
"source": "arXiv"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"title": "MetaGPT: a Multi-Agent Framework to Automate Your ...",
|
||||
"link": "https://medium.datadriveninvestor.com/metagpt-a-multi-agent-framework-to-automate-your-software-company-4b6ae747cc36",
|
||||
"redirect_link": "https://www.google.comhttps://medium.datadriveninvestor.com/metagpt-a-multi-agent-framework-to-automate-your-software-company-4b6ae747cc36",
|
||||
"displayed_link": "https://medium.datadriveninvestor.com \u203a metagpt-a-...",
|
||||
"favicon": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/f37f87ccfb08b6fc2fe7e2076c022e76e8319069677ee18a99026fb1e05709cf.png",
|
||||
"snippet": "MetaGPT is about to reach 10000 stars on Github. It's a Multi-Agent Framework that can behave as an engineer, product manager, architect, project managers.",
|
||||
"snippet_highlighted_words": [
|
||||
"MetaGPT"
|
||||
],
|
||||
"source": "DataDrivenInvestor"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"title": "MetaGPT: Complete Guide to the Best AI Agent Available ...",
|
||||
"link": "https://www.unite.ai/metagpt-complete-guide-to-the-best-ai-agent-available-right-now/",
|
||||
"redirect_link": "https://www.google.comhttps://www.unite.ai/metagpt-complete-guide-to-the-best-ai-agent-available-right-now/",
|
||||
"displayed_link": "https://www.unite.ai \u203a metagpt-complete-guide-to-the-...",
|
||||
"favicon": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/f37f87ccfb08b6fc2fe7e2076c022e76334a7b2eeab09f16973a82a209ee6339.png",
|
||||
"date": "Sep 11, 2023",
|
||||
"snippet": "Discover why MetaGPT outperforms AutoGPT, BabyAgi, and other AI agents in complex coding tasks. Our in-depth article guides you through the ...",
|
||||
"snippet_highlighted_words": [
|
||||
"MetaGPT"
|
||||
],
|
||||
"source": "Unite.AI"
|
||||
},
|
||||
{
|
||||
"position": 5,
|
||||
"title": "MetaGPT AI technology page - Lablab.ai",
|
||||
"link": "https://lablab.ai/tech/metagpt",
|
||||
"redirect_link": "https://www.google.comhttps://lablab.ai/tech/metagpt",
|
||||
"displayed_link": "https://lablab.ai \u203a tech \u203a metagpt",
|
||||
"favicon": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/f37f87ccfb08b6fc2fe7e2076c022e766a141f2bf05b1ab902f83ed00f4148a4.png",
|
||||
"snippet": "MetaGPT: Collaborative AI for Complex Tasks. MetaGPT is a groundbreaking AI technology, designed to transform the landscape of software development.",
|
||||
"snippet_highlighted_words": [
|
||||
"MetaGPT",
|
||||
"MetaGPT"
|
||||
],
|
||||
"source": "lablab.ai"
|
||||
},
|
||||
{
|
||||
"position": 6,
|
||||
"title": "MetaGPT | Discover AI use cases",
|
||||
"link": "https://gpt3demo.com/apps/metagpt",
|
||||
"redirect_link": "https://www.google.comhttps://gpt3demo.com/apps/metagpt",
|
||||
"displayed_link": "https://gpt3demo.com \u203a apps \u203a metagpt",
|
||||
"favicon": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/f37f87ccfb08b6fc2fe7e2076c022e76142721493557b5d95328dafb62b6b43a.jpeg",
|
||||
"snippet": "Assign different roles to GPTs to form a collaborative software entity for complex tasks. MetaGPT takes a one-line requirement as input and outputs user ...",
|
||||
"snippet_highlighted_words": [
|
||||
"MetaGPT"
|
||||
],
|
||||
"source": "GPT-3 Demo"
|
||||
},
|
||||
{
|
||||
"position": 7,
|
||||
"title": "Meet MetaGPT: The ChatGPT-Powered AI Assistant That ...",
|
||||
"link": "https://www.kdnuggets.com/meet-metagpt-the-chatgptpowered-ai-assistant-that-turns-text-into-web-apps",
|
||||
"redirect_link": "https://www.google.comhttps://www.kdnuggets.com/meet-metagpt-the-chatgptpowered-ai-assistant-that-turns-text-into-web-apps",
|
||||
"displayed_link": "https://www.kdnuggets.com \u203a meet-metagpt-the-chatg...",
|
||||
"favicon": "https://serpapi.com/searches/65952b400ead410fae1f548f/images/f37f87ccfb08b6fc2fe7e2076c022e767b0d4a705b7ad21b521b16648b390fe8.png",
|
||||
"date": "Sep 8, 2023",
|
||||
"snippet": "This revolutionary AI tool lets you create no-code web applications in just seconds!",
|
||||
"source": "KDnuggets"
|
||||
}
|
||||
],
|
||||
"related_searches": [
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "metagpt online",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=MetaGPT+online&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAglEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=MetaGPT+online"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "metagpt paper",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=MetaGPT+paper&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgoEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=MetaGPT+paper"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "Metagpt review",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=Metagpt+review&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgrEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=Metagpt+review"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "Metagpt download",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=Metagpt+download&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgpEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=Metagpt+download"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "metagpt ai",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=MetaGPT+AI&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgeEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=MetaGPT+AI"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "metagpt github",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=Metagpt+github&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgfEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=Metagpt+github"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "metagpt reddit",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=MetaGPT+Reddit&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgnEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=MetaGPT+Reddit"
|
||||
},
|
||||
{
|
||||
"block_position": 1,
|
||||
"query": "how to use metagpt",
|
||||
"link": "https://www.google.com/search?num=8&sca_esv=4bcb71572bca9257&sca_upv=1&hl=en&gl=us&q=How+to+use+MetaGPT&sa=X&ved=2ahUKEwjh-qqa9sCDAxV4fTABHZ8gClUQ1QJ6BAgqEAE",
|
||||
"serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=How+to+use+MetaGPT"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current": 1,
|
||||
"next": "https://www.google.com/search?q=metagpt&oq=metagpt&hl=en&gl=us&num=8&start=8&sourceid=chrome&ie=UTF-8",
|
||||
"other_pages": {
|
||||
"2": "https://www.google.com/search?q=metagpt&oq=metagpt&hl=en&gl=us&num=8&start=8&sourceid=chrome&ie=UTF-8",
|
||||
"3": "https://www.google.com/search?q=metagpt&oq=metagpt&hl=en&gl=us&num=8&start=16&sourceid=chrome&ie=UTF-8",
|
||||
"4": "https://www.google.com/search?q=metagpt&oq=metagpt&hl=en&gl=us&num=8&start=24&sourceid=chrome&ie=UTF-8",
|
||||
"5": "https://www.google.com/search?q=metagpt&oq=metagpt&hl=en&gl=us&num=8&start=32&sourceid=chrome&ie=UTF-8"
|
||||
}
|
||||
},
|
||||
"serpapi_pagination": {
|
||||
"current": 1,
|
||||
"next_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&start=8",
|
||||
"next": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&start=8",
|
||||
"other_pages": {
|
||||
"2": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&start=8",
|
||||
"3": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&start=16",
|
||||
"4": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&start=24",
|
||||
"5": "https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&num=8&q=metagpt&start=32"
|
||||
}
|
||||
}
|
||||
}
|
||||
102
tests/data/search/serper-metagpt-6.json
Normal file
102
tests/data/search/serper-metagpt-6.json
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
[
|
||||
{
|
||||
"searchParameters": {
|
||||
"q": "metagpt",
|
||||
"num": 8,
|
||||
"page": 1,
|
||||
"type": "search",
|
||||
"engine": "google"
|
||||
},
|
||||
"organic": [
|
||||
{
|
||||
"title": "geekan/MetaGPT: The Multi-Agent Framework: Given one line Requirement, return PRD, Design, Tasks, Repo - GitHub",
|
||||
"link": "https://github.com/geekan/MetaGPT",
|
||||
"snippet": "MetaGPT takes a one line requirement as input and outputs user stories / competitive analysis / requirements / data structures / APIs / documents, etc.",
|
||||
"sitelinks": [
|
||||
{
|
||||
"title": "README.md",
|
||||
"link": "https://github.com/geekan/MetaGPT/blob/main/README.md"
|
||||
},
|
||||
{
|
||||
"title": "Roadmap",
|
||||
"link": "https://github.com/geekan/MetaGPT/blob/main/docs/ROADMAP.md"
|
||||
},
|
||||
{
|
||||
"title": "Issues",
|
||||
"link": "https://github.com/geekan/MetaGPT/issues"
|
||||
},
|
||||
{
|
||||
"title": "Actions",
|
||||
"link": "https://github.com/geekan/MetaGPT/actions"
|
||||
}
|
||||
],
|
||||
"position": 1
|
||||
},
|
||||
{
|
||||
"title": "MetaGPT: Meta Programming for A Multi-Agent Collaborative Framework - arXiv",
|
||||
"link": "https://arxiv.org/abs/2308.00352",
|
||||
"snippet": "Abstract:Remarkable progress has been made on automated problem solving through societies of agents based on large language models (LLMs).",
|
||||
"date": "Aug 1, 2023",
|
||||
"position": 2
|
||||
},
|
||||
{
|
||||
"title": "How To Install MetaGPT - Build A Startup With One Prompt!! - YouTube",
|
||||
"link": "https://youtube.com/watch?v=uT75J_KG_aY",
|
||||
"snippet": "In this video, we review MetaGPT, a new project that aims ...",
|
||||
"date": "Aug 14, 2023",
|
||||
"attributes": {
|
||||
"Duration": "6:36",
|
||||
"Posted": "Aug 14, 2023"
|
||||
},
|
||||
"imageUrl": "https://i.ytimg.com/vi/uT75J_KG_aY/default.jpg?sqp=-oaymwEECHgQQw&rs=AMzJL3lfWRsXgckPQztWhHaRKYqxffksoA",
|
||||
"position": 3
|
||||
},
|
||||
{
|
||||
"title": "Meet MetaGPT: The ChatGPT-Powered AI Assistant That Turns Text Into Web Apps",
|
||||
"link": "https://www.kdnuggets.com/meet-metagpt-the-chatgptpowered-ai-assistant-that-turns-text-into-web-apps",
|
||||
"snippet": "This revolutionary AI tool lets you create no-code web applications in just seconds!",
|
||||
"date": "Sep 8, 2023",
|
||||
"position": 4
|
||||
},
|
||||
{
|
||||
"title": "MetaGPT: Complete Guide to the Best AI Agent Available Right Now - Unite.AI",
|
||||
"link": "https://www.unite.ai/metagpt-complete-guide-to-the-best-ai-agent-available-right-now/",
|
||||
"snippet": "Discover why MetaGPT outperforms AutoGPT, BabyAgi, and other AI agents in complex coding tasks. Our in-depth article guides you through the ...",
|
||||
"date": "Sep 11, 2023",
|
||||
"position": 5
|
||||
},
|
||||
{
|
||||
"title": "MetaGPT | Discover AI use cases - GPT-3 Demo",
|
||||
"link": "https://gpt3demo.com/apps/metagpt",
|
||||
"snippet": "Assign different roles to GPTs to form a collaborative software entity for complex tasks. MetaGPT takes a one-line requirement as input and outputs user ...",
|
||||
"position": 6
|
||||
}
|
||||
],
|
||||
"relatedSearches": [
|
||||
{
|
||||
"query": "How to use MetaGPT"
|
||||
},
|
||||
{
|
||||
"query": "MetaGPT Reddit"
|
||||
},
|
||||
{
|
||||
"query": "Metagpt arXiv"
|
||||
},
|
||||
{
|
||||
"query": "MetaGPT youtube"
|
||||
},
|
||||
{
|
||||
"query": "MetaGPT example"
|
||||
},
|
||||
{
|
||||
"query": "Metagpt huggingface"
|
||||
},
|
||||
{
|
||||
"query": "metagpt: meta programming for multi-agent collaborative framework"
|
||||
},
|
||||
{
|
||||
"query": "MetaGPT alternative"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
115
tests/data/search/serper-metagpt-8.json
Normal file
115
tests/data/search/serper-metagpt-8.json
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
[
|
||||
{
|
||||
"searchParameters": {
|
||||
"q": "metagpt",
|
||||
"num": 8,
|
||||
"page": 1,
|
||||
"type": "search",
|
||||
"engine": "google"
|
||||
},
|
||||
"organic": [
|
||||
{
|
||||
"title": "geekan/MetaGPT: The Multi-Agent Framework: Given one line Requirement, return PRD, Design, Tasks, Repo - GitHub",
|
||||
"link": "https://github.com/geekan/MetaGPT",
|
||||
"snippet": "MetaGPT takes a one line requirement as input and outputs user stories / competitive analysis / requirements / data structures / APIs / documents, etc.",
|
||||
"sitelinks": [
|
||||
{
|
||||
"title": "README.md",
|
||||
"link": "https://github.com/geekan/MetaGPT/blob/main/README.md"
|
||||
},
|
||||
{
|
||||
"title": "Roadmap",
|
||||
"link": "https://github.com/geekan/MetaGPT/blob/main/docs/ROADMAP.md"
|
||||
},
|
||||
{
|
||||
"title": "Issues",
|
||||
"link": "https://github.com/geekan/MetaGPT/issues"
|
||||
},
|
||||
{
|
||||
"title": "Actions",
|
||||
"link": "https://github.com/geekan/MetaGPT/actions"
|
||||
}
|
||||
],
|
||||
"position": 1
|
||||
},
|
||||
{
|
||||
"title": "MetaGPT: Meta Programming for A Multi-Agent Collaborative Framework - arXiv",
|
||||
"link": "https://arxiv.org/abs/2308.00352",
|
||||
"snippet": "Abstract:Remarkable progress has been made on automated problem solving through societies of agents based on large language models (LLMs).",
|
||||
"date": "Aug 1, 2023",
|
||||
"position": 2
|
||||
},
|
||||
{
|
||||
"title": "How To Install MetaGPT - Build A Startup With One Prompt!! - YouTube",
|
||||
"link": "https://youtube.com/watch?v=uT75J_KG_aY",
|
||||
"snippet": "In this video, we review MetaGPT, a new project that aims ...",
|
||||
"date": "Aug 14, 2023",
|
||||
"attributes": {
|
||||
"Duration": "6:36",
|
||||
"Posted": "Aug 14, 2023"
|
||||
},
|
||||
"imageUrl": "https://i.ytimg.com/vi/uT75J_KG_aY/default.jpg?sqp=-oaymwEECHgQQw&rs=AMzJL3lfWRsXgckPQztWhHaRKYqxffksoA",
|
||||
"position": 3
|
||||
},
|
||||
{
|
||||
"title": "Meet MetaGPT: The ChatGPT-Powered AI Assistant That Turns Text Into Web Apps",
|
||||
"link": "https://www.kdnuggets.com/meet-metagpt-the-chatgptpowered-ai-assistant-that-turns-text-into-web-apps",
|
||||
"snippet": "This revolutionary AI tool lets you create no-code web applications in just seconds!",
|
||||
"date": "Sep 8, 2023",
|
||||
"position": 4
|
||||
},
|
||||
{
|
||||
"title": "MetaGPT: Complete Guide to the Best AI Agent Available Right Now - Unite.AI",
|
||||
"link": "https://www.unite.ai/metagpt-complete-guide-to-the-best-ai-agent-available-right-now/",
|
||||
"snippet": "Discover why MetaGPT outperforms AutoGPT, BabyAgi, and other AI agents in complex coding tasks. Our in-depth article guides you through the ...",
|
||||
"date": "Sep 11, 2023",
|
||||
"position": 5
|
||||
},
|
||||
{
|
||||
"title": "MetaGPT | Discover AI use cases - GPT-3 Demo",
|
||||
"link": "https://gpt3demo.com/apps/metagpt",
|
||||
"snippet": "Assign different roles to GPTs to form a collaborative software entity for complex tasks. MetaGPT takes a one-line requirement as input and outputs user ...",
|
||||
"position": 6
|
||||
},
|
||||
{
|
||||
"title": "MetaGPT AI technology page - Lablab.ai",
|
||||
"link": "https://lablab.ai/tech/metagpt",
|
||||
"snippet": "MetaGPT: Collaborative AI for Complex Tasks. MetaGPT is a groundbreaking AI technology, designed to transform the landscape of software development.",
|
||||
"position": 7
|
||||
},
|
||||
{
|
||||
"title": "MetaGPT: Meta Programming for Multi-Agent Collaborative Framework | OpenReview",
|
||||
"link": "https://openreview.net/forum?id=VtmBAGCN7o",
|
||||
"snippet": "This paper introduces MetaGPT, an innovative meta-programming framework for multi-agent collaborations based on LLM, which encodes Standardized ...",
|
||||
"date": "Sep 22, 2023",
|
||||
"position": 8
|
||||
}
|
||||
],
|
||||
"relatedSearches": [
|
||||
{
|
||||
"query": "How to use MetaGPT"
|
||||
},
|
||||
{
|
||||
"query": "MetaGPT Reddit"
|
||||
},
|
||||
{
|
||||
"query": "Metagpt arXiv"
|
||||
},
|
||||
{
|
||||
"query": "MetaGPT youtube"
|
||||
},
|
||||
{
|
||||
"query": "MetaGPT example"
|
||||
},
|
||||
{
|
||||
"query": "Metagpt huggingface"
|
||||
},
|
||||
{
|
||||
"query": "metagpt: meta programming for multi-agent collaborative framework"
|
||||
},
|
||||
{
|
||||
"query": "MetaGPT alternative"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -117,7 +117,6 @@ if __name__ == '__main__':
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_debug_error():
|
||||
CONFIG.src_workspace = CONFIG.git_repo.workdir / uuid.uuid4().hex
|
||||
ctx = RunCodeContext(
|
||||
|
|
@ -150,5 +149,16 @@ async def test_debug_error():
|
|||
rsp = await debug_error.run()
|
||||
|
||||
assert "class Player" in rsp # rewrite the same class
|
||||
# a key logic to rewrite to (original one is "if self.score > 12")
|
||||
# Problematic code:
|
||||
# ```
|
||||
# if self.score > 21 and any(card.rank == 'A' for card in self.hand):
|
||||
# self.score -= 10
|
||||
# ```
|
||||
# Should rewrite to (used "gpt-3.5-turbo-1106"):
|
||||
# ```
|
||||
# ace_count = sum(1 for card in self.hand if card.rank == 'A')
|
||||
# while self.score > 21 and ace_count > 0:
|
||||
# self.score -= 10
|
||||
# ace_count -= 1
|
||||
# ```
|
||||
assert "while self.score > 21" in rsp
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ from tests.metagpt.actions.mock_markdown import PRD_SAMPLE
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_design_api():
|
||||
inputs = ["我们需要一个音乐播放器,它应该有播放、暂停、上一曲、下一曲等功能。", PRD_SAMPLE]
|
||||
for prd in inputs:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ from metagpt.actions.design_api_review import DesignReview
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_design_api_review():
|
||||
prd = "我们需要一个音乐播放器,它应该有播放、暂停、上一曲、下一曲等功能。"
|
||||
api_design = """
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ context = """
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_generate_questions():
|
||||
action = GenerateQuestions()
|
||||
rsp = await action.run(context)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ async def test_generate_table(invoice_path: Path, expected_result: dict):
|
|||
("invoice_path", "query", "expected_result"),
|
||||
[(Path("invoices/invoice-1.pdf"), "Invoicing date", "2023年02月03日")],
|
||||
)
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_reply_question(invoice_path: Path, query: dict, expected_result: str):
|
||||
invoice_path = TEST_DATA_PATH / invoice_path
|
||||
ocr_result = await InvoiceOCR().run(file_path=Path(invoice_path))
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ from metagpt.logs import logger
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_prepare_interview():
|
||||
action = PrepareInterview()
|
||||
rsp = await action.run("I just graduated and hope to find a job as a Python engineer")
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ from tests.metagpt.actions.mock_json import DESIGN, PRD
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_design_api():
|
||||
await FileRepository.save_file("1.txt", content=str(PRD), relative_path=PRDS_FILE_REPO)
|
||||
await FileRepository.save_file("1.txt", content=str(DESIGN), relative_path=SYSTEM_DESIGN_FILE_REPO)
|
||||
|
|
|
|||
|
|
@ -11,13 +11,46 @@ from pathlib import Path
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.rebuild_class_view import RebuildClassView
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.const import GRAPH_REPO_FILE_REPO
|
||||
from metagpt.llm import LLM
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rebuild():
|
||||
action = RebuildClassView(name="RedBean", context=Path(__file__).parent.parent, llm=LLM())
|
||||
action = RebuildClassView(
|
||||
name="RedBean", context=str(Path(__file__).parent.parent.parent.parent / "metagpt"), llm=LLM()
|
||||
)
|
||||
await action.run()
|
||||
graph_file_repo = CONFIG.git_repo.new_file_repository(relative_path=GRAPH_REPO_FILE_REPO)
|
||||
assert graph_file_repo.changed_files
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("path", "direction", "diff", "want"),
|
||||
[
|
||||
("metagpt/startup.py", "=", ".", "metagpt/startup.py"),
|
||||
("metagpt/startup.py", "+", "MetaGPT", "MetaGPT/metagpt/startup.py"),
|
||||
("metagpt/startup.py", "-", "metagpt", "startup.py"),
|
||||
],
|
||||
)
|
||||
def test_align_path(path, direction, diff, want):
|
||||
res = RebuildClassView._align_root(path=path, direction=direction, diff_path=diff)
|
||||
assert res == want
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("path_root", "package_root", "want_direction", "want_diff"),
|
||||
[
|
||||
("/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"),
|
||||
],
|
||||
)
|
||||
def test_diff_path(path_root, package_root, want_direction, want_diff):
|
||||
direction, diff = RebuildClassView._diff_path(path_root=Path(path_root), package_root=Path(package_root))
|
||||
assert direction == want_direction
|
||||
assert diff == want_diff
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
55
tests/metagpt/actions/test_rebuild_sequence_view.py
Normal file
55
tests/metagpt/actions/test_rebuild_sequence_view.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2024/1/4
|
||||
@Author : mashenquan
|
||||
@File : test_rebuild_sequence_view.py
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions.rebuild_sequence_view import RebuildSequenceView
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.const import GRAPH_REPO_FILE_REPO
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.utils.common import aread
|
||||
from metagpt.utils.file_repository import FileRepository
|
||||
from metagpt.utils.git_repository import ChangeType
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rebuild():
|
||||
# Mock
|
||||
data = await aread(filename=Path(__file__).parent / "../../data/graph_db/networkx.json")
|
||||
graph_db_filename = Path(CONFIG.git_repo.workdir.name).with_suffix(".json")
|
||||
await FileRepository.save_file(
|
||||
filename=str(graph_db_filename),
|
||||
relative_path=GRAPH_REPO_FILE_REPO,
|
||||
content=data,
|
||||
)
|
||||
CONFIG.git_repo.add_change({f"{GRAPH_REPO_FILE_REPO}/{graph_db_filename}": ChangeType.UNTRACTED})
|
||||
CONFIG.git_repo.commit("commit1")
|
||||
|
||||
action = RebuildSequenceView(
|
||||
name="RedBean", context=str(Path(__file__).parent.parent.parent.parent / "metagpt"), llm=LLM()
|
||||
)
|
||||
await action.run()
|
||||
graph_file_repo = CONFIG.git_repo.new_file_repository(relative_path=GRAPH_REPO_FILE_REPO)
|
||||
assert graph_file_repo.changed_files
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("root", "pathname", "want"),
|
||||
[
|
||||
(Path(__file__).parent.parent.parent, "/".join(__file__.split("/")[-2:]), Path(__file__)),
|
||||
(Path(__file__).parent.parent.parent, "f/g.txt", None),
|
||||
],
|
||||
)
|
||||
def test_get_full_filename(root, pathname, want):
|
||||
res = RebuildSequenceView._get_full_filename(root=root, pathname=pathname)
|
||||
assert res == want
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
@ -8,14 +8,7 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions import CollectLinks, research
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action():
|
||||
action = CollectLinks()
|
||||
result = await action.run(topic="baidu")
|
||||
assert result
|
||||
from metagpt.actions import research
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -47,7 +47,10 @@ class TestSkillAction:
|
|||
assert args.get("size_type") == "512x512"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_parser_action(self):
|
||||
async def test_parser_action(self, mocker):
|
||||
# mock
|
||||
mocker.patch("metagpt.learn.text_to_image", return_value="https://mock.com/xxx")
|
||||
|
||||
parser_action = ArgumentsParingAction(skill=self.skill, ask="Draw an apple")
|
||||
rsp = await parser_action.run()
|
||||
assert rsp
|
||||
|
|
@ -80,7 +83,8 @@ class TestSkillAction:
|
|||
@pytest.mark.asyncio
|
||||
async def test_skill_action_error(self):
|
||||
action = SkillAction(skill=self.skill, args={})
|
||||
await action.run()
|
||||
rsp = await action.run()
|
||||
assert "Error" in rsp.content
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -177,7 +177,6 @@ class Snake:
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_summarize_code():
|
||||
CONFIG.src_workspace = CONFIG.git_repo.workdir / "src"
|
||||
await FileRepository.save_file(filename="1.json", relative_path=SYSTEM_DESIGN_FILE_REPO, content=DESIGN_CONTENT)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ from metagpt.schema import Message
|
|||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_prompt(agent_description, language, context, knowledge, history_summary):
|
||||
# Prerequisites
|
||||
CONFIG.agent_description = agent_description
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ from tests.metagpt.actions.mock_markdown import TASKS_2, WRITE_CODE_PROMPT_SAMPL
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_code():
|
||||
context = CodingContext(
|
||||
filename="task_filename.py", design_doc=Document(content="设计一个名为'add'的函数,该函数接受两个整数作为输入,并返回它们的和。")
|
||||
|
|
@ -45,7 +44,6 @@ async def test_write_code():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_code_directly():
|
||||
prompt = WRITE_CODE_PROMPT_SAMPLE + "\n" + TASKS_2[0]
|
||||
llm = LLM()
|
||||
|
|
@ -54,7 +52,6 @@ async def test_write_code_directly():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_code_deps():
|
||||
# Prerequisites
|
||||
CONFIG.src_workspace = CONFIG.git_repo.workdir / "snake1/snake1"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ from metagpt.schema import CodingContext, Document
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_code_review(capfd):
|
||||
code = """
|
||||
def add(a, b):
|
||||
|
|
|
|||
|
|
@ -27,14 +27,12 @@ class Person:
|
|||
],
|
||||
ids=["google", "numpy", "sphinx"],
|
||||
)
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_docstring(style: str, part: str):
|
||||
ret = await WriteDocstring().run(code, style=style)
|
||||
assert part in ret
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write():
|
||||
code = await WriteDocstring.write_docstring(__file__)
|
||||
assert code
|
||||
|
|
|
|||
|
|
@ -8,22 +8,25 @@
|
|||
"""
|
||||
import pytest
|
||||
|
||||
from metagpt.actions import UserRequirement
|
||||
from metagpt.actions import UserRequirement, WritePRD
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.const import DOCS_FILE_REPO, PRDS_FILE_REPO, REQUIREMENT_FILENAME
|
||||
from metagpt.logs import logger
|
||||
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 metagpt.utils.file_repository import FileRepository
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_prd():
|
||||
async def test_write_prd(new_filename):
|
||||
product_manager = ProductManager()
|
||||
requirements = "开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结"
|
||||
await FileRepository.save_file(filename=REQUIREMENT_FILENAME, content=requirements, relative_path=DOCS_FILE_REPO)
|
||||
product_manager.rc.react_mode = RoleReactMode.BY_ORDER
|
||||
prd = await product_manager.run(Message(content=requirements, cause_by=UserRequirement))
|
||||
assert prd.cause_by == any_to_str(WritePRD)
|
||||
logger.info(requirements)
|
||||
logger.info(prd)
|
||||
|
||||
|
|
@ -31,3 +34,7 @@ async def test_write_prd():
|
|||
assert prd is not None
|
||||
assert prd.content != ""
|
||||
assert CONFIG.git_repo.new_file_repository(relative_path=PRDS_FILE_REPO).changed_files
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ from metagpt.actions.write_prd_review import WritePRDReview
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_prd_review():
|
||||
prd = """
|
||||
Introduction: This is a new feature for our product.
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ CONTEXT = """
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_review():
|
||||
write_review = WriteReview()
|
||||
review = await write_review.run(CONTEXT)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ from metagpt.actions.write_teaching_plan import WriteTeachingPlanPart
|
|||
("topic", "context"),
|
||||
[("Title", "Lesson 1: Learn to draw an apple."), ("Teaching Content", "Lesson 1: Learn to draw an apple.")],
|
||||
)
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_teaching_plan_part(topic, context):
|
||||
action = WriteTeachingPlanPart(topic=topic, context=context)
|
||||
rsp = await action.run()
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ from metagpt.schema import Document, TestingContext
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_test():
|
||||
code = """
|
||||
import random
|
||||
|
|
@ -40,7 +39,6 @@ async def test_write_test():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_code_invalid_code(mocker):
|
||||
# Mock the _aask method to return an invalid code string
|
||||
mocker.patch.object(WriteTest, "_aask", return_value="Invalid Code String")
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ from metagpt.actions.write_tutorial import WriteContent, WriteDirectory
|
|||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("language", "topic"), [("English", "Write a tutorial about Python")])
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_directory(language: str, topic: str):
|
||||
ret = await WriteDirectory(language=language).run(topic=topic)
|
||||
assert isinstance(ret, dict)
|
||||
|
|
@ -30,7 +29,6 @@ async def test_write_directory(language: str, topic: str):
|
|||
("language", "topic", "directory"),
|
||||
[("English", "Write a tutorial about Python", {"Introduction": ["What is Python?", "Why learn Python?"]})],
|
||||
)
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_content(language: str, topic: str, directory: Dict):
|
||||
ret = await WriteContent(language=language, directory=directory).run(topic=topic)
|
||||
assert isinstance(ret, str)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,16 @@ points = [
|
|||
]
|
||||
|
||||
|
||||
def assert_almost_equal(actual, expected):
|
||||
delta = 1e-10
|
||||
if isinstance(expected, list):
|
||||
assert len(actual) == len(expected)
|
||||
for ac, exp in zip(actual, expected):
|
||||
assert abs(ac - exp) <= delta, f"{ac} is not within {delta} of {exp}"
|
||||
else:
|
||||
assert abs(actual - expected) <= delta, f"{actual} is not within {delta} of {expected}"
|
||||
|
||||
|
||||
def test_qdrant_store():
|
||||
qdrant_connection = QdrantConnection(memory=True)
|
||||
vectors_config = VectorParams(size=2, distance=Distance.COSINE)
|
||||
|
|
@ -42,30 +52,30 @@ def test_qdrant_store():
|
|||
qdrant_store.add("Book", points)
|
||||
results = qdrant_store.search("Book", query=[1.0, 1.0])
|
||||
assert results[0]["id"] == 2
|
||||
assert results[0]["score"] == 0.999106722578389
|
||||
assert_almost_equal(results[0]["score"], 0.999106722578389)
|
||||
assert results[1]["id"] == 7
|
||||
assert results[1]["score"] == 0.9961650411397226
|
||||
assert_almost_equal(results[1]["score"], 0.9961650411397226)
|
||||
results = qdrant_store.search("Book", query=[1.0, 1.0], return_vector=True)
|
||||
assert results[0]["id"] == 2
|
||||
assert results[0]["score"] == 0.999106722578389
|
||||
assert results[0]["vector"] == [0.7363563179969788, 0.6765939593315125]
|
||||
assert_almost_equal(results[0]["score"], 0.999106722578389)
|
||||
assert_almost_equal(results[0]["vector"], [0.7363563179969788, 0.6765939593315125])
|
||||
assert results[1]["id"] == 7
|
||||
assert results[1]["score"] == 0.9961650411397226
|
||||
assert results[1]["vector"] == [0.7662628889083862, 0.6425272226333618]
|
||||
assert_almost_equal(results[1]["score"], 0.9961650411397226)
|
||||
assert_almost_equal(results[1]["vector"], [0.7662628889083862, 0.6425272226333618])
|
||||
results = qdrant_store.search(
|
||||
"Book",
|
||||
query=[1.0, 1.0],
|
||||
query_filter=Filter(must=[FieldCondition(key="rand_number", range=Range(gte=8))]),
|
||||
)
|
||||
assert results[0]["id"] == 8
|
||||
assert results[0]["score"] == 0.9100373450784073
|
||||
assert_almost_equal(results[0]["score"], 0.9100373450784073)
|
||||
assert results[1]["id"] == 9
|
||||
assert results[1]["score"] == 0.7127610621127889
|
||||
assert_almost_equal(results[1]["score"], 0.7127610621127889)
|
||||
results = qdrant_store.search(
|
||||
"Book",
|
||||
query=[1.0, 1.0],
|
||||
query_filter=Filter(must=[FieldCondition(key="rand_number", range=Range(gte=8))]),
|
||||
return_vector=True,
|
||||
)
|
||||
assert results[0]["vector"] == [0.35037919878959656, 0.9366079568862915]
|
||||
assert results[1]["vector"] == [0.9999677538871765, 0.00802854634821415]
|
||||
assert_almost_equal(results[0]["vector"], [0.35037919878959656, 0.9366079568862915])
|
||||
assert_almost_equal(results[1]["vector"], [0.9999677538871765, 0.00802854634821415])
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
@File : test_skill_loader.py
|
||||
@Desc : Unit tests.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.config import CONFIG
|
||||
|
|
@ -23,7 +25,8 @@ async def test_suite():
|
|||
{"id": 6, "name": "knowledge", "type": "builtin", "config": {}, "enabled": True},
|
||||
{"id": 6, "name": "web_search", "type": "builtin", "config": {}, "enabled": True},
|
||||
]
|
||||
loader = await SkillsDeclaration.load()
|
||||
pathname = Path(__file__).parent / "../../../docs/.well-known/skills.yaml"
|
||||
loader = await SkillsDeclaration.load(skill_yaml_file_name=pathname)
|
||||
skills = loader.get_skill_list()
|
||||
assert skills
|
||||
assert len(skills) >= 3
|
||||
|
|
|
|||
|
|
@ -12,10 +12,18 @@ import pytest
|
|||
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.learn.text_to_image import text_to_image
|
||||
from metagpt.tools.metagpt_text_to_image import MetaGPTText2Image
|
||||
from metagpt.tools.openai_text_to_image import OpenAIText2Image
|
||||
from metagpt.utils.s3 import S3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_metagpt_llm():
|
||||
async def test_text_to_image(mocker):
|
||||
# mock
|
||||
mocker.patch.object(MetaGPTText2Image, "text_2_image", return_value=b"mock MetaGPTText2Image")
|
||||
mocker.patch.object(OpenAIText2Image, "text_2_image", return_value=b"mock OpenAIText2Image")
|
||||
mocker.patch.object(S3, "cache", return_value="http://mock/s3")
|
||||
|
||||
# Prerequisites
|
||||
assert CONFIG.METAGPT_TEXT_TO_IMAGE_MODEL_URL
|
||||
assert CONFIG.OPENAI_API_KEY
|
||||
|
|
|
|||
8
tests/metagpt/provider/conftest.py
Normal file
8
tests/metagpt/provider/conftest.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def llm_mock(rsp_cache, mocker, request):
|
||||
# An empty fixture to overwrite the global llm_mock fixture
|
||||
# because in provider folder, we want to test the aask and aask functions for the specific models
|
||||
pass
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
# @Desc : the unittest of ZhiPuAILLM
|
||||
|
||||
import pytest
|
||||
from zhipuai.utils.sse_client import Event
|
||||
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.provider.zhipuai_api import ZhiPuAILLM
|
||||
|
|
@ -15,35 +14,16 @@ messages = [{"role": "user", "content": prompt_msg}]
|
|||
|
||||
resp_content = "I'm chatglm-turbo"
|
||||
default_resp = {
|
||||
"code": 200,
|
||||
"data": {
|
||||
"choices": [{"role": "assistant", "content": resp_content}],
|
||||
"usage": {"prompt_tokens": 20, "completion_tokens": 20},
|
||||
},
|
||||
"choices": [{"finish_reason": "stop", "index": 0, "message": {"content": resp_content, "role": "assistant"}}],
|
||||
"usage": {"completion_tokens": 22, "prompt_tokens": 19, "total_tokens": 41},
|
||||
}
|
||||
|
||||
|
||||
def mock_zhipuai_invoke(**kwargs) -> dict:
|
||||
return default_resp
|
||||
|
||||
|
||||
async def mock_zhipuai_ainvoke(**kwargs) -> dict:
|
||||
return default_resp
|
||||
|
||||
|
||||
async def mock_zhipuai_asse_invoke(**kwargs):
|
||||
async def mock_zhipuai_acreate_stream(self, **kwargs):
|
||||
class MockResponse(object):
|
||||
async def _aread(self):
|
||||
class Iterator(object):
|
||||
events = [
|
||||
Event(id="xxx", event="add", data=resp_content, retry=0),
|
||||
Event(
|
||||
id="xxx",
|
||||
event="finish",
|
||||
data="",
|
||||
meta='{"usage": {"completion_tokens": 20,"prompt_tokens": 20}}',
|
||||
),
|
||||
]
|
||||
events = [{"choices": [{"index": 0, "delta": {"content": resp_content, "role": "assistant"}}]}]
|
||||
|
||||
async def __aiter__(self):
|
||||
for event in self.events:
|
||||
|
|
@ -52,23 +32,26 @@ async def mock_zhipuai_asse_invoke(**kwargs):
|
|||
async for chunk in Iterator():
|
||||
yield chunk
|
||||
|
||||
async def async_events(self):
|
||||
async def stream(self):
|
||||
async for chunk in self._aread():
|
||||
yield chunk
|
||||
|
||||
return MockResponse()
|
||||
|
||||
|
||||
async def mock_zhipuai_acreate(self, **kwargs) -> dict:
|
||||
return default_resp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_zhipuai_acompletion(mocker):
|
||||
mocker.patch("metagpt.provider.zhipuai.zhipu_model_api.ZhiPuModelAPI.invoke", mock_zhipuai_invoke)
|
||||
mocker.patch("metagpt.provider.zhipuai.zhipu_model_api.ZhiPuModelAPI.ainvoke", mock_zhipuai_ainvoke)
|
||||
mocker.patch("metagpt.provider.zhipuai.zhipu_model_api.ZhiPuModelAPI.asse_invoke", mock_zhipuai_asse_invoke)
|
||||
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()
|
||||
|
||||
resp = await zhipu_gpt.acompletion(messages)
|
||||
assert resp["data"]["choices"][0]["content"] == resp_content
|
||||
assert resp["choices"][0]["message"]["content"] == resp_content
|
||||
|
||||
resp = await zhipu_gpt.aask(prompt_msg, stream=False)
|
||||
assert resp == resp_content
|
||||
|
|
|
|||
|
|
@ -11,16 +11,16 @@ from metagpt.provider.zhipuai.async_sse_client import AsyncSSEClient
|
|||
async def test_async_sse_client():
|
||||
class Iterator(object):
|
||||
async def __aiter__(self):
|
||||
yield b"data: test_value"
|
||||
yield b'data: {"test_key": "test_value"}'
|
||||
|
||||
async_sse_client = AsyncSSEClient(event_source=Iterator())
|
||||
async for event in async_sse_client.async_events():
|
||||
assert event.data, "test_value"
|
||||
async for chunk in async_sse_client.stream():
|
||||
assert "test_value" in chunk.values()
|
||||
|
||||
class InvalidIterator(object):
|
||||
async def __aiter__(self):
|
||||
yield b"invalid: test_value"
|
||||
|
||||
async_sse_client = AsyncSSEClient(event_source=InvalidIterator())
|
||||
async for event in async_sse_client.async_events():
|
||||
assert not event
|
||||
async for chunk in async_sse_client.stream():
|
||||
assert not chunk
|
||||
|
|
|
|||
|
|
@ -6,15 +6,13 @@ from typing import Any, Tuple
|
|||
|
||||
import pytest
|
||||
import zhipuai
|
||||
from zhipuai.model_api.api import InvokeType
|
||||
from zhipuai.utils.http_client import headers as zhipuai_default_headers
|
||||
|
||||
from metagpt.provider.zhipuai.zhipu_model_api import ZhiPuModelAPI
|
||||
|
||||
api_key = "xxx.xxx"
|
||||
zhipuai.api_key = api_key
|
||||
|
||||
default_resp = b'{"result": "test response"}'
|
||||
default_resp = b'{"choices": [{"finish_reason": "stop", "index": 0, "message": {"content": "test response", "role": "assistant"}}]}'
|
||||
|
||||
|
||||
async def mock_requestor_arequest(self, **kwargs) -> Tuple[Any, Any, str]:
|
||||
|
|
@ -23,22 +21,15 @@ async def mock_requestor_arequest(self, **kwargs) -> Tuple[Any, Any, str]:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_zhipu_model_api(mocker):
|
||||
header = ZhiPuModelAPI.get_header()
|
||||
zhipuai_default_headers.update({"Authorization": api_key})
|
||||
assert header == zhipuai_default_headers
|
||||
|
||||
sse_header = ZhiPuModelAPI.get_sse_header()
|
||||
assert len(sse_header["Authorization"]) == 191
|
||||
|
||||
url_prefix, url_suffix = ZhiPuModelAPI.split_zhipu_api_url(InvokeType.SYNC, kwargs={"model": "chatglm_turbo"})
|
||||
url_prefix, url_suffix = ZhiPuModelAPI(api_key=api_key).split_zhipu_api_url()
|
||||
assert url_prefix == "https://open.bigmodel.cn/api"
|
||||
assert url_suffix == "/paas/v3/model-api/chatglm_turbo/invoke"
|
||||
assert url_suffix == "/paas/v4/chat/completions"
|
||||
|
||||
mocker.patch("metagpt.provider.general_api_requestor.GeneralAPIRequestor.arequest", mock_requestor_arequest)
|
||||
result = await ZhiPuModelAPI.arequest(
|
||||
InvokeType.SYNC, stream=False, method="get", headers={}, kwargs={"model": "chatglm_turbo"}
|
||||
result = await ZhiPuModelAPI(api_key=api_key).arequest(
|
||||
stream=False, method="get", headers={}, kwargs={"model": "glm-3-turbo"}
|
||||
)
|
||||
assert result == default_resp
|
||||
|
||||
result = await ZhiPuModelAPI.ainvoke()
|
||||
assert result["result"] == "test response"
|
||||
result = await ZhiPuModelAPI(api_key=api_key).acreate()
|
||||
assert result["choices"][0]["message"]["content"] == "test response"
|
||||
|
|
|
|||
|
|
@ -284,4 +284,6 @@ class MockMessages:
|
|||
prd = Message(role="Product Manager", content=PRD, cause_by=WritePRD)
|
||||
system_design = Message(role="Architect", content=SYSTEM_DESIGN, cause_by=WriteDesign)
|
||||
tasks = Message(role="Project Manager", content=TASKS, cause_by=WriteTasks)
|
||||
json_tasks = Message(role="Project Manager", content=json.dumps(JSON_TASKS), cause_by=WriteTasks)
|
||||
json_tasks = Message(
|
||||
role="Project Manager", content=json.dumps(JSON_TASKS, ensure_ascii=False), cause_by=WriteTasks
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ from tests.metagpt.roles.mock import MockMessages
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_architect():
|
||||
# Prerequisites
|
||||
filename = uuid.uuid4().hex + ".json"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ from pydantic import BaseModel
|
|||
from metagpt.actions.skill_action import SkillAction
|
||||
from metagpt.actions.talk_action import TalkAction
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.logs import logger
|
||||
from metagpt.memory.brain_memory import BrainMemory
|
||||
from metagpt.roles.assistant import Assistant
|
||||
from metagpt.schema import Message
|
||||
|
|
@ -21,7 +20,6 @@ from metagpt.utils.common import any_to_str
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_run():
|
||||
CONFIG.language = "Chinese"
|
||||
|
||||
|
|
@ -88,7 +86,7 @@ async def test_run():
|
|||
if not has_action:
|
||||
break
|
||||
msg: Message = await role.act()
|
||||
logger.info(msg)
|
||||
# logger.info(msg)
|
||||
assert msg
|
||||
assert msg.cause_by == seed.cause_by
|
||||
assert msg.content
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ from tests.metagpt.roles.mock import STRS_FOR_PARSING, TASKS, MockMessages
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_engineer():
|
||||
# Prerequisites
|
||||
rqno = "20231221155954.json"
|
||||
|
|
@ -114,7 +113,6 @@ def test_todo():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_new_coding_context():
|
||||
# Prerequisites
|
||||
demo_path = Path(__file__).parent / "../../data/demo_project"
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ from metagpt.schema import Message
|
|||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_invoice_ocr_assistant(query: str, invoice_path: Path, invoice_table_path: Path, expected_result: dict):
|
||||
invoice_path = TEST_DATA_PATH / invoice_path
|
||||
role = InvoiceOCRAssistant()
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ from tests.metagpt.roles.mock import MockMessages
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_product_manager():
|
||||
async def test_product_manager(new_filename):
|
||||
product_manager = ProductManager()
|
||||
rsp = await product_manager.run(MockMessages.req)
|
||||
logger.info(rsp)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ from tests.metagpt.roles.mock import MockMessages
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_project_manager():
|
||||
project_manager = ProjectManager()
|
||||
rsp = await project_manager.run(MockMessages.system_design)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# @Desc : unittest of Role
|
||||
import pytest
|
||||
|
||||
from metagpt.llm import HumanProvider
|
||||
from metagpt.roles.role import Role
|
||||
|
||||
|
||||
|
|
@ -12,5 +13,10 @@ def test_role_desc():
|
|||
assert role.desc == "Best Seller"
|
||||
|
||||
|
||||
def test_role_human():
|
||||
role = Role(is_human=True)
|
||||
assert isinstance(role.llm, HumanProvider)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from metagpt.schema import Message
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skip
|
||||
async def test_init():
|
||||
class Inputs(BaseModel):
|
||||
name: str
|
||||
|
|
@ -103,7 +104,6 @@ async def test_new_file_name():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_run():
|
||||
CONFIG.set_context({"language": "Chinese", "teaching_language": "English"})
|
||||
lesson = """
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ from metagpt.roles.tutorial_assistant import TutorialAssistant
|
|||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("language", "topic"), [("Chinese", "Write a tutorial about pip")])
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_tutorial_assistant(language: str, topic: str):
|
||||
role = TutorialAssistant(language=language)
|
||||
msg = await role.run(topic)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ def test_action_serialize():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_action_deserialize():
|
||||
action = Action()
|
||||
serialized_data = action.model_dump()
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ def test_architect_serialize():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_architect_deserialize():
|
||||
role = Architect()
|
||||
ser_role_dict = role.model_dump(by_alias=True)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from metagpt.actions.prepare_interview import PrepareInterview
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_action_deserialize():
|
||||
action = PrepareInterview()
|
||||
serialized_data = action.model_dump()
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ from metagpt.schema import Message
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_product_manager_deserialize():
|
||||
async def test_product_manager_deserialize(new_filename):
|
||||
role = ProductManager()
|
||||
ser_role_dict = role.model_dump(by_alias=True)
|
||||
new_role = ProductManager(**ser_role_dict)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ def test_project_manager_serialize():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_project_manager_deserialize():
|
||||
role = ProjectManager()
|
||||
ser_role_dict = role.model_dump(by_alias=True)
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ def test_engineer_serialize():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_engineer_deserialize():
|
||||
role = Engineer(use_code_review=True)
|
||||
ser_role_dict = role.model_dump()
|
||||
|
|
@ -97,7 +96,6 @@ def test_role_serdeser_save():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_role_serdeser_interrupt():
|
||||
role_c = RoleC()
|
||||
shutil.rmtree(SERDESER_PATH.joinpath("team"), ignore_errors=True)
|
||||
|
|
|
|||
|
|
@ -109,7 +109,6 @@ async def test_team_recover_save():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_team_recover_multi_roles_save():
|
||||
idea = "write a snake game"
|
||||
stg_path = SERDESER_PATH.joinpath("team")
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ def test_write_design_serialize():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_code_deserialize():
|
||||
context = CodingContext(
|
||||
filename="test_code.py", design_doc=Document(content="write add function to calculate two numbers")
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from metagpt.schema import CodingContext, Document
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_code_review_deserialize():
|
||||
code_content = """
|
||||
def div(a: int, b: int = 0):
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ def test_write_task_serialize():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_design_deserialize():
|
||||
action = WriteDesign()
|
||||
serialized_data = action.model_dump()
|
||||
|
|
@ -32,7 +31,6 @@ async def test_write_design_deserialize():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_task_deserialize():
|
||||
action = WriteTasks()
|
||||
serialized_data = action.model_dump()
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ class Person:
|
|||
],
|
||||
ids=["google", "numpy", "sphinx"],
|
||||
)
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_action_deserialize(style: str, part: str):
|
||||
action = WriteDocstring()
|
||||
serialized_data = action.model_dump()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from metagpt.actions import WritePRD
|
|||
from metagpt.schema import Message
|
||||
|
||||
|
||||
def test_action_serialize():
|
||||
def test_action_serialize(new_filename):
|
||||
action = WritePRD()
|
||||
ser_action_dict = action.model_dump()
|
||||
assert "name" in ser_action_dict
|
||||
|
|
@ -17,8 +17,7 @@ def test_action_serialize():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_action_deserialize():
|
||||
async def test_action_deserialize(new_filename):
|
||||
action = WritePRD()
|
||||
serialized_data = action.model_dump()
|
||||
new_action = WritePRD(**serialized_data)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ CONTEXT = """
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_action_deserialize():
|
||||
action = WriteReview()
|
||||
serialized_data = action.model_dump()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from metagpt.actions.write_tutorial import WriteContent, WriteDirectory
|
|||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("language", "topic"), [("English", "Write a tutorial about Python")])
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_directory_deserialize(language: str, topic: str):
|
||||
action = WriteDirectory()
|
||||
serialized_data = action.model_dump()
|
||||
|
|
@ -31,7 +30,6 @@ async def test_write_directory_deserialize(language: str, topic: str):
|
|||
("language", "topic", "directory"),
|
||||
[("English", "Write a tutorial about Python", {"Introduction": ["What is Python?", "Why learn Python?"]})],
|
||||
)
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_write_content_deserialize(language: str, topic: str, directory: Dict):
|
||||
action = WriteContent(language=language, directory=directory)
|
||||
serialized_data = action.model_dump()
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ def test_get_roles(env: Environment):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_publish_and_process_message(env: Environment):
|
||||
async def test_publish_and_process_message(env: Environment, new_filename):
|
||||
if CONFIG.git_repo:
|
||||
CONFIG.git_repo.delete_repository()
|
||||
CONFIG.git_repo = None
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ from metagpt.config import CONFIG
|
|||
from metagpt.const import SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO
|
||||
from metagpt.schema import (
|
||||
AIMessage,
|
||||
ClassAttribute,
|
||||
ClassMethod,
|
||||
ClassView,
|
||||
CodeSummarizeContext,
|
||||
Document,
|
||||
Message,
|
||||
|
|
@ -156,5 +159,30 @@ def test_CodeSummarizeContext(file_list, want):
|
|||
assert want in m
|
||||
|
||||
|
||||
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")
|
||||
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(
|
||||
name="_test",
|
||||
visibility="#",
|
||||
static=True,
|
||||
args=[ClassAttribute(name="a", value_type="str"), ClassAttribute(name="b", value_type="int")],
|
||||
return_type="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'
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -16,14 +16,14 @@ runner = CliRunner()
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_team():
|
||||
async def test_empty_team(new_filename):
|
||||
# FIXME: we're now using "metagpt" cli, so the entrance should be replaced instead.
|
||||
company = Team()
|
||||
history = await company.run(idea="Build a simple search system. I will upload my files later.")
|
||||
logger.info(history)
|
||||
|
||||
|
||||
def test_startup():
|
||||
def test_startup(new_filename):
|
||||
args = ["Make a cli snake game"]
|
||||
result = runner.invoke(app, args)
|
||||
logger.info(result)
|
||||
|
|
|
|||
|
|
@ -24,13 +24,14 @@ async def test_oas2_svc():
|
|||
process = subprocess.Popen(["python", str(script_pathname)], cwd=str(workdir), env=env)
|
||||
await asyncio.sleep(5)
|
||||
|
||||
url = "http://localhost:8080/openapi/greeting/dave"
|
||||
headers = {"accept": "text/plain", "Content-Type": "application/json"}
|
||||
data = {}
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
assert response.text == "Hello dave\n"
|
||||
|
||||
process.terminate()
|
||||
try:
|
||||
url = "http://localhost:8080/openapi/greeting/dave"
|
||||
headers = {"accept": "text/plain", "Content-Type": "application/json"}
|
||||
data = {}
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
assert response.text == "Hello dave\n"
|
||||
finally:
|
||||
process.terminate()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
@Author : mashenquan
|
||||
@File : test_metagpt_text_to_image.py
|
||||
"""
|
||||
import base64
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -13,7 +15,14 @@ from metagpt.tools.metagpt_text_to_image import oas3_metagpt_text_to_image
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_draw():
|
||||
async def test_draw(mocker):
|
||||
# mock
|
||||
mock_post = mocker.patch("aiohttp.ClientSession.post")
|
||||
mock_response = AsyncMock()
|
||||
mock_response.status = 200
|
||||
mock_response.json.return_value = {"images": [base64.b64encode(b"success")], "parameters": {"size": 1110}}
|
||||
mock_post.return_value.__aenter__.return_value = mock_response
|
||||
|
||||
# Prerequisites
|
||||
assert CONFIG.METAGPT_TEXT_TO_IMAGE_MODEL_URL
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"""
|
||||
@Time : 2023/12/26
|
||||
@Author : mashenquan
|
||||
@File : test_hello.py
|
||||
@File : test_openapi_v3_hello.py
|
||||
"""
|
||||
import asyncio
|
||||
import subprocess
|
||||
|
|
@ -24,13 +24,14 @@ async def test_hello():
|
|||
process = subprocess.Popen(["python", str(script_pathname)], cwd=workdir, env=env)
|
||||
await asyncio.sleep(5)
|
||||
|
||||
url = "http://localhost:8082/openapi/greeting/dave"
|
||||
headers = {"accept": "text/plain", "Content-Type": "application/json"}
|
||||
data = {}
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
assert response.text == "Hello dave\n"
|
||||
|
||||
process.terminate()
|
||||
try:
|
||||
url = "http://localhost:8082/openapi/greeting/dave"
|
||||
headers = {"accept": "text/plain", "Content-Type": "application/json"}
|
||||
data = {}
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
assert response.text == "Hello dave\n"
|
||||
finally:
|
||||
process.terminate()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 2023/7/22 02:40
|
||||
# @Author : stellahong (stellahong@deepwisdom.ai)
|
||||
#
|
||||
import os
|
||||
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.tools.sd_engine import SDEngine
|
||||
|
||||
|
||||
def test_sd_engine_init():
|
||||
sd_engine = SDEngine()
|
||||
assert sd_engine.payload["seed"] == -1
|
||||
|
||||
|
||||
def test_sd_engine_generate_prompt():
|
||||
sd_engine = SDEngine()
|
||||
sd_engine.construct_payload(prompt="test")
|
||||
assert sd_engine.payload["prompt"] == "test"
|
||||
|
||||
|
||||
async def test_sd_engine_run_t2i():
|
||||
sd_engine = SDEngine()
|
||||
await sd_engine.run_t2i(prompts=["test"])
|
||||
img_path = CONFIG.workspace_path / "resources" / "SD_Output" / "output_0.png"
|
||||
assert os.path.exists(img_path)
|
||||
|
|
@ -7,15 +7,20 @@
|
|||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
|
||||
import tests.data.search
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.logs import logger
|
||||
from metagpt.tools import SearchEngineType
|
||||
from metagpt.tools.search_engine import SearchEngine
|
||||
|
||||
search_cache_path = Path(tests.data.search.__path__[0])
|
||||
|
||||
|
||||
class MockSearchEnine:
|
||||
async def run(self, query: str, max_results: int = 8, as_string: bool = True) -> str | list[dict[str, str]]:
|
||||
|
|
@ -41,16 +46,23 @@ class MockSearchEnine:
|
|||
(SearchEngineType.CUSTOM_ENGINE, MockSearchEnine().run, 6, False),
|
||||
],
|
||||
)
|
||||
async def test_search_engine(search_engine_type, run_func: Callable, max_results: int, as_string: bool):
|
||||
async def test_search_engine(search_engine_type, run_func: Callable, max_results: int, as_string: bool, aiohttp_mocker):
|
||||
# Prerequisites
|
||||
cache_json_path = None
|
||||
if search_engine_type is SearchEngineType.SERPAPI_GOOGLE:
|
||||
assert CONFIG.SERPAPI_API_KEY and CONFIG.SERPAPI_API_KEY != "YOUR_API_KEY"
|
||||
cache_json_path = search_cache_path / f"serpapi-metagpt-{max_results}.json"
|
||||
elif search_engine_type is SearchEngineType.DIRECT_GOOGLE:
|
||||
assert CONFIG.GOOGLE_API_KEY and CONFIG.GOOGLE_API_KEY != "YOUR_API_KEY"
|
||||
assert CONFIG.GOOGLE_CSE_ID and CONFIG.GOOGLE_CSE_ID != "YOUR_CSE_ID"
|
||||
elif search_engine_type is SearchEngineType.SERPER_GOOGLE:
|
||||
assert CONFIG.SERPER_API_KEY and CONFIG.SERPER_API_KEY != "YOUR_API_KEY"
|
||||
cache_json_path = search_cache_path / f"serper-metagpt-{max_results}.json"
|
||||
|
||||
if cache_json_path:
|
||||
with open(cache_json_path) as f:
|
||||
data = json.load(f)
|
||||
aiohttp_mocker.set_json(data)
|
||||
search_engine = SearchEngine(search_engine_type, run_func)
|
||||
rsp = await search_engine.run("metagpt", max_results, as_string)
|
||||
logger.info(rsp)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ def search_engine_server():
|
|||
meilisearch_process.wait()
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_meilisearch(search_engine_server):
|
||||
# Prerequisites
|
||||
# https://www.meilisearch.com/docs/learn/getting_started/installation
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ from metagpt.tools.translator import Translator
|
|||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.usefixtures("llm_api")
|
||||
@pytest.mark.usefixtures("llm_mock")
|
||||
async def test_translate(llm_api):
|
||||
poetries = [
|
||||
("Let life be beautiful like summer flowers", "花"),
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ from metagpt.utils.parse_html import WebPage
|
|||
@pytest.mark.parametrize(
|
||||
"browser_type, use_proxy, kwagrs, url, urls",
|
||||
[
|
||||
("chromium", {"proxy": True}, {}, "https://deepwisdom.ai", ("https://deepwisdom.ai",)),
|
||||
("firefox", {}, {"ignore_https_errors": True}, "https://deepwisdom.ai", ("https://deepwisdom.ai",)),
|
||||
("webkit", {}, {"ignore_https_errors": True}, "https://deepwisdom.ai", ("https://deepwisdom.ai",)),
|
||||
("chromium", {"proxy": True}, {}, "https://www.deepwisdom.ai", ("https://www.deepwisdom.ai",)),
|
||||
("firefox", {}, {"ignore_https_errors": True}, "https://www.deepwisdom.ai", ("https://www.deepwisdom.ai",)),
|
||||
("webkit", {}, {"ignore_https_errors": True}, "https://www.deepwisdom.ai", ("https://www.deepwisdom.ai",)),
|
||||
],
|
||||
ids=["chromium-normal", "firefox-normal", "webkit-normal"],
|
||||
)
|
||||
|
|
@ -23,6 +23,7 @@ async def test_scrape_web_page(browser_type, use_proxy, kwagrs, url, urls, proxy
|
|||
global_proxy = CONFIG.global_proxy
|
||||
try:
|
||||
if use_proxy:
|
||||
server, proxy = await proxy
|
||||
CONFIG.global_proxy = proxy
|
||||
browser = web_browser_engine_playwright.PlaywrightWrapper(browser_type=browser_type, **kwagrs)
|
||||
result = await browser.run(url)
|
||||
|
|
@ -35,6 +36,7 @@ async def test_scrape_web_page(browser_type, use_proxy, kwagrs, url, urls, proxy
|
|||
assert len(results) == len(urls) + 1
|
||||
assert all(("MetaGPT" in i.inner_text) for i in results)
|
||||
if use_proxy:
|
||||
server.close()
|
||||
assert "Proxy:" in capfd.readouterr().out
|
||||
finally:
|
||||
CONFIG.global_proxy = global_proxy
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ async def test_scrape_web_page(browser_type, use_proxy, url, urls, proxy, capfd)
|
|||
global_proxy = CONFIG.global_proxy
|
||||
try:
|
||||
if use_proxy:
|
||||
server, proxy = await proxy
|
||||
CONFIG.global_proxy = proxy
|
||||
browser = web_browser_engine_selenium.SeleniumWrapper(browser_type=browser_type)
|
||||
result = await browser.run(url)
|
||||
|
|
@ -38,6 +39,7 @@ async def test_scrape_web_page(browser_type, use_proxy, url, urls, proxy, capfd)
|
|||
assert len(results) == len(urls) + 1
|
||||
assert all(("MetaGPT" in i.inner_text) for i in results)
|
||||
if use_proxy:
|
||||
server.close()
|
||||
assert "Proxy:" in capfd.readouterr().out
|
||||
finally:
|
||||
CONFIG.global_proxy = global_proxy
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ from metagpt.utils.common import (
|
|||
read_file_block,
|
||||
read_json_file,
|
||||
require_python_version,
|
||||
split_namespace,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -163,6 +164,23 @@ class TestGetProjectRoot:
|
|||
assert concat_namespace("a", "b", "c", "e") == "a:b:c:e"
|
||||
assert concat_namespace("a", "b", "c", "e", "f") == "a:b:c:e:f"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("val", "want"),
|
||||
[
|
||||
(
|
||||
"tests/metagpt/test_role.py:test_react:Input:subscription",
|
||||
["tests/metagpt/test_role.py", "test_react", "Input", "subscription"],
|
||||
),
|
||||
(
|
||||
"tests/metagpt/test_role.py:test_react:Input:goal",
|
||||
["tests/metagpt/test_role.py", "test_react", "Input", "goal"],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_split_namespace(self, val, want):
|
||||
res = split_namespace(val)
|
||||
assert res == want
|
||||
|
||||
def test_read_json_file(self):
|
||||
assert read_json_file(str(Path(__file__).parent / "../../data/ut_writer/yft_swaggerApi.json"), encoding="utf-8")
|
||||
with pytest.raises(FileNotFoundError):
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ async def test_js_parser():
|
|||
repo_parser = RepoParser(base_directory=data.path)
|
||||
symbols = repo_parser.generate_symbols()
|
||||
for s in symbols:
|
||||
await GraphRepository.update_graph_db(graph_db=graph, file_info=s)
|
||||
await GraphRepository.update_graph_db_with_file_info(graph_db=graph, file_info=s)
|
||||
data = graph.json()
|
||||
assert data
|
||||
|
||||
|
|
@ -71,11 +71,11 @@ async def test_codes():
|
|||
for file_info in symbols:
|
||||
for code_block in file_info.page_info:
|
||||
try:
|
||||
val = code_block.json(ensure_ascii=False)
|
||||
val = code_block.model_dump_json()
|
||||
assert val
|
||||
except TypeError as e:
|
||||
assert not e
|
||||
await GraphRepository.update_graph_db(graph_db=graph, file_info=file_info)
|
||||
await GraphRepository.update_graph_db_with_file_info(graph_db=graph, file_info=file_info)
|
||||
data = graph.json()
|
||||
assert data
|
||||
print(data)
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@
|
|||
@Author : alexanderwu
|
||||
@File : test_read_docx.py
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from metagpt.const import METAGPT_ROOT
|
||||
from metagpt.utils.read_document import read_docx
|
||||
|
||||
|
||||
@pytest.mark.skip # https://copyprogramming.com/howto/python-docx-error-opening-file-bad-magic-number-for-file-header-eoferror
|
||||
class TestReadDocx:
|
||||
def test_read_docx(self):
|
||||
docx_sample = METAGPT_ROOT / "tests/data/docx_for_test.docx"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
@Author : mashenquan
|
||||
@File : test_redis.py
|
||||
"""
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -12,14 +13,23 @@ from metagpt.config import CONFIG
|
|||
from metagpt.utils.redis import Redis
|
||||
|
||||
|
||||
async def async_mock_from_url(*args, **kwargs):
|
||||
mock_client = AsyncMock()
|
||||
mock_client.set.return_value = None
|
||||
mock_client.get.side_effect = [b"test", b""]
|
||||
return mock_client
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_redis():
|
||||
async def test_redis(mocker):
|
||||
# Mock
|
||||
mocker.patch("aioredis.from_url", return_value=async_mock_from_url())
|
||||
|
||||
# Prerequisites
|
||||
assert CONFIG.REDIS_HOST and CONFIG.REDIS_HOST != "YOUR_REDIS_HOST"
|
||||
assert CONFIG.REDIS_PORT and CONFIG.REDIS_PORT != "YOUR_REDIS_PORT"
|
||||
# assert CONFIG.REDIS_USER
|
||||
assert CONFIG.REDIS_PASSWORD is not None and CONFIG.REDIS_PASSWORD != "YOUR_REDIS_PASSWORD"
|
||||
assert CONFIG.REDIS_DB is not None and CONFIG.REDIS_DB != "YOUR_REDIS_DB_INDEX, str, 0-based"
|
||||
CONFIG.REDIS_HOST = "MOCK_REDIS_HOST"
|
||||
CONFIG.REDIS_PORT = "MOCK_REDIS_PORT"
|
||||
CONFIG.REDIS_PASSWORD = "MOCK_REDIS_PASSWORD"
|
||||
CONFIG.REDIS_DB = 0
|
||||
|
||||
conn = Redis()
|
||||
assert not conn.is_valid
|
||||
|
|
|
|||
|
|
@ -2,20 +2,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Desc : unittest of repair_llm_raw_output
|
||||
|
||||
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.utils.repair_llm_raw_output import (
|
||||
RepairType,
|
||||
extract_content_from_output,
|
||||
repair_invalid_json,
|
||||
repair_llm_raw_output,
|
||||
retry_parse_json_text,
|
||||
)
|
||||
|
||||
"""
|
||||
CONFIG.repair_llm_output should be True before retry_parse_json_text imported.
|
||||
so we move `from ... impot ...` into each `test_xx` to avoid `Module level import not at top of file` format warning.
|
||||
"""
|
||||
CONFIG.repair_llm_output = True
|
||||
|
||||
|
||||
def test_repair_case_sensitivity():
|
||||
from metagpt.utils.repair_llm_raw_output import repair_llm_raw_output
|
||||
|
||||
raw_output = """{
|
||||
"Original requirements": "Write a 2048 game",
|
||||
"search Information": "",
|
||||
|
|
@ -36,6 +34,8 @@ def test_repair_case_sensitivity():
|
|||
|
||||
|
||||
def test_repair_special_character_missing():
|
||||
from metagpt.utils.repair_llm_raw_output import repair_llm_raw_output
|
||||
|
||||
raw_output = """[CONTENT]
|
||||
"Anything UNCLEAR": "No unclear requirements or information."
|
||||
[CONTENT]"""
|
||||
|
|
@ -66,11 +66,12 @@ def test_repair_special_character_missing():
|
|||
target_output = '[CONTENT] {"a": "b"} [/CONTENT]'
|
||||
|
||||
output = repair_llm_raw_output(output=raw_output, req_keys=["[/CONTENT]"])
|
||||
print("output\n", output)
|
||||
assert output == target_output
|
||||
|
||||
|
||||
def test_required_key_pair_missing():
|
||||
from metagpt.utils.repair_llm_raw_output import repair_llm_raw_output
|
||||
|
||||
raw_output = '[CONTENT] {"a": "b"}'
|
||||
target_output = '[CONTENT] {"a": "b"}\n[/CONTENT]'
|
||||
|
||||
|
|
@ -107,6 +108,8 @@ xxx
|
|||
|
||||
|
||||
def test_repair_json_format():
|
||||
from metagpt.utils.repair_llm_raw_output import RepairType, repair_llm_raw_output
|
||||
|
||||
raw_output = "{ xxx }]"
|
||||
target_output = "{ xxx }"
|
||||
|
||||
|
|
@ -125,8 +128,23 @@ def test_repair_json_format():
|
|||
output = repair_llm_raw_output(output=raw_output, req_keys=[None], repair_type=RepairType.JSON)
|
||||
assert output == target_output
|
||||
|
||||
raw_output = """
|
||||
{
|
||||
"Language": "en_us", # define language
|
||||
"Programming Language": "Python"
|
||||
}
|
||||
"""
|
||||
target_output = """{
|
||||
"Language": "en_us",
|
||||
"Programming Language": "Python"
|
||||
}"""
|
||||
output = repair_llm_raw_output(output=raw_output, req_keys=[None], repair_type=RepairType.JSON)
|
||||
assert output == target_output
|
||||
|
||||
|
||||
def test_repair_invalid_json():
|
||||
from metagpt.utils.repair_llm_raw_output import repair_invalid_json
|
||||
|
||||
raw_output = """{
|
||||
"key": "value"
|
||||
},
|
||||
|
|
@ -169,6 +187,8 @@ value
|
|||
|
||||
|
||||
def test_retry_parse_json_text():
|
||||
from metagpt.utils.repair_llm_raw_output import retry_parse_json_text
|
||||
|
||||
invalid_json_text = """{
|
||||
"Original Requirements": "Create a 2048 game",
|
||||
"Competitive Quadrant Chart": "quadrantChart\n\ttitle Reach and engagement of campaigns\n\t\tx-axis"
|
||||
|
|
@ -197,6 +217,25 @@ def test_retry_parse_json_text():
|
|||
output = retry_parse_json_text(output=invalid_json_text)
|
||||
assert output == target_json
|
||||
|
||||
invalid_json_text = '''{
|
||||
"Data structures and interfaces": """
|
||||
class UI:
|
||||
- game_engine: GameEngine
|
||||
+ __init__(engine: GameEngine) -> None
|
||||
+ display_board() -> None
|
||||
+ display_score() -> None
|
||||
+ prompt_move() -> str
|
||||
+ reset_game() -> None
|
||||
"""
|
||||
"Anything UNCLEAR": "no"
|
||||
}'''
|
||||
target_json = {
|
||||
"Data structures and interfaces": "\n class UI:\n - game_engine: GameEngine\n + __init__(engine: GameEngine) -> None\n + display_board() -> None\n + display_score() -> None\n + prompt_move() -> str\n + reset_game() -> None\n ",
|
||||
"Anything UNCLEAR": "no",
|
||||
}
|
||||
output = retry_parse_json_text(output=invalid_json_text)
|
||||
assert output == target_json
|
||||
|
||||
|
||||
def test_extract_content_from_output():
|
||||
"""
|
||||
|
|
@ -205,6 +244,7 @@ def test_extract_content_from_output():
|
|||
xxx [CONTENT] xxx [CONTENT] xxxx [/CONTENT]
|
||||
xxx [CONTENT] xxxx [/CONTENT] xxx [CONTENT][/CONTENT] xxx [CONTENT][/CONTENT] # target pair is the last one
|
||||
"""
|
||||
from metagpt.utils.repair_llm_raw_output import extract_content_from_output
|
||||
|
||||
output = (
|
||||
'Sure! Here is the properly formatted JSON output based on the given context:\n\n[CONTENT]\n{\n"'
|
||||
|
|
|
|||
|
|
@ -9,20 +9,36 @@ import uuid
|
|||
from pathlib import Path
|
||||
|
||||
import aiofiles
|
||||
import mock
|
||||
import pytest
|
||||
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.utils.common import aread
|
||||
from metagpt.utils.s3 import S3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_s3():
|
||||
@mock.patch("aioboto3.Session")
|
||||
async def test_s3(mock_session_class):
|
||||
# Set up the mock response
|
||||
data = await aread(__file__, "utf-8")
|
||||
mock_session_object = mock.Mock()
|
||||
reader_mock = mock.AsyncMock()
|
||||
reader_mock.read.side_effect = [data.encode("utf-8"), b"", data.encode("utf-8")]
|
||||
type(reader_mock).url = mock.PropertyMock(return_value="https://mock")
|
||||
mock_client = mock.AsyncMock()
|
||||
mock_client.put_object.return_value = None
|
||||
mock_client.get_object.return_value = {"Body": reader_mock}
|
||||
mock_client.__aenter__.return_value = mock_client
|
||||
mock_client.__aexit__.return_value = None
|
||||
mock_session_object.client.return_value = mock_client
|
||||
mock_session_class.return_value = mock_session_object
|
||||
|
||||
# Prerequisites
|
||||
assert CONFIG.S3_ACCESS_KEY and CONFIG.S3_ACCESS_KEY != "YOUR_S3_ACCESS_KEY"
|
||||
assert CONFIG.S3_SECRET_KEY and CONFIG.S3_SECRET_KEY != "YOUR_S3_SECRET_KEY"
|
||||
assert CONFIG.S3_ENDPOINT_URL and CONFIG.S3_ENDPOINT_URL != "YOUR_S3_ENDPOINT_URL"
|
||||
# assert CONFIG.S3_SECURE: true # true/false
|
||||
assert CONFIG.S3_BUCKET and CONFIG.S3_BUCKET != "YOUR_S3_BUCKET"
|
||||
# assert CONFIG.S3_ACCESS_KEY and CONFIG.S3_ACCESS_KEY != "YOUR_S3_ACCESS_KEY"
|
||||
# assert CONFIG.S3_SECRET_KEY and CONFIG.S3_SECRET_KEY != "YOUR_S3_SECRET_KEY"
|
||||
# assert CONFIG.S3_ENDPOINT_URL and CONFIG.S3_ENDPOINT_URL != "YOUR_S3_ENDPOINT_URL"
|
||||
# assert CONFIG.S3_BUCKET and CONFIG.S3_BUCKET != "YOUR_S3_BUCKET"
|
||||
|
||||
conn = S3()
|
||||
assert conn.is_valid
|
||||
|
|
@ -42,6 +58,7 @@ async def test_s3():
|
|||
assert "http" in res
|
||||
|
||||
# Mock session env
|
||||
type(reader_mock).url = mock.PropertyMock(return_value="")
|
||||
old_options = CONFIG.options.copy()
|
||||
new_options = old_options.copy()
|
||||
new_options["S3_ACCESS_KEY"] = "YOUR_S3_ACCESS_KEY"
|
||||
|
|
@ -54,6 +71,8 @@ async def test_s3():
|
|||
finally:
|
||||
CONFIG.set_context(old_options)
|
||||
|
||||
await reader.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
13
tests/metagpt/utils/test_session.py
Normal file
13
tests/metagpt/utils/test_session.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
# _*_ coding: utf-8 _*_
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_nodeid(request):
|
||||
print(request.node.nodeid)
|
||||
assert request.node.nodeid
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
97
tests/mock/mock_llm.py
Normal file
97
tests/mock/mock_llm.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
from typing import Optional
|
||||
|
||||
from metagpt.logs import log_llm_stream, logger
|
||||
from metagpt.provider.openai_api import OpenAILLM
|
||||
|
||||
|
||||
class MockLLM(OpenAILLM):
|
||||
def __init__(self, allow_open_api_call):
|
||||
super().__init__()
|
||||
self.allow_open_api_call = allow_open_api_call
|
||||
self.rsp_cache: dict = {}
|
||||
self.rsp_candidates: list[dict] = [] # a test can have multiple calls with the same llm, thus a list
|
||||
|
||||
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
|
||||
|
||||
rsp = await self._achat_completion(messages, timeout=timeout)
|
||||
return self.get_choice_text(rsp)
|
||||
|
||||
async def original_aask(
|
||||
self,
|
||||
msg: str,
|
||||
system_msgs: Optional[list[str]] = None,
|
||||
format_msgs: Optional[list[dict[str, str]]] = None,
|
||||
timeout=3,
|
||||
stream=True,
|
||||
):
|
||||
"""A copy of metagpt.provider.base_llm.BaseLLM.aask, we can't use super().aask because it will be mocked"""
|
||||
if system_msgs:
|
||||
message = self._system_msgs(system_msgs)
|
||||
else:
|
||||
message = [self._default_system_msg()]
|
||||
if not self.use_system_prompt:
|
||||
message = []
|
||||
if format_msgs:
|
||||
message.extend(format_msgs)
|
||||
message.append(self._user_msg(msg))
|
||||
rsp = await self.acompletion_text(message, stream=stream, timeout=timeout)
|
||||
return rsp
|
||||
|
||||
async def original_aask_batch(self, msgs: list, timeout=3) -> str:
|
||||
"""A copy of metagpt.provider.base_llm.BaseLLM.aask_batch, we can't use super().aask because it will be mocked"""
|
||||
context = []
|
||||
for msg in msgs:
|
||||
umsg = self._user_msg(msg)
|
||||
context.append(umsg)
|
||||
rsp_text = await self.acompletion_text(context, timeout=timeout)
|
||||
context.append(self._assistant_msg(rsp_text))
|
||||
return self._extract_assistant_rsp(context)
|
||||
|
||||
async def aask(
|
||||
self,
|
||||
msg: str,
|
||||
system_msgs: Optional[list[str]] = None,
|
||||
format_msgs: Optional[list[dict[str, str]]] = None,
|
||||
timeout=3,
|
||||
stream=True,
|
||||
) -> str:
|
||||
msg_key = msg # used to identify it a message has been called before
|
||||
if system_msgs:
|
||||
joined_system_msg = "#MSG_SEP#".join(system_msgs) + "#SYSTEM_MSG_END#"
|
||||
msg_key = joined_system_msg + msg_key
|
||||
rsp = await self._mock_rsp(msg_key, self.original_aask, msg, system_msgs, format_msgs, timeout, stream)
|
||||
return rsp
|
||||
|
||||
async def aask_batch(self, msgs: list, timeout=3) -> str:
|
||||
msg_key = "#MSG_SEP#".join([msg if isinstance(msg, str) else msg.content for msg in msgs])
|
||||
rsp = await self._mock_rsp(msg_key, self.original_aask_batch, msgs, timeout)
|
||||
return rsp
|
||||
|
||||
async def _mock_rsp(self, msg_key, ask_func, *args, **kwargs):
|
||||
if msg_key not in self.rsp_cache:
|
||||
if not self.allow_open_api_call:
|
||||
raise ValueError(
|
||||
"In current test setting, api call is not allowed, you should properly mock your tests, "
|
||||
"or add expected api response in tests/data/rsp_cache.json. "
|
||||
f"The prompt you want for api call: {msg_key}"
|
||||
)
|
||||
# Call the original unmocked method
|
||||
rsp = await ask_func(*args, **kwargs)
|
||||
else:
|
||||
logger.warning("Use response cache")
|
||||
rsp = self.rsp_cache[msg_key]
|
||||
self.rsp_candidates.append({msg_key: rsp})
|
||||
return rsp
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
python -m pip install --upgrade pip
|
||||
pip install -e .[test]
|
||||
npm install -g @mermaid-js/mermaid-cli
|
||||
playwright install --with-deps chromium
|
||||
playwright install --with-deps
|
||||
Loading…
Add table
Add a link
Reference in a new issue