diff --git a/metagpt/actions/di/detect_intent.py b/metagpt/actions/di/detect_intent.py new file mode 100644 index 000000000..88baab0fc --- /dev/null +++ b/metagpt/actions/di/detect_intent.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +This script is designed to classify intentions from complete conversation content. + +Usage: + This script can be used to classify intentions from a conversation. It utilizes models for detecting intentions + from the text provided and categorizes them accordingly. If the intention of certain words or phrases is unclear, + it prompts the user for clarification. + +Dependencies: + This script depends on the metagpt library, pydantic, and other utilities for message parsing and interaction. + +""" +from __future__ import annotations + +import asyncio +from enum import Enum +from typing import Tuple + +from pydantic import BaseModel + +from metagpt.actions import Action + + +class SOPItemDef(BaseModel): + """ + Represents an item in a Standard Operating Procedure (SOP). + + Attributes: + name (str): name of the SOP item. + description (str): The description or title of the SOP. + sop (List[str]): The steps or details of the SOP. + """ + + name: str + description: str + sop: list[str] = [] + + +class SOPItem(Enum): + SOFTWARE_DEVELOPMENT = SOPItemDef( + name="software development", + description="Intentions related to or including software development, such as developing or building software, games, app, websites, etc. Excluding bug fixes, report any issues.", + sop=[ + "Writes a PRD based on software requirements.", + "Writes a design to the project repository, based on the PRD of the project.", + "Writes a project plan to the project repository, based on the design of the project.", + "Writes code to implement designed features according to the project plan and adds them to the project repository.", + # "Run QA test on the project repository.", + "Stage and commit changes for the project repository using Git.", + ], + ) + FIX_BUGS = SOPItemDef( + name="fix bugs", + description="Fix bugs in a given project.", + sop=[ + "Fix bugs in the project repository.", + "Stage and commit changes for the project repository using Git.", + ], + ) + FORMAT_REPO = SOPItemDef( + name="format repo", + description="download repository from git and format the project to MetaGPT project", + sop=[ + "Imports a project from a Git website and formats it to MetaGPT project format to enable incremental appending requirements.", + "Stage and commit changes for the project repository using Git.", + ], + ) + OTHER = SOPItemDef( + name="other", + description="Other intentions that do not fall into the above categories, including data science, machine learning, deep learning, etc.", + sop=[], + ) + + @property + def type_name(self): + return self.value.name + + @classmethod + def get_type(cls, type_name): + for member in cls: + if member.type_name == type_name: + return member.value + return None + + +DETECT_PROMPT = """ +# User Requirement +{user_requirement} +# Intentions +{intentions} +# Task +Classify user requirement into one type of the above intentions, output the name of the intention directly. +Intention name: +""" + +REQ_WITH_SOP = """ +{user_requirement} +You should follow the following Standard Operating Procedure: +{sop} +""" + + +class DetectIntent(Action): + async def run(self, user_requirement: str) -> Tuple[str, str]: + intentions = "\n".join([f"{si.type_name}: {si.value.description}" for si in SOPItem]) + prompt = DETECT_PROMPT.format(user_requirement=user_requirement, intentions=intentions) + + sop_type = await self._aask(prompt) + sop_type = sop_type.strip() + + sop = SOPItem.get_type(sop_type).sop + + req_with_sop = ( + REQ_WITH_SOP.format(user_requirement=user_requirement, sop="\n".join(sop)) if sop else user_requirement + ) + + return req_with_sop, sop_type + + +async def main(): + # Example usage of the DetectIntent action + user_requirements = ["Develop a 2048 game.", "Run data analysis on sklearn wine dataset"] + detect_intent = DetectIntent() + + for user_requirement in user_requirements: + req_with_sop, sop_type = await detect_intent.run(user_requirement) + print(req_with_sop) + print(f"Detected SOP Type: {sop_type}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/metagpt/actions/di/test_detect_intent.py b/tests/metagpt/actions/di/test_detect_intent.py new file mode 100644 index 000000000..7c9cf9eba --- /dev/null +++ b/tests/metagpt/actions/di/test_detect_intent.py @@ -0,0 +1,55 @@ +import pytest + +from metagpt.actions.di.detect_intent import DetectIntent + +SOFTWARE_DEV_REQ1 = """ +I'd like to create a personalized website that features the 'Game of Life' simulation. +""" + +SOFTWARE_DEV_REQ2 = """ +Create a website widget for TODO list management. +""" + +SOFTWARE_DEV_REQ3 = """ +Create an official website with a top bar, banner, About Us section, and footer. +""" + +DI_REQ1 = """ +can you finetune a 78 Llama model using https://github.com/huggingface/peft should be instructions in the Readme. +""" + +DI_REQ2 = """ +I came across a blog post on the website Mafengwo (https://www.mafengwo.cn/i/17171539.html) that discusses the possibility of generating images with hidden text. The post refers to a script that can be used for this purpose. Could you help me set up this script and use it to generate some images? I would like the images to have the hidden text 'MAX' and also some with 'MetaGPT' as the hidden text. +""" + +DI_REQ3 = """ +Extract all of the blog posts from `https://stripe.com/blog/page/1` and return a CSV with the columns `date`, `article_text`, `author` and `summary`. Generate a summary for each article yourself. +""" + +FIX_BUG_REQ = """ +Fix this error from the 2048 game repo: TypeError: __init__() takes 1 positional argument but 2 were given" +""" + +FORMAT_REPO_REQ = """ +git clone 'https://github.com/spec-first/connexion' and format to MetaGPT project +""" + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "requirement, expected_intent_type", + [ + (SOFTWARE_DEV_REQ1, "software development"), + (SOFTWARE_DEV_REQ2, "software development"), + (SOFTWARE_DEV_REQ3, "software development"), + (DI_REQ1, "other"), + (DI_REQ2, "other"), + (DI_REQ3, "other"), + (FIX_BUG_REQ, "fix bugs"), + (FORMAT_REPO_REQ, "format repo"), + ], +) +async def test_detect_intent(requirement, expected_intent_type): + di = DetectIntent() + _, intent_type = await di.run(requirement) + assert intent_type == expected_intent_type