mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-02 14:45:17 +02:00
feat: archive
This commit is contained in:
parent
5f16d6e853
commit
799dbd396e
10 changed files with 291 additions and 5 deletions
17
.well-known/skills.yaml
Normal file
17
.well-known/skills.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
entities:
|
||||
Assistant:
|
||||
skills:
|
||||
- name: text_to_speech
|
||||
description: Text-to-speech
|
||||
requisite:
|
||||
- AZURE_TTS_SUBSCRIPTION_KEY
|
||||
- AZURE_TTS_REGION
|
||||
- name: text_to_image
|
||||
description: Create a drawing based on the text.
|
||||
requisite:
|
||||
- OPENAI_API_KEY
|
||||
- METAGPT_TEXT_TO_IMAGE_MODEL
|
||||
- name: text_to_embedding
|
||||
description: Convert the text into embeddings.
|
||||
requisite:
|
||||
- OPENAI_API_KEY
|
||||
|
|
@ -62,6 +62,6 @@ class Action(ABC):
|
|||
instruct_content = output_class(**parsed_data)
|
||||
return ActionOutput(content, instruct_content)
|
||||
|
||||
async def run(self, *args, **kwargs):
|
||||
async def run(self, *args, **kwargs) -> str | ActionOutput | None:
|
||||
"""Run action"""
|
||||
raise NotImplementedError("The run method should be implemented in a subclass.")
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@
|
|||
@File : action_output
|
||||
"""
|
||||
|
||||
from typing import Dict, Type
|
||||
from typing import Dict, Type, Optional
|
||||
|
||||
from pydantic import BaseModel, create_model, root_validator, validator
|
||||
|
||||
|
||||
class ActionOutput:
|
||||
content: str
|
||||
instruct_content: BaseModel
|
||||
instruct_content: Optional[BaseModel] = None
|
||||
|
||||
def __init__(self, content: str, instruct_content: BaseModel):
|
||||
def __init__(self, content: str, instruct_content: BaseModel=None):
|
||||
self.content = content
|
||||
self.instruct_content = instruct_content
|
||||
|
||||
|
|
|
|||
32
metagpt/actions/talk_action.py
Normal file
32
metagpt/actions/talk_action.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
from metagpt.actions import Action, ActionOutput
|
||||
from metagpt.logs import logger
|
||||
|
||||
|
||||
|
||||
class TalkAction(Action):
|
||||
def __init__(self, options, name: str = '', talk='', history_summary='', context=None, llm=None):
|
||||
context = context or {}
|
||||
context["talk"] = talk
|
||||
context["history_summery"] = history_summary
|
||||
super(TalkAction, self).__init__(options=options, name=name, context=context, llm=llm)
|
||||
self._talk = talk
|
||||
self._history_summary = history_summary
|
||||
self._rsp = None
|
||||
|
||||
@property
|
||||
def prompt(self):
|
||||
prompt = f"{self._history_summary}\n\n"
|
||||
if self._history_summary != "":
|
||||
prompt += "According to the historical conversation above, "
|
||||
language = self.options.get("language", "Chinese")
|
||||
prompt += f"Answer in {language}:\n {self._talk}"
|
||||
return prompt
|
||||
|
||||
async def run(self, *args, **kwargs) -> ActionOutput:
|
||||
prompt = self.prompt
|
||||
logger.info(prompt)
|
||||
rsp = await self.llm.aask(msg=prompt, system_msgs=[])
|
||||
logger.info(rsp)
|
||||
self._rsp = ActionOutput(content=rsp)
|
||||
return self._rsp
|
||||
|
||||
38
metagpt/learn/skill_loader.py
Normal file
38
metagpt/learn/skill_loader.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
from pathlib import Path
|
||||
from typing import List, Dict
|
||||
|
||||
import yaml
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Skill(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
requisite: List[str]
|
||||
|
||||
|
||||
class EntitySkills(BaseModel):
|
||||
skills: List[Skill]
|
||||
|
||||
|
||||
class SkillsDeclaration(BaseModel):
|
||||
entities: Dict[str, EntitySkills]
|
||||
|
||||
|
||||
class SkillLoader:
|
||||
def __init__(self):
|
||||
skill_file_name = Path(__file__).parent.parent.parent / ".well-known/skills.yaml"
|
||||
with open(str(skill_file_name), 'r') as file:
|
||||
skills = yaml.safe_load(file)
|
||||
self._skills = SkillsDeclaration(**skills)
|
||||
|
||||
def get_skill_list(self, entity_name: str = "Assistant"):
|
||||
if not self._skills or entity_name not in self._skills.entities:
|
||||
return {}
|
||||
entity_skills = self._skills.entities.get(entity_name)
|
||||
|
||||
description_to_name_mappings = {}
|
||||
for s in entity_skills.skills:
|
||||
description_to_name_mappings[s.description] = s.name
|
||||
|
||||
return description_to_name_mappings
|
||||
47
metagpt/memory/brain_memory.py
Normal file
47
metagpt/memory/brain_memory.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
import pydantic
|
||||
|
||||
from metagpt import Message
|
||||
|
||||
class MessageType(Enum):
|
||||
Talk = "TALK"
|
||||
Solution = "SOLUTION"
|
||||
Problem = "PROBLEM"
|
||||
Skill = "SKILL"
|
||||
Answer = "ANSWER"
|
||||
|
||||
|
||||
class BrainMemory(pydantic.BaseModel):
|
||||
history: List[Message] = []
|
||||
stack: List[Message] = []
|
||||
solution: List[Message] = []
|
||||
|
||||
|
||||
def add_talk(self, msg: Message):
|
||||
msg.add_tag(MessageType.Talk.value)
|
||||
self.history.append(msg)
|
||||
|
||||
def add_answer(self, msg: Message):
|
||||
msg.add_tag(MessageType.Answer.value)
|
||||
self.history.append(msg)
|
||||
|
||||
@property
|
||||
def history_text(self):
|
||||
if len(self.history) == 0:
|
||||
return ""
|
||||
texts = [m.content for m in self.history[:-1]]
|
||||
return "\n".join(texts)
|
||||
|
||||
def move_to_solution(self):
|
||||
while len(self.history) > 1:
|
||||
msg = self.history.pop()
|
||||
self.solution.append(msg)
|
||||
|
||||
@property
|
||||
def last_talk(self):
|
||||
if len(self.history) == 0 or not self.history[-1].is_contain_tags([MessageType.Talk.value]):
|
||||
return ""
|
||||
return self.history[-1].content
|
||||
|
||||
|
|
@ -313,6 +313,8 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter):
|
|||
|
||||
async def get_summary(self, text: str, max_words=20):
|
||||
"""Generate text summary"""
|
||||
if len(text) < max_words:
|
||||
return text
|
||||
language = self._options.get("language", "English")
|
||||
command = f"Translate the above content into a {language} summary of less than {max_words} words."
|
||||
msg = text + "\n\n" + command
|
||||
|
|
|
|||
143
metagpt/roles/assistant.py
Normal file
143
metagpt/roles/assistant.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2023/8/7
|
||||
@Author : mashenquan
|
||||
@File : fork_meta_role.py
|
||||
@Desc : I am attempting to incorporate certain symbol concepts from UML into MetaGPT, enabling it to have the
|
||||
ability to freely construct flows through symbol concatenation. Simultaneously, I am also striving to
|
||||
make these symbols configurable and standardized, making the process of building flows more convenient.
|
||||
For more about `fork` node in activity diagrams, see: `https://www.uml-diagrams.org/activity-diagrams.html`
|
||||
This file defines a `fork` style meta role capable of generating arbitrary roles at runtime based on a
|
||||
configuration file.
|
||||
@Modified By: mashenquan, 2023/8/22. A definition has been provided for the return value of _think: returning false indicates that further reasoning cannot continue.
|
||||
|
||||
"""
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
from metagpt.actions import ActionOutput
|
||||
from metagpt.actions.talk_action import TalkAction
|
||||
from metagpt.config import Config
|
||||
from metagpt.learn.skill_loader import SkillLoader
|
||||
from metagpt.logs import logger
|
||||
from metagpt.memory.brain_memory import BrainMemory, MessageType
|
||||
from metagpt.provider.openai_api import CostManager
|
||||
from metagpt.roles import Role
|
||||
from metagpt.schema import Message
|
||||
|
||||
DEFAULT_MAX_TOKENS = 1500
|
||||
COMMAND_TOKENS = 500
|
||||
|
||||
|
||||
class Assistant(Role):
|
||||
"""解决通用问题的助手"""
|
||||
|
||||
def __init__(self, options, cost_manager, name="Lily", profile="An assistant", goal="Help to solve problem",
|
||||
constraints="Talk in {language}", desc="", *args, **kwargs):
|
||||
super(Assistant, self).__init__(options=options, cost_manager=cost_manager, name=name, profile=profile,
|
||||
goal=goal, constraints=constraints, desc=desc, *args, **kwargs)
|
||||
self.memory = BrainMemory()
|
||||
self.skills = SkillLoader()
|
||||
|
||||
async def think(self) -> bool:
|
||||
"""Everything will be done part by part."""
|
||||
if self.memory.history_text != "":
|
||||
self._refine_memory()
|
||||
|
||||
|
||||
prompt = ""
|
||||
history_text = self.memory.history_text
|
||||
history_summary = ""
|
||||
if history_text != "":
|
||||
max_tokens = self.options.get("MAX_TOKENS", DEFAULT_MAX_TOKENS)
|
||||
history_summary = await self._llm.get_summary(history_text, max_tokens - COMMAND_TOKENS)
|
||||
prompt += history_summary + "\n\n"
|
||||
prompt += "Analyze the conversation history above, in conjunction with the current sentence: \n{self.memory.last_talk}\n\n"
|
||||
else:
|
||||
prompt += f"Refer to this sentence:\n {self.memory.last_talk}\n"
|
||||
skills = self.skills.get_skill_list()
|
||||
for desc, name in skills.items():
|
||||
prompt += f"If want you to do {desc}, return `[SKILL]: {name}` brief and clear. For instance: [SKILL]: text_to_image\n"
|
||||
if history_text != "":
|
||||
prompt += "If the last sentence is not related to the conversation history above, return `[SOLUTION]: {title of the history conversation}` brief and clear. For instance: [SOLUTION]: Solution for distributing watermelon\n"
|
||||
prompt += "If the preceding text presents a complete question and solution, rewrite and return `[SOLUTION]: {problem}` brief and clear. For instance: [SOLUTION]: Solution for distributing watermelon\n"
|
||||
prompt += "If the preceding text presents an unresolved issue and its corresponding discussion, rewrite and return `[PROBLEM]: {problem}` brief and clear. For instance: [PROBLEM]: How to distribute watermelon?\n"
|
||||
prompt += "Otherwise, rewrite and return `[TALK]: {talk}` brief and clear. For instance: [TALK]: distribute watermelon"
|
||||
logger.info(prompt)
|
||||
rsp = await self._llm.aask(prompt, [])
|
||||
logger.info(rsp)
|
||||
return await self._plan(rsp, history_summary=history_summary)
|
||||
|
||||
async def act(self) -> ActionOutput:
|
||||
result = await self._rc.todo.run(**self._options)
|
||||
if not result:
|
||||
return None
|
||||
if isinstance(result, str):
|
||||
msg = Message(content=result)
|
||||
output = ActionOutput(content=result)
|
||||
else:
|
||||
msg = Message(content=result.content, instruct_content=result.instruct_content,
|
||||
cause_by=type(self._rc.todo))
|
||||
output = result
|
||||
self.memory.add_answer(msg)
|
||||
return output
|
||||
|
||||
async def talk(self, text):
|
||||
self.memory.add_talk(Message(content=text, tags=set([MessageType.Talk.value])))
|
||||
|
||||
async def _plan(self, rsp, **kwargs) -> bool:
|
||||
skill, text = Assistant.extract_info(rsp)
|
||||
handlers = {
|
||||
MessageType.Talk.value: self.talk_handler,
|
||||
MessageType.Problem.value: self.problem_handler,
|
||||
MessageType.Solution.value: self.solution_handler,
|
||||
MessageType.Skill.value: self.skill_handler,
|
||||
}
|
||||
handler = handlers.get(skill, self.talk_handler)
|
||||
return await handler(text, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def extract_info(input_string):
|
||||
pattern = r'\[([A-Z]+)\]:\s*(.+)'
|
||||
match = re.match(pattern, input_string)
|
||||
if match:
|
||||
return match.group(1), match.group(2)
|
||||
else:
|
||||
return None, input_string
|
||||
|
||||
async def problem_handler(self, text, **kwargs) -> bool:
|
||||
action = TalkAction(options=self.options, talk=text, llm=self._llm, **kwargs)
|
||||
self.add_to_do(action)
|
||||
return True
|
||||
|
||||
async def solution_handler(self, text, **kwargs) -> bool:
|
||||
self.memory.move_to_solution() # 问题解决后及时清空内存
|
||||
action = TalkAction(options=self.options, talk=text, history_summary="", **kwargs)
|
||||
self.add_to_do(action)
|
||||
|
||||
async def skill_handler(self, text, **kwargs) -> bool:
|
||||
pass
|
||||
|
||||
async def _refine_memory(self):
|
||||
|
||||
|
||||
async def main():
|
||||
options = Config().runtime_options
|
||||
cost_manager = CostManager(**options)
|
||||
topic = "dataiku vs. datarobot"
|
||||
role = Assistant(options=options, cost_manager=cost_manager, language="Chinese")
|
||||
await role.talk(topic)
|
||||
while True:
|
||||
has_action = await role.think()
|
||||
if not has_action:
|
||||
break
|
||||
msg = await role.act()
|
||||
print(msg)
|
||||
# 获取用户终端输入
|
||||
talk = input("You: ")
|
||||
await role.talk(talk)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
|
|
@ -320,3 +320,9 @@ class Role:
|
|||
for k, v in merged_opts.items():
|
||||
value = value.replace("{" + f"{k}" + "}", str(v))
|
||||
return value
|
||||
|
||||
def add_action(self, act):
|
||||
self._actions.append(act)
|
||||
|
||||
def add_to_do(self, act):
|
||||
self._rc.todo = act
|
||||
|
|
@ -10,7 +10,7 @@ from __future__ import annotations
|
|||
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Type, TypedDict, Set, Optional
|
||||
from typing import Type, TypedDict, Set, Optional, List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
@ -98,6 +98,7 @@ class AIMessage(Message):
|
|||
super().__init__(content, 'assistant')
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_content = 'test_message'
|
||||
msgs = [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue