diff --git a/examples/agent_creator.py b/examples/agent_creator.py index dbb6f735a..d4d7de3be 100644 --- a/examples/agent_creator.py +++ b/examples/agent_creator.py @@ -55,16 +55,13 @@ class CreateAgent(Action): class AgentCreator(Role): - def __init__( - self, - name: str = "Matrix", - profile: str = "AgentCreator", - agent_template: str = MULTI_ACTION_AGENT_CODE_EXAMPLE, - **kwargs, - ): - super().__init__(name, profile, **kwargs) + name: str = "Matrix" + profile: str = "AgentCreator" + agent_template: str = MULTI_ACTION_AGENT_CODE_EXAMPLE + + def __init__(self, **kwargs): + super().__init__(**kwargs) self._init_actions([CreateAgent]) - self.agent_template = agent_template async def _act(self) -> Message: logger.info(f"{self._setting}: to do {self._rc.todo}({self._rc.todo.name})") @@ -86,10 +83,6 @@ if __name__ == "__main__": creator = AgentCreator(agent_template=agent_template) - # msg = """Write an agent called SimpleTester that will take any code snippet (str) - # and return a testing code (str) for testing - # the given code snippet. Use pytest as the testing framework.""" - msg = """ Write an agent called SimpleTester that will take any code snippet (str) and do the following: 1. write a testing code (str) for testing the given code snippet, save the testing code as a .py file in the current working directory; diff --git a/examples/build_customized_agent.py b/examples/build_customized_agent.py index ccdf7748a..7a7fa6b56 100644 --- a/examples/build_customized_agent.py +++ b/examples/build_customized_agent.py @@ -10,9 +10,8 @@ import subprocess import fire from metagpt.actions import Action -from metagpt.llm import LLM from metagpt.logs import logger -from metagpt.roles import Role +from metagpt.roles.role import Role, RoleReactMode from metagpt.schema import Message @@ -23,8 +22,7 @@ class SimpleWriteCode(Action): your code: """ - def __init__(self, name: str = "SimpleWriteCode", context=None, llm: LLM = None): - super().__init__(name, context, llm) + name: str = "SimpleWriteCode" async def run(self, instruction: str): prompt = self.PROMPT_TEMPLATE.format(instruction=instruction) @@ -44,8 +42,7 @@ class SimpleWriteCode(Action): class SimpleRunCode(Action): - def __init__(self, name: str = "SimpleRunCode", context=None, llm: LLM = None): - super().__init__(name, context, llm) + name: str = "SimpleRunCode" async def run(self, code_text: str): result = subprocess.run(["python3", "-c", code_text], capture_output=True, text=True) @@ -55,13 +52,11 @@ class SimpleRunCode(Action): class SimpleCoder(Role): - def __init__( - self, - name: str = "Alice", - profile: str = "SimpleCoder", - **kwargs, - ): - super().__init__(name, profile, **kwargs) + name: str = "Alice" + profile: str = "SimpleCoder" + + def __init__(self, **kwargs): + super().__init__(**kwargs) self._init_actions([SimpleWriteCode]) async def _act(self) -> Message: @@ -76,15 +71,13 @@ class SimpleCoder(Role): class RunnableCoder(Role): - def __init__( - self, - name: str = "Alice", - profile: str = "RunnableCoder", - **kwargs, - ): - super().__init__(name, profile, **kwargs) + name: str = "Alice" + profile: str = "RunnableCoder" + + def __init__(self, **kwargs): + super().__init__(**kwargs) self._init_actions([SimpleWriteCode, SimpleRunCode]) - self._set_react_mode(react_mode="by_order") + self._set_react_mode(react_mode=RoleReactMode.BY_ORDER.value) async def _act(self) -> Message: logger.info(f"{self._setting}: to do {self._rc.todo}({self._rc.todo.name})") diff --git a/examples/build_customized_multi_agents.py b/examples/build_customized_multi_agents.py index 16c36e08d..70ad71c6b 100644 --- a/examples/build_customized_multi_agents.py +++ b/examples/build_customized_multi_agents.py @@ -8,7 +8,6 @@ import re import fire from metagpt.actions import Action, UserRequirement -from metagpt.llm import LLM from metagpt.logs import logger from metagpt.roles import Role from metagpt.schema import Message @@ -28,9 +27,7 @@ class SimpleWriteCode(Action): Return ```python your_code_here ``` with NO other texts, your code: """ - - def __init__(self, name: str = "SimpleWriteCode", context=None, llm: LLM = None): - super().__init__(name, context, llm) + name: str = "SimpleWriteCode" async def run(self, instruction: str): prompt = self.PROMPT_TEMPLATE.format(instruction=instruction) @@ -43,13 +40,11 @@ class SimpleWriteCode(Action): class SimpleCoder(Role): - def __init__( - self, - name: str = "Alice", - profile: str = "SimpleCoder", - **kwargs, - ): - super().__init__(name, profile, **kwargs) + name: str = "Alice" + profile: str = "SimpleCoder" + + def __init__(self, **kwargs): + super().__init__(**kwargs) self._watch([UserRequirement]) self._init_actions([SimpleWriteCode]) @@ -62,8 +57,7 @@ class SimpleWriteTest(Action): your code: """ - def __init__(self, name: str = "SimpleWriteTest", context=None, llm: LLM = None): - super().__init__(name, context, llm) + name: str = "SimpleWriteTest" async def run(self, context: str, k: int = 3): prompt = self.PROMPT_TEMPLATE.format(context=context, k=k) @@ -76,13 +70,11 @@ class SimpleWriteTest(Action): class SimpleTester(Role): - def __init__( - self, - name: str = "Bob", - profile: str = "SimpleTester", - **kwargs, - ): - super().__init__(name, profile, **kwargs) + name: str = "Bob" + profile: str = "SimpleTester" + + def __init__(self, **kwargs): + super().__init__(**kwargs) self._init_actions([SimpleWriteTest]) # self._watch([SimpleWriteCode]) self._watch([SimpleWriteCode, SimpleWriteReview]) # feel free to try this too @@ -106,8 +98,7 @@ class SimpleWriteReview(Action): Review the test cases and provide one critical comments: """ - def __init__(self, name: str = "SimpleWriteReview", context=None, llm: LLM = None): - super().__init__(name, context, llm) + name: str = "SimpleWriteReview" async def run(self, context: str): prompt = self.PROMPT_TEMPLATE.format(context=context) @@ -118,13 +109,11 @@ class SimpleWriteReview(Action): class SimpleReviewer(Role): - def __init__( - self, - name: str = "Charlie", - profile: str = "SimpleReviewer", - **kwargs, - ): - super().__init__(name, profile, **kwargs) + name: str = "Charlie" + profile: str = "SimpleReviewer" + + def __init__(self, **kwargs): + super().__init__(**kwargs) self._init_actions([SimpleWriteReview]) self._watch([SimpleWriteTest]) @@ -147,7 +136,7 @@ async def main( ) team.invest(investment=investment) - team.start_project(idea) + team.run_project(idea) await team.run(n_round=n_round) diff --git a/examples/invoice_ocr.py b/examples/invoice_ocr.py index a6e565772..d9a2e8a6d 100644 --- a/examples/invoice_ocr.py +++ b/examples/invoice_ocr.py @@ -10,7 +10,7 @@ import asyncio from pathlib import Path -from metagpt.roles.invoice_ocr_assistant import InvoiceOCRAssistant +from metagpt.roles.invoice_ocr_assistant import InvoiceOCRAssistant, InvoicePath from metagpt.schema import Message @@ -26,7 +26,7 @@ async def main(): for path in absolute_file_paths: role = InvoiceOCRAssistant() - await role.run(Message(content="Invoicing date", instruct_content={"file_path": path})) + await role.run(Message(content="Invoicing date", instruct_content=InvoicePath(file_path=path))) if __name__ == "__main__": diff --git a/examples/search_with_specific_engine.py b/examples/search_with_specific_engine.py index 923f538ed..1a217fdf2 100644 --- a/examples/search_with_specific_engine.py +++ b/examples/search_with_specific_engine.py @@ -9,9 +9,9 @@ async def main(): # Serper API # await Searcher(engine=SearchEngineType.SERPER_GOOGLE).run(question) # SerpAPI - # await Searcher(engine=SearchEngineType.SERPAPI_GOOGLE).run(question) + await Searcher(engine=SearchEngineType.SERPAPI_GOOGLE).run(question) # Google API - await Searcher(engine=SearchEngineType.DIRECT_GOOGLE).run(question) + # await Searcher(engine=SearchEngineType.DIRECT_GOOGLE).run(question) if __name__ == "__main__": diff --git a/metagpt/actions/clone_function.py b/metagpt/actions/clone_function.py index 1447e8dbf..24d584515 100644 --- a/metagpt/actions/clone_function.py +++ b/metagpt/actions/clone_function.py @@ -1,8 +1,12 @@ import traceback from pathlib import Path +from pydantic import Field + from metagpt.actions.write_code import WriteCode +from metagpt.llm import LLM from metagpt.logs import logger +from metagpt.provider.base_gpt_api import BaseGPTAPI from metagpt.schema import Message from metagpt.utils.highlight import highlight @@ -27,8 +31,9 @@ def run(*args) -> pd.DataFrame: class CloneFunction(WriteCode): - def __init__(self, name="CloneFunction", context: list[Message] = None, llm=None): - super().__init__(name, context, llm) + name: str = "CloneFunction" + context: list[Message] = [] + llm: BaseGPTAPI = Field(default_factory=LLM) def _save(self, code_path, code): if isinstance(code_path, str): diff --git a/metagpt/actions/design_api_review.py b/metagpt/actions/design_api_review.py index 7f25bb9a3..0ff522fe8 100644 --- a/metagpt/actions/design_api_review.py +++ b/metagpt/actions/design_api_review.py @@ -5,12 +5,20 @@ @Author : alexanderwu @File : design_api_review.py """ + +from typing import Optional + +from pydantic import Field + from metagpt.actions.action import Action +from metagpt.llm import LLM +from metagpt.provider.base_gpt_api import BaseGPTAPI class DesignReview(Action): - def __init__(self, name, context=None, llm=None): - super().__init__(name, context, llm) + name: str = "DesignReview" + context: Optional[str] = None + llm: BaseGPTAPI = Field(default_factory=LLM) async def run(self, prd, api_design): prompt = ( diff --git a/metagpt/actions/execute_task.py b/metagpt/actions/execute_task.py index afdeda323..8d4e569b4 100644 --- a/metagpt/actions/execute_task.py +++ b/metagpt/actions/execute_task.py @@ -5,13 +5,19 @@ @Author : femto Zheng @File : execute_task.py """ + +from pydantic import Field + from metagpt.actions import Action +from metagpt.llm import LLM +from metagpt.provider.base_gpt_api import BaseGPTAPI from metagpt.schema import Message class ExecuteTask(Action): - def __init__(self, name="ExecuteTask", context: list[Message] = None, llm=None): - super().__init__(name, context, llm) + name: str = "ExecuteTask" + context: list[Message] = [] + llm: BaseGPTAPI = Field(default_factory=LLM) def run(self, *args, **kwargs): pass diff --git a/metagpt/actions/generate_questions.py b/metagpt/actions/generate_questions.py index c38c463bc..8573708f2 100644 --- a/metagpt/actions/generate_questions.py +++ b/metagpt/actions/generate_questions.py @@ -21,5 +21,7 @@ class GenerateQuestions(Action): """This class allows LLM to further mine noteworthy details based on specific "##TOPIC"(discussion topic) and "##RECORD" (discussion records), thereby deepening the discussion.""" + name: str = "GenerateQuestions" + async def run(self, context): return await QUESTIONS.fill(context=context, llm=self.llm) diff --git a/metagpt/actions/invoice_ocr.py b/metagpt/actions/invoice_ocr.py index dcf537a58..87f81371e 100644 --- a/metagpt/actions/invoice_ocr.py +++ b/metagpt/actions/invoice_ocr.py @@ -12,17 +12,21 @@ import os import zipfile from datetime import datetime from pathlib import Path +from typing import Optional import pandas as pd from paddleocr import PaddleOCR +from pydantic import Field from metagpt.actions import Action from metagpt.const import INVOICE_OCR_TABLE_PATH +from metagpt.llm import LLM from metagpt.logs import logger from metagpt.prompts.invoice_ocr import ( EXTRACT_OCR_MAIN_INFO_PROMPT, REPLY_OCR_QUESTION_PROMPT, ) +from metagpt.provider.base_gpt_api import BaseGPTAPI from metagpt.utils.common import OutputParser from metagpt.utils.file import File @@ -36,8 +40,9 @@ class InvoiceOCR(Action): """ - def __init__(self, name: str = "", *args, **kwargs): - super().__init__(name, *args, **kwargs) + name: str = "InvoiceOCR" + context: Optional[str] = None + llm: BaseGPTAPI = Field(default_factory=LLM) @staticmethod async def _check_file_type(file_path: Path) -> str: @@ -125,9 +130,10 @@ class GenerateTable(Action): """ - def __init__(self, name: str = "", language: str = "ch", *args, **kwargs): - super().__init__(name, *args, **kwargs) - self.language = language + name: str = "GenerateTable" + context: Optional[str] = None + llm: BaseGPTAPI = Field(default_factory=LLM) + language: str = "ch" async def run(self, ocr_results: list, filename: str, *args, **kwargs) -> dict[str, str]: """Processes OCR results, extracts invoice information, generates a table, and saves it as an Excel file. @@ -169,9 +175,10 @@ class ReplyQuestion(Action): """ - def __init__(self, name: str = "", language: str = "ch", *args, **kwargs): - super().__init__(name, *args, **kwargs) - self.language = language + name: str = "ReplyQuestion" + context: Optional[str] = None + llm: BaseGPTAPI = Field(default_factory=LLM) + language: str = "ch" async def run(self, query: str, ocr_result: list, *args, **kwargs) -> str: """Reply to questions based on ocr results. diff --git a/metagpt/actions/prepare_interview.py b/metagpt/actions/prepare_interview.py index 7ed42d590..04cc954d2 100644 --- a/metagpt/actions/prepare_interview.py +++ b/metagpt/actions/prepare_interview.py @@ -19,5 +19,7 @@ Attention: Provide as markdown block as the format above, at least 10 questions. class PrepareInterview(Action): + name: str = "PrepareInterview" + async def run(self, context): return await QUESTIONS.fill(context=context, llm=self.llm) diff --git a/metagpt/actions/research.py b/metagpt/actions/research.py index a70038c51..074cdee0a 100644 --- a/metagpt/actions/research.py +++ b/metagpt/actions/research.py @@ -3,13 +3,15 @@ from __future__ import annotations import asyncio -from typing import Callable +from typing import Callable, Optional, Union -from pydantic import parse_obj_as +from pydantic import Field, parse_obj_as from metagpt.actions import Action from metagpt.config import CONFIG +from metagpt.llm import LLM from metagpt.logs import logger +from metagpt.provider.base_gpt_api import BaseGPTAPI from metagpt.tools.search_engine import SearchEngine from metagpt.tools.web_browser_engine import WebBrowserEngine, WebBrowserEngineType from metagpt.utils.common import OutputParser @@ -78,17 +80,12 @@ above. The report must meet the following requirements: class CollectLinks(Action): """Action class to collect links from a search engine.""" - def __init__( - self, - name: str = "", - *args, - rank_func: Callable[[list[str]], None] | None = None, - **kwargs, - ): - super().__init__(name, *args, **kwargs) - self.desc = "Collect links from a search engine." - self.search_engine = SearchEngine() - self.rank_func = rank_func + name: str = "CollectLinks" + context: Optional[str] = None + llm: BaseGPTAPI = Field(default_factory=LLM) + desc: str = "Collect links from a search engine." + search_engine: SearchEngine = Field(default_factory=SearchEngine) + rank_func: Union[Callable[[list[str]], None], None] = None async def run( self, @@ -178,20 +175,20 @@ class CollectLinks(Action): class WebBrowseAndSummarize(Action): """Action class to explore the web and provide summaries of articles and webpages.""" - def __init__( - self, - *args, - browse_func: Callable[[list[str]], None] | None = None, - **kwargs, - ): - super().__init__(*args, **kwargs) + name: str = "WebBrowseAndSummarize" + context: Optional[str] = None + llm: BaseGPTAPI = Field(default_factory=LLM) + desc: str = "Explore the web and provide summaries of articles and webpages." + browse_func: Union[Callable[[list[str]], None], None] = None + web_browser_engine: WebBrowserEngine = WebBrowserEngine( + engine=WebBrowserEngineType.CUSTOM if browse_func else None, + run_func=browse_func, + ) + + def __init__(self, **kwargs): + super().__init__(**kwargs) if CONFIG.model_for_researcher_summary: self.llm.model = CONFIG.model_for_researcher_summary - self.web_browser_engine = WebBrowserEngine( - engine=WebBrowserEngineType.CUSTOM if browse_func else None, - run_func=browse_func, - ) - self.desc = "Explore the web and provide summaries of articles and webpages." async def run( self, @@ -247,8 +244,12 @@ class WebBrowseAndSummarize(Action): class ConductResearch(Action): """Action class to conduct research and generate a research report.""" - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + name: str = "ConductResearch" + context: Optional[str] = None + llm: BaseGPTAPI = Field(default_factory=LLM) + + def __init__(self, **kwargs): + super().__init__(**kwargs) if CONFIG.model_for_researcher_report: self.llm.model = CONFIG.model_for_researcher_report diff --git a/metagpt/actions/write_docstring.py b/metagpt/actions/write_docstring.py index 0ad134157..1c27a9433 100644 --- a/metagpt/actions/write_docstring.py +++ b/metagpt/actions/write_docstring.py @@ -22,9 +22,13 @@ This script uses the 'fire' library to create a command-line interface. It gener the specified docstring style and adds them to the code. """ import ast -from typing import Literal +from typing import Literal, Optional + +from pydantic import Field from metagpt.actions.action import Action +from metagpt.llm import LLM +from metagpt.provider.base_gpt_api import BaseGPTAPI from metagpt.utils.common import OutputParser from metagpt.utils.pycst import merge_docstring @@ -157,9 +161,9 @@ class WriteDocstring(Action): desc: A string describing the action. """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.desc = "Write docstring for code." + desc: str = "Write docstring for code." + context: Optional[str] = None + llm: BaseGPTAPI = Field(default_factory=LLM) async def run( self, diff --git a/metagpt/actions/write_review.py b/metagpt/actions/write_review.py index 8a4856317..646f44aeb 100644 --- a/metagpt/actions/write_review.py +++ b/metagpt/actions/write_review.py @@ -6,8 +6,12 @@ """ from typing import List +from pydantic import Field + from metagpt.actions import Action from metagpt.actions.action_node import ActionNode +from metagpt.llm import LLM +from metagpt.provider.base_gpt_api import BaseGPTAPI REVIEW = ActionNode( key="Review", @@ -33,5 +37,8 @@ WRITE_REVIEW_NODE = ActionNode.from_children("WRITE_REVIEW_NODE", [REVIEW, LGTM] class WriteReview(Action): """Write a review for the given context.""" + name: str = "WriteReview" + llm: BaseGPTAPI = Field(default_factory=LLM) + async def run(self, context): return await WRITE_REVIEW_NODE.fill(context=context, llm=self.llm, schema="json") diff --git a/metagpt/actions/write_tutorial.py b/metagpt/actions/write_tutorial.py index d41915de3..f33a6b114 100644 --- a/metagpt/actions/write_tutorial.py +++ b/metagpt/actions/write_tutorial.py @@ -9,8 +9,12 @@ from typing import Dict +from pydantic import Field + from metagpt.actions import Action +from metagpt.llm import LLM from metagpt.prompts.tutorial_assistant import CONTENT_PROMPT, DIRECTORY_PROMPT +from metagpt.provider.base_gpt_api import BaseGPTAPI from metagpt.utils.common import OutputParser @@ -22,9 +26,9 @@ class WriteDirectory(Action): language: The language to output, default is "Chinese". """ - def __init__(self, name: str = "", language: str = "Chinese", *args, **kwargs): - super().__init__(name, *args, **kwargs) - self.language = language + name: str = "WriteDirectory" + llm: BaseGPTAPI = Field(default_factory=LLM) + language: str = "Chinese" async def run(self, topic: str, *args, **kwargs) -> Dict: """Execute the action to generate a tutorial directory according to the topic. @@ -49,10 +53,10 @@ class WriteContent(Action): language: The language to output, default is "Chinese". """ - def __init__(self, name: str = "", directory: str = "", language: str = "Chinese", *args, **kwargs): - super().__init__(name, *args, **kwargs) - self.language = language - self.directory = directory + name: str = "WriteContent" + llm: BaseGPTAPI = Field(default_factory=LLM) + directory: dict = dict() + language: str = "Chinese" async def run(self, topic: str, *args, **kwargs) -> str: """Execute the action to write document content according to the directory and topic. diff --git a/metagpt/roles/customer_service.py b/metagpt/roles/customer_service.py index 777f62731..c7baa697d 100644 --- a/metagpt/roles/customer_service.py +++ b/metagpt/roles/customer_service.py @@ -29,8 +29,4 @@ class CustomerService(Sales): name: str = "Xiaomei" profile: str = "Human customer service" desc: str = DESC - store: Optional[str] = None - - def __init__(self, **kwargs): - super().__init__(**kwargs) diff --git a/metagpt/roles/invoice_ocr_assistant.py b/metagpt/roles/invoice_ocr_assistant.py index bf8fc454e..3349a498f 100644 --- a/metagpt/roles/invoice_ocr_assistant.py +++ b/metagpt/roles/invoice_ocr_assistant.py @@ -7,14 +7,35 @@ @File : invoice_ocr_assistant.py """ +import json +from pathlib import Path +from typing import Optional + import pandas as pd +from pydantic import BaseModel from metagpt.actions.invoice_ocr import GenerateTable, InvoiceOCR, ReplyQuestion from metagpt.prompts.invoice_ocr import INVOICE_OCR_SUCCESS -from metagpt.roles import Role +from metagpt.roles.role import Role, RoleReactMode from metagpt.schema import Message +class InvoicePath(BaseModel): + file_path: Path = "" + + +class OCRResults(BaseModel): + ocr_result: str = "[]" + + +class InvoiceData(BaseModel): + invoice_data: list[dict] = [] + + +class ReplyData(BaseModel): + content: str = "" + + class InvoiceOCRAssistant(Role): """Invoice OCR assistant, support OCR text recognition of invoice PDF, png, jpg, and zip files, generate a table for the payee, city, total amount, and invoicing date of the invoice, @@ -28,21 +49,19 @@ class InvoiceOCRAssistant(Role): language: The language in which the invoice table will be generated. """ - def __init__( - self, - name: str = "Stitch", - profile: str = "Invoice OCR Assistant", - goal: str = "OCR identifies invoice files and generates invoice main information table", - constraints: str = "", - language: str = "ch", - ): - super().__init__(name, profile, goal, constraints) + name: str = "Stitch" + profile: str = "Invoice OCR Assistant" + goal: str = "OCR identifies invoice files and generates invoice main information table" + constraints: str = "" + language: str = "ch" + filename: str = "" + origin_query: str = "" + orc_data: Optional[list] = None + + def __init__(self, **kwargs): + super().__init__(**kwargs) self._init_actions([InvoiceOCR]) - self.language = language - self.filename = "" - self.origin_query = "" - self.orc_data = None - self._set_react_mode(react_mode="by_order") + self._set_react_mode(react_mode=RoleReactMode.BY_ORDER.value) async def _act(self) -> Message: """Perform an action as determined by the role. @@ -54,7 +73,8 @@ class InvoiceOCRAssistant(Role): todo = self._rc.todo if isinstance(todo, InvoiceOCR): self.origin_query = msg.content - file_path = msg.instruct_content.get("file_path") + invoice_path: InvoicePath = msg.instruct_content + file_path = invoice_path.file_path self.filename = file_path.name if not file_path: raise Exception("Invoice file not uploaded") @@ -69,17 +89,23 @@ class InvoiceOCRAssistant(Role): self._rc.todo = None content = INVOICE_OCR_SUCCESS + resp = OCRResults(ocr_result=json.dumps(resp)) + msg = Message(content=content, instruct_content=resp) + self._rc.memory.add(msg) + return await super().react() elif isinstance(todo, GenerateTable): - ocr_results = msg.instruct_content - resp = await todo.run(ocr_results, self.filename) + ocr_results: OCRResults = msg.instruct_content + resp = await todo.run(json.loads(ocr_results.ocr_result), self.filename) # Convert list to Markdown format string df = pd.DataFrame(resp) markdown_table = df.to_markdown(index=False) content = f"{markdown_table}\n\n\n" + resp = InvoiceData(invoice_data=resp) else: resp = await todo.run(self.origin_query, self.orc_data) content = resp + resp = ReplyData(content=resp) msg = Message(content=content, instruct_content=resp) self._rc.memory.add(msg) diff --git a/metagpt/roles/researcher.py b/metagpt/roles/researcher.py index 456e8baba..27f046878 100644 --- a/metagpt/roles/researcher.py +++ b/metagpt/roles/researcher.py @@ -4,7 +4,6 @@ the `cause_by` value in the `Message` to a string to support the new message distribution feature. """ - import asyncio from pydantic import BaseModel @@ -13,7 +12,7 @@ from metagpt.actions import Action, CollectLinks, ConductResearch, WebBrowseAndS from metagpt.actions.research import get_research_system_text from metagpt.const import RESEARCH_PATH from metagpt.logs import logger -from metagpt.roles import Role +from metagpt.roles.role import Role, RoleReactMode from metagpt.schema import Message @@ -25,21 +24,20 @@ class Report(BaseModel): class Researcher(Role): - def __init__( - self, - name: str = "David", - profile: str = "Researcher", - goal: str = "Gather information and conduct research", - constraints: str = "Ensure accuracy and relevance of information", - language: str = "en-us", - **kwargs, - ): - super().__init__(name, profile, goal, constraints, **kwargs) - self._init_actions([CollectLinks(name), WebBrowseAndSummarize(name), ConductResearch(name)]) - self._set_react_mode(react_mode="by_order") - self.language = language - if language not in ("en-us", "zh-cn"): - logger.warning(f"The language `{language}` has not been tested, it may not work.") + name: str = "David" + profile: str = "Researcher" + goal: str = "Gather information and conduct research" + constraints: str = "Ensure accuracy and relevance of information" + language: str = "en-us" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._init_actions( + [CollectLinks(name=self.name), WebBrowseAndSummarize(name=self.name), ConductResearch(name=self.name)] + ) + self._set_react_mode(react_mode=RoleReactMode.BY_ORDER.value) + if self.language not in ("en-us", "zh-cn"): + logger.warning(f"The language `{self.language}` has not been tested, it may not work.") async def _act(self) -> Message: logger.info(f"{self._setting}: to do {self._rc.todo}({self._rc.todo.name})") @@ -107,7 +105,7 @@ if __name__ == "__main__": import fire async def main(topic: str, language="en-us"): - role = Researcher(topic, language=language) + role = Researcher(language=language) await role.run(topic) fire.Fire(main) diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index d868e820f..b59f51929 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -208,8 +208,7 @@ class Role(BaseModel): if "actions" in kwargs: self._init_actions(kwargs["actions"]) - if "watch" in kwargs: - self._watch(kwargs["watch"]) + self._watch(kwargs.get("watch") or [UserRequirement]) def __init_subclass__(cls, **kwargs: Any) -> None: super().__init_subclass__(**kwargs) @@ -482,6 +481,7 @@ class Role(BaseModel): async def _act_by_order(self) -> Message: """switch action each time by order defined in _init_actions, i.e. _act (Action1) -> _act (Action2) -> ...""" start_idx = self._rc.state if self._rc.state >= 0 else 0 # action to run from recovered state + rsp = Message(content="No actions taken yet") # return default message if _actions=[] for i in range(start_idx, len(self._states)): self._set_state(i) rsp = await self._act() diff --git a/metagpt/roles/sk_agent.py b/metagpt/roles/sk_agent.py index 56482ef26..791dff5e2 100644 --- a/metagpt/roles/sk_agent.py +++ b/metagpt/roles/sk_agent.py @@ -7,13 +7,19 @@ @Modified By: mashenquan, 2023-11-1. In accordance with Chapter 2.2.1 and 2.2.2 of RFC 116, utilize the new message distribution feature for message filtering. """ + +from pydantic import Field +from semantic_kernel import Kernel +from semantic_kernel.orchestration.sk_function_base import SKFunctionBase from semantic_kernel.planning import SequentialPlanner from semantic_kernel.planning.action_planner.action_planner import ActionPlanner -from semantic_kernel.planning.basic_planner import BasicPlanner +from semantic_kernel.planning.basic_planner import BasicPlanner, Plan from metagpt.actions import UserRequirement from metagpt.actions.execute_task import ExecuteTask +from metagpt.llm import LLM from metagpt.logs import logger +from metagpt.provider.base_gpt_api import BaseGPTAPI from metagpt.roles import Role from metagpt.schema import Message from metagpt.utils.make_sk_kernel import make_sk_kernel @@ -30,27 +36,33 @@ class SkAgent(Role): constraints (str): Constraints for the SkAgent. """ - def __init__( - self, - name: str = "Sunshine", - profile: str = "sk_agent", - goal: str = "Execute task based on passed in task description", - constraints: str = "", - planner_cls=BasicPlanner, - ) -> None: + name: str = "Sunshine" + profile: str = "sk_agent" + goal: str = "Execute task based on passed in task description" + constraints: str = "" + + plan: Plan = None + planner_cls: BasicPlanner = BasicPlanner + planner: BasicPlanner = Field(default_factory=BasicPlanner) + llm: BaseGPTAPI = Field(default_factory=LLM) + kernel: Kernel = Field(default_factory=Kernel) + import_semantic_skill_from_directory: str = "" + import_skill: dict[str, SKFunctionBase] = dict() + + def __init__(self, **kwargs) -> None: """Initializes the Engineer role with given attributes.""" - super().__init__(name, profile, goal, constraints) + super().__init__(**kwargs) self._init_actions([ExecuteTask()]) self._watch([UserRequirement]) self.kernel = make_sk_kernel() # how funny the interface is inconsistent - if planner_cls == BasicPlanner: - self.planner = planner_cls() - elif planner_cls in [SequentialPlanner, ActionPlanner]: - self.planner = planner_cls(self.kernel) + if self.planner_cls == BasicPlanner: + self.planner = self.planner_cls() + elif self.planner_cls in [SequentialPlanner, ActionPlanner]: + self.planner = self.planner_cls(self.kernel) else: - raise f"Unsupported planner of type {planner_cls}" + raise Exception(f"Unsupported planner of type {self.planner_cls}") self.import_semantic_skill_from_directory = self.kernel.import_semantic_skill_from_directory self.import_skill = self.kernel.import_skill diff --git a/metagpt/roles/tutorial_assistant.py b/metagpt/roles/tutorial_assistant.py index e0be4de61..5d1323371 100644 --- a/metagpt/roles/tutorial_assistant.py +++ b/metagpt/roles/tutorial_assistant.py @@ -12,7 +12,7 @@ from typing import Dict from metagpt.actions.write_tutorial import WriteContent, WriteDirectory from metagpt.const import TUTORIAL_PATH from metagpt.logs import logger -from metagpt.roles import Role +from metagpt.roles.role import Role, RoleReactMode from metagpt.schema import Message from metagpt.utils.file import File @@ -28,21 +28,20 @@ class TutorialAssistant(Role): language: The language in which the tutorial documents will be generated. """ - def __init__( - self, - name: str = "Stitch", - profile: str = "Tutorial Assistant", - goal: str = "Generate tutorial documents", - constraints: str = "Strictly follow Markdown's syntax, with neat and standardized layout", - language: str = "Chinese", - ): - super().__init__(name, profile, goal, constraints) - self._init_actions([WriteDirectory(language=language)]) - self.topic = "" - self.main_title = "" - self.total_content = "" - self.language = language - self._set_react_mode(react_mode="by_order") + name: str = "Stitch" + profile: str = "Tutorial Assistant" + goal: str = "Generate tutorial documents" + constraints: str = "Strictly follow Markdown's syntax, with neat and standardized layout" + language: str = "Chinese" + + topic = "" + main_title = "" + total_content = "" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._init_actions([WriteDirectory(language=self.language)]) + self._set_react_mode(react_mode=RoleReactMode.BY_ORDER.value) async def _handle_directory(self, titles: Dict) -> Message: """Handle the directories for the tutorial document. diff --git a/metagpt/utils/make_sk_kernel.py b/metagpt/utils/make_sk_kernel.py index 83b4005ec..de84e3630 100644 --- a/metagpt/utils/make_sk_kernel.py +++ b/metagpt/utils/make_sk_kernel.py @@ -21,12 +21,14 @@ def make_sk_kernel(): if CONFIG.openai_api_type == "azure": kernel.add_chat_service( "chat_completion", - AzureChatCompletion(CONFIG.deployment_name, CONFIG.openai_base_url, CONFIG.openai_api_key), + AzureChatCompletion( + deployment_name=CONFIG.deployment_name, endpoint=CONFIG.openai_base_url, api_key=CONFIG.openai_api_key + ), ) else: kernel.add_chat_service( "chat_completion", - OpenAIChatCompletion(CONFIG.openai_api_model, CONFIG.openai_api_key), + OpenAIChatCompletion(model_id=CONFIG.openai_api_model, api_key=CONFIG.openai_api_key), ) return kernel diff --git a/tests/metagpt/roles/test_invoice_ocr_assistant.py b/tests/metagpt/roles/test_invoice_ocr_assistant.py index c9aad93a7..ab3092004 100644 --- a/tests/metagpt/roles/test_invoice_ocr_assistant.py +++ b/tests/metagpt/roles/test_invoice_ocr_assistant.py @@ -7,12 +7,13 @@ @File : test_invoice_ocr_assistant.py """ +import json from pathlib import Path import pandas as pd import pytest -from metagpt.roles.invoice_ocr_assistant import InvoiceOCRAssistant +from metagpt.roles.invoice_ocr_assistant import InvoiceOCRAssistant, InvoicePath from metagpt.schema import Message @@ -55,8 +56,8 @@ async def test_invoice_ocr_assistant( ): invoice_path = Path.cwd() / invoice_path role = InvoiceOCRAssistant() - await role.run(Message(content=query, instruct_content={"file_path": invoice_path})) + await role.run(Message(content=query, instruct_content=InvoicePath(file_path=invoice_path))) invoice_table_path = Path.cwd() / invoice_table_path df = pd.read_excel(invoice_table_path) dict_result = df.to_dict(orient="records") - assert dict_result == expected_result + assert json.dumps(dict_result) == json.dumps(expected_result)