feat: archive

This commit is contained in:
莘权 马 2023-08-25 21:10:14 +08:00
parent 5f16d6e853
commit 799dbd396e
10 changed files with 291 additions and 5 deletions

17
.well-known/skills.yaml Normal file
View 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

View file

@ -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.")

View file

@ -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

View 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

View 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

View 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

View file

@ -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
View 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())

View file

@ -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

View file

@ -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 = [