Skip to content

Commit

Permalink
feat: merge geekan:main
Browse files Browse the repository at this point in the history
  • Loading branch information
莘权 马 committed Dec 8, 2023
1 parent 769dc82 commit e677de8
Show file tree
Hide file tree
Showing 14 changed files with 251 additions and 18 deletions.
1 change: 1 addition & 0 deletions examples/agent_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def parse_code(rsp):
pattern = r"```python(.*)```"
match = re.search(pattern, rsp, re.DOTALL)
code_text = match.group(1) if match else ""
CONFIG.workspace_path.mkdir(parents=True, exist_ok=True)
with open(CONFIG.workspace_path / "agent_created_agent.py", "w") as f:
f.write(code_text)
return code_text
Expand Down
22 changes: 20 additions & 2 deletions examples/search_kb.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,35 @@
"""
import asyncio

from metagpt.actions import Action
from metagpt.const import DATA_PATH
from metagpt.document_store import FaissStore
from metagpt.logs import logger
from metagpt.roles import Sales
from metagpt.schema import Message

""" example.json, e.g.
[
{
"source": "Which facial cleanser is good for oily skin?",
"output": "ABC cleanser is preferred by many with oily skin."
},
{
"source": "Is L'Oreal good to use?",
"output": "L'Oreal is a popular brand with many positive reviews."
}
]
"""


async def search():
store = FaissStore(DATA_PATH / "example.json")
role = Sales(profile="Sales", store=store)

queries = ["Which facial cleanser is good for oily skin?", "Is L'Oreal good to use?"]
role._watch({Action})
queries = [
Message("Which facial cleanser is good for oily skin?", cause_by=Action),
Message("Is L'Oreal good to use?", cause_by=Action),
]
for query in queries:
logger.info(f"User: {query}")
result = await role.run(query)
Expand Down
4 changes: 2 additions & 2 deletions metagpt/actions/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from abc import ABC
from typing import Optional

from tenacity import retry, stop_after_attempt, wait_fixed
from tenacity import retry, stop_after_attempt, wait_random_exponential

from metagpt.actions.action_output import ActionOutput
from metagpt.llm import LLM
Expand Down Expand Up @@ -53,7 +53,7 @@ async def _aask(self, prompt: str, system_msgs: Optional[list[str]] = None) -> s
system_msgs.append(self.prefix)
return await self.llm.aask(prompt, system_msgs)

@retry(stop=stop_after_attempt(3), wait=wait_fixed(1))
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
async def _aask_v1(
self,
prompt: str,
Expand Down
4 changes: 2 additions & 2 deletions metagpt/actions/summarize_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"""
from pathlib import Path

from tenacity import retry, stop_after_attempt, wait_fixed
from tenacity import retry, stop_after_attempt, wait_random_exponential

from metagpt.actions.action import Action
from metagpt.config import CONFIG
Expand Down Expand Up @@ -92,7 +92,7 @@ class SummarizeCode(Action):
def __init__(self, name="SummarizeCode", context=None, llm=None):
super().__init__(name, context, llm)

@retry(stop=stop_after_attempt(2), wait=wait_fixed(1))
@retry(stop=stop_after_attempt(2), wait=wait_random_exponential(min=1, max=60))
async def summarize_code(self, prompt):
code_rsp = await self._aask(prompt)
return code_rsp
Expand Down
4 changes: 2 additions & 2 deletions metagpt/actions/write_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
RunCodeResult to standardize and unify parameter passing between WriteCode, RunCode, and DebugError.
"""

from tenacity import retry, stop_after_attempt, wait_fixed
from tenacity import retry, stop_after_attempt, wait_random_exponential

from metagpt.actions.action import Action
from metagpt.config import CONFIG
Expand Down Expand Up @@ -81,7 +81,7 @@ class WriteCode(Action):
def __init__(self, name="WriteCode", context=None, llm=None):
super().__init__(name, context, llm)

@retry(stop=stop_after_attempt(2), wait=wait_fixed(1))
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
async def write_code(self, prompt) -> str:
code_rsp = await self._aask(prompt)
code = CodeParser.parse_code(block="", text=code_rsp)
Expand Down
4 changes: 2 additions & 2 deletions metagpt/actions/write_code_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
WriteCode object, rather than passing them in when calling the run function.
"""

from tenacity import retry, stop_after_attempt, wait_fixed
from tenacity import retry, stop_after_attempt

from metagpt.actions.action import Action
from metagpt.config import CONFIG
Expand Down Expand Up @@ -94,7 +94,7 @@ class WriteCodeReview(Action):
def __init__(self, name="WriteCodeReview", context=None, llm=None):
super().__init__(name, context, llm)

@retry(stop=stop_after_attempt(2), wait=wait_fixed(1))
@retry(stop=stop_after_attempt(2), wait=wait_random_exponential(min=1, max=60))
async def write_code_review_and_rewrite(self, prompt):
code_rsp = await self._aask(prompt)
result = CodeParser.parse_block("Code Review Result", code_rsp)
Expand Down
6 changes: 3 additions & 3 deletions metagpt/provider/openai_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
retry,
retry_if_exception_type,
stop_after_attempt,
wait_fixed,
wait_random_exponential,
)

from metagpt.config import CONFIG
Expand Down Expand Up @@ -231,8 +231,8 @@ async def acompletion(self, messages: list[dict]) -> dict:
return await self._achat_completion(messages)

@retry(
stop=stop_after_attempt(3),
wait=wait_fixed(1),
wait=wait_random_exponential(min=1, max=60),
stop=stop_after_attempt(6),
after=after_log(logger, logger.level("WARNING").name),
retry=retry_if_exception_type(APIConnectionError),
retry_error_callback=log_and_reraise,
Expand Down
4 changes: 2 additions & 2 deletions metagpt/provider/zhipuai_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
retry,
retry_if_exception_type,
stop_after_attempt,
wait_fixed,
wait_random_exponential,
)

from metagpt.config import CONFIG
Expand Down Expand Up @@ -122,7 +122,7 @@ async def _achat_completion_stream(self, messages: list[dict]) -> str:

@retry(
stop=stop_after_attempt(3),
wait=wait_fixed(1),
wait=wait_random_exponential(min=1, max=60),
after=after_log(logger, logger.level("WARNING").name),
retry=retry_if_exception_type(ConnectionError),
retry_error_callback=log_and_reraise,
Expand Down
2 changes: 1 addition & 1 deletion metagpt/roles/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from metagpt.roles.product_manager import ProductManager
from metagpt.roles.engineer import Engineer
from metagpt.roles.qa_engineer import QaEngineer
from metagpt.roles.seacher import Searcher
from metagpt.roles.searcher import Searcher
from metagpt.roles.sales import Sales
from metagpt.roles.customer_service import CustomerService

Expand Down
2 changes: 1 addition & 1 deletion metagpt/roles/sales.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(

def _set_store(self, store):
if store:
action = SearchAndSummarize("", engine=SearchEngineType.CUSTOM_ENGINE, search_func=store.search)
action = SearchAndSummarize("", engine=SearchEngineType.CUSTOM_ENGINE, search_func=store.asearch)
else:
action = SearchAndSummarize()
self._init_actions([action])
2 changes: 1 addition & 1 deletion metagpt/roles/seacher.py → metagpt/roles/searcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
@Time : 2023/5/23 17:25
@Author : alexanderwu
@File : seacher.py
@File : searcher.py
@Modified By: mashenquan, 2023-11-1. According to Chapter 2.2.1 and 2.2.2 of RFC 116, change the data type of
the `cause_by` value in the `Message` to a string to support the new message distribution feature.
"""
Expand Down
101 changes: 101 additions & 0 deletions metagpt/subscription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import asyncio
from typing import AsyncGenerator, Awaitable, Callable

from pydantic import BaseModel, Field

from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message


class SubscriptionRunner(BaseModel):
"""A simple wrapper to manage subscription tasks for different roles using asyncio.
Example:
>>> import asyncio
>>> from metagpt.subscription import SubscriptionRunner
>>> from metagpt.roles import Searcher
>>> from metagpt.schema import Message
>>> async def trigger():
... while True:
... yield Message("the latest news about OpenAI")
... await asyncio.sleep(3600 * 24)
>>> async def callback(msg: Message):
... print(msg.content)
>>> async def main():
... pb = SubscriptionRunner()
... await pb.subscribe(Searcher(), trigger(), callback)
... await pb.run()
>>> asyncio.run(main())
"""

tasks: dict[Role, asyncio.Task] = Field(default_factory=dict)

class Config:
arbitrary_types_allowed = True

async def subscribe(
self,
role: Role,
trigger: AsyncGenerator[Message, None],
callback: Callable[
[
Message,
],
Awaitable[None],
],
):
"""Subscribes a role to a trigger and sets up a callback to be called with the role's response.
Args:
role: The role to subscribe.
trigger: An asynchronous generator that yields Messages to be processed by the role.
callback: An asynchronous function to be called with the response from the role.
"""
loop = asyncio.get_running_loop()

async def _start_role():
async for msg in trigger:
resp = await role.run(msg)
await callback(resp)

self.tasks[role] = loop.create_task(_start_role(), name=f"Subscription-{role}")

async def unsubscribe(self, role: Role):
"""Unsubscribes a role from its trigger and cancels the associated task.
Args:
role: The role to unsubscribe.
"""
task = self.tasks.pop(role)
task.cancel()

async def run(self, raise_exception: bool = True):
"""Runs all subscribed tasks and handles their completion or exception.
Args:
raise_exception: _description_. Defaults to True.
Raises:
task.exception: _description_
"""
while True:
for role, task in self.tasks.items():
if task.done():
if task.exception():
if raise_exception:
raise task.exception()
logger.opt(exception=task.exception()).error(f"Task {task.get_name()} run error")
else:
logger.warning(
f"Task {task.get_name()} has completed. "
"If this is unexpected behavior, please check the trigger function."
)
self.tasks.pop(role)
break
else:
await asyncio.sleep(1)
11 changes: 11 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ async def handle_client(reader, writer):
return "http://{}:{}".format(*server.sockets[0].getsockname())


# see https://github.com/Delgan/loguru/issues/59#issuecomment-466591978
@pytest.fixture
def loguru_caplog(caplog):
class PropogateHandler(logging.Handler):
def emit(self, record):
logging.getLogger(record.name).handle(record)

logger.add(PropogateHandler(), format="{message}")
yield caplog


# init & dispose git repo
@pytest.fixture(scope="session", autouse=True)
def setup_and_teardown_git_repo(request):
Expand Down
Loading

0 comments on commit e677de8

Please sign in to comment.