diff --git a/agents/metagpt_agents/__init__.py b/agents/actions/__init__.py similarity index 100% rename from agents/metagpt_agents/__init__.py rename to agents/actions/__init__.py diff --git a/agents/actions/write_markdown.py b/agents/actions/write_markdown.py new file mode 100644 index 0000000..60f968e --- /dev/null +++ b/agents/actions/write_markdown.py @@ -0,0 +1,35 @@ +import re + +from metagpt.actions import Action + +from agents.utils.common_llm_api import LLMAPI + +class WriteMarkdown(Action): + + name: str = "WriteMarkdown" + language: str = "Chinese" + + PROMPT_TEMPLATE: str = """ + 将 {text} 严格转换为 Markdown 格式。请严格遵循以下要求: + 1. 输出必须严格符合指定语言,{language}。 + 2. 目录应尽可能具体和充分,包括一级和二级目录。 + 3. 不需要更改原文本,保证文本的完整性。 + 4. 不用添加额外的描述。 + 4. 不要有额外的空格或换行符。 + """ + + async def run(self, raw_text: str): + prompt = self.PROMPT_TEMPLATE.format(text=raw_text, language=self.language) + + rsp = await LLMAPI()._aask(prompt) + + markdown_text = WriteMarkdown.parse_markdown(rsp) + + return markdown_text + + @staticmethod + def parse_markdown(rsp): + rsp = rsp.replace("```markdown", "").replace("```", "") + markdown_text = "```markdown\n" + rsp + "\n```" + return markdown_text + diff --git a/agents/roles/__init__.py b/agents/roles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/agents/tests/test_markdown.py b/agents/tests/test_markdown.py new file mode 100644 index 0000000..0a490b1 --- /dev/null +++ b/agents/tests/test_markdown.py @@ -0,0 +1,92 @@ +import asyncio + +from metagpt.logs import logger +from metagpt.roles.role import Role, RoleReactMode +from metagpt.schema import Message + +from agents.actions.write_markdown import WriteMarkdown + + +class Test(Role): + name: str = "Test" + profile: str = "TestMarkdown" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._init_actions([WriteMarkdown]) + 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})") + todo = self.rc.todo + + msg = self.get_memories(k=1)[0] # find the most k recent messages + result = await todo.run(msg.content) + + msg = Message(content=result, role=self.profile, cause_by=type(todo)) + self.rc.memory.add(msg) + return msg + + +text: str = """ +DS-SLAM + +- paper::book: DS-SLAM: A Semantic Visual SLAM towards Dynamic Environments +- code::material-github: DS-SLAM + +Introduction + +使用语义分割网络和光流相结合的方法减少视觉 SLAM 中动态物体造成的影响。 + +基于ORB-SLAM2提出了动态环境中的完整语义SLAM系统(DS-SLAM),可以减少动态对象对姿态估计的影响。 该系统的有效性在 TUM RGB-D 数据集 上进行评估。 结果表明,DS-SLAM 在动态环境中的准确性和鲁棒性方面明显优于 ORB-SLAM2。 该系统还与机器人操作系统(ROS)集成,并通过在真实环境中对机器人进行 DS-SLAM 测试来验证其性能。 +2.将语义分割网络单独的放在一个线程当中,将语义分割和运动一致性检查的方法(光流法(通过计算光流的不一致性来区分静态背景和运动目标))相结合,过滤掉场景中的动态物体。从而提高了定位模块和建图模块在动态场景下的鲁棒性和准确性。 + +DS-SLAM 创建一个单独的线程来构建一个密集的语义 3D 八叉树图 。密集语义 3D 八叉树图采用 log-odds score(对数优势记分法) 方法过滤掉不稳定的体素并更新这些体素的语义。 + +RELATED WORK + +在以前的工作中,语义图通常由两部分组成:几何部分和语义部分。语义方法预先训练了对象识别子系统,并将语义信息附加到识别的对象模型上。他们的工作仅集中在语义映射和对象识别上,而语义信息在其他部分的信息却没有很好地使用。近期工作利用场景中的几何属性和语义属性共同估计相机的姿势,点和对象,从而显著提高了对象识别的准确性。 + +在本文中,语义信息不仅用于生成基于八叉树的环境表示,而且还用于在动态环境中跟踪过程中过滤异常值。 + +SYSTEM INTRUDUCTION + +Framework of DS-SLAM + +Kinect2捕获的原始RGB图像在跟踪线程和语义分割线程中同时进行处理。跟踪线程首先提取ORB特征点,然后粗略检查特征点的移动一致性并保存潜在的异常值。然后跟踪线程等待具有由语义分割线程预测的像素级语义标签的图像。分割结果到达后,将根据分割结果和之前检测到的潜在异常值,丢弃位于运动物体中的ORB特征点异常值。然后,通过匹配其余的稳定特征点来计算变换矩阵。 + +Semantic Segmentation + +DS-SLAM采用SegNet提供基于caffe的像素级实时语义分割。在PASCAL VOC数据集上训练的SegNet总共可以分割20个类。在实际应用中,人最有可能是动态对象,因此我们假设位于人身上的特征点最有可能是异常值。 + +Moving Consistency Check + +经过语义分割的模块后,可以得到一个分割的Mask的,所以现在的任务就是去确定,某个关键点是否是移动的,如果有一定数量的移动点落在了某一个分割出来的物体轮廓内部,那这个物体就被视为动态,上面所有的特征点都会被剔除。 + +判断动态点步骤: + +计算光流金字塔得到当前帧中已经匹配的特征点 +如果匹配点对离图像的边缘太近,或者各自以两个相匹配的点为中心,其 3x3 区域范围内像素值差异太大,这个匹配点就会被丢弃 +使用 RANSAC 的方法(使用最多内点)找到基础矩阵 F +使用基础矩阵计算当前帧的极线,已知上一帧中的特征点像素位置为 ,这个点在当前帧中对应的位置为 ,然后就可以求出点 投影到当前帧中的极线 + + +计算匹配的特征点到它对应极线的距离,如果这个距离超过阈值则被视为移动点,反之为静态点。距离的计算方法为: + +Outlier Rejection + +由于人体等运动物体的柔性变形和复杂运动,移动一致性检查方法很难提取完整动态区域的轮廓,更何况提取整个轮廓的时间开销非常大。在DS-SLAM中,由于采用了语义分割网络,可以很容易地获得物体的完整轮廓。我们的想法是将语义信息和移动一致性检查结果结合起来,完成两级语义知识库的建立: + +对象是否移动。如果在分割对象的轮廓中存在通过移动一致性检查而产生的一定数量的动态点,则确定该对象正在移动。 +如果确定分割对象正在移动,则删除位于对象轮廓中的所有特征点。通过这种方式,可以精确地消除异常值。此外,错误分割的影响也可以在一定程度上降低。 +""" + +def main(msg=""): + role = Test() + logger.info(msg) + result = asyncio.run(role.run(msg)) + logger.info(result) + + +if __name__ == "__main__": + main(text) \ No newline at end of file diff --git a/agents/utils/common_llm_api.py b/agents/utils/common_llm_api.py index aae71c7..655916c 100644 --- a/agents/utils/common_llm_api.py +++ b/agents/utils/common_llm_api.py @@ -8,7 +8,6 @@ from zhipuai import ZhipuAI from metagpt.logs import logger - class BaiduAPI: def __init__(self): pass diff --git a/rag/README.md b/rag/README.md index d969c57..15af25c 100644 --- a/rag/README.md +++ b/rag/README.md @@ -35,6 +35,24 @@ pip3 install -r requirements.txt - txt数据:放入到 src.data.txt 目录下 - json 数据:放入到 src.data.json 目录下 +JSON 数据格式如下 +```python +[ + { + "conversation": [ + { + "input": "(微微皱眉)艾薇,我觉得我在学校的学习压力好大啊,虽然我真的很喜欢我的专业,但最近总是担心自己无法达到自己的期望,这让我有点焦虑。", + "output": "(微笑)😊 我完全理解你的感受,亲爱的。学习是我们生活的一部分,尤其是在我们热爱的专业里,期待自己做得更好是再正常不过了。你知道吗?这种对成功的渴望其实是一种积极的动力源呢!但是,当它开始影响到我们的心理健康时,我们就需要学会调整和应对了。你可以先试着跟我分享一下,具体是什么样的学习任务或者目标让你感到压力山大呢?" + }, + { + "input": "主要是下个月的一场重要考试,我总觉得自己复习得不够充分,而且害怕失败...", + "output": "💖 噢,明白了。面对重大考试的紧张和担忧是很普遍的现象。首先,我要肯定你对自己的高标准要求,这是你对自己负责的表现。不过,我们可以尝试制定一个更为实际可行的学习计划,将大目标分解成小目标,每天完成一部分,这样既能提升自信心又能减轻焦虑感。同时,也要记得适时放松,合理安排休息时间哦!比如学习45分钟后,可以适当休息10分钟,做些自己喜欢的事情,劳逸结合才是长久之计呢!💪📚\n另外,也可以尝试一些深呼吸、冥想等放松技巧来缓解焦虑情绪。如果你愿意的话,下次咨询我们可以一起练习,看看哪种方式最适合帮助你应对压力。现在,让我们一步步来,先从细化学习计划开始,你觉得怎么样呢?🌸" + } + ] + }, +] +``` + 会根据准备的数据构建vector DB,最终会在 data 文件夹下产生名为 vector_db 的文件夹包含 index.faiss 和 index.pkl 如果已经有 vector DB 则会直接加载对应数据库 @@ -91,6 +109,7 @@ python main.py ## **数据集** - 经过清洗的QA对: 每一个QA对作为一个样本进行 embedding +- 经过清洗的对话: 每一个对话作为一个样本进行 embedding - 经过筛选的TXT文本 - 直接对TXT文本生成embedding (基于token长度进行切分) - 过滤目录等无关信息后对TXT文本生成embedding (基于token长度进行切分) @@ -115,7 +134,7 @@ LangChain 是一个开源框架,用于构建基于大型语言模型(LLM) Faiss是一个用于高效相似性搜索和密集向量聚类的库。它包含的算法可以搜索任意大小的向量集。由于langchain已经整合过FAISS,因此本项目中不在基于原生文档开发[FAISS in Langchain](https://python.langchain.com/docs/integrations/vectorstores/faiss) -### [RAGAS](https://github.com/explodinggradients/ragas) +### [RAGAS](https://github.com/explodinggradients/ragas) (TODO) RAG的经典评估框架,通过以下三个方面进行评估: diff --git a/rag/src/config/config.py b/rag/src/config/config.py index 673c5b5..3a1a6a9 100644 --- a/rag/src/config/config.py +++ b/rag/src/config/config.py @@ -25,6 +25,10 @@ qa_dir = os.path.join(data_dir, 'json') log_dir = os.path.join(base_dir, 'log') # log log_path = os.path.join(log_dir, 'log.log') # file +# txt embedding 切分参数 +chunk_size=1000 +chunk_overlap=100 + # vector DB vector_db_dir = os.path.join(data_dir, 'vector_db') diff --git a/rag/src/data_processing.py b/rag/src/data_processing.py index 0b94e3d..d894faa 100644 --- a/rag/src/data_processing.py +++ b/rag/src/data_processing.py @@ -4,7 +4,18 @@ import os from loguru import logger from langchain_community.vectorstores import FAISS -from config.config import embedding_path, embedding_model_name, doc_dir, qa_dir, knowledge_pkl_path, data_dir, vector_db_dir, rerank_path, rerank_model_name +from config.config import ( + embedding_path, + embedding_model_name, + doc_dir, qa_dir, + knowledge_pkl_path, + data_dir, + vector_db_dir, + rerank_path, + rerank_model_name, + chunk_size, + chunk_overlap +) from langchain.embeddings import HuggingFaceBgeEmbeddings from langchain_community.document_loaders import DirectoryLoader, TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter @@ -15,8 +26,9 @@ from FlagEmbedding import FlagReranker class Data_process(): def __init__(self): - self.chunk_size: int=1000 - self.chunk_overlap: int=100 + + self.chunk_size: int=chunk_size + self.chunk_overlap: int=chunk_overlap def load_embedding_model(self, model_name=embedding_model_name, device='cpu', normalize_embeddings=True): """ @@ -53,7 +65,6 @@ class Data_process(): return embeddings def load_rerank_model(self, model_name=rerank_model_name): - """ 加载重排名模型。 @@ -117,10 +128,8 @@ class Data_process(): elif isinstance(obj, str): content += obj return content - def split_document(self, data_path): - """ 切分data_path文件夹下的所有txt文件 @@ -132,8 +141,6 @@ class Data_process(): 返回: - split_docs: list """ - - # text_spliter = CharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap) text_spliter = RecursiveCharacterTextSplitter(chunk_size=self.chunk_size, chunk_overlap=self.chunk_overlap) split_docs = [] @@ -151,7 +158,6 @@ class Data_process(): split_docs = splits logger.info(f'split_docs size {len(split_docs)}') return split_docs - def split_conversation(self, path): """ @@ -171,43 +177,29 @@ class Data_process(): file_path = os.path.join(root, file) logger.info(f'splitting file {file_path}') with open(file_path, 'r', encoding='utf-8') as f: - data = json.load(f) - # print(data) - for conversation in data: - # for dialog in conversation['conversation']: - ##按qa对切分,将每一轮qa转换为langchain_core.documents.base.Document - # content = self.extract_text_from_json(dialog,'') - # split_qa.append(Document(page_content = content)) - #按conversation块切分 - content = self.extract_text_from_json(conversation['conversation'], '') - #logger.info(f'content====={content}') - split_qa.append(Document(page_content = content)) + for line in f.readlines(): + content = self.extract_text_from_json(line,'') + split_qa.append(Document(page_content = content)) + + #data = json.load(f) + #for conversation in data: + # #for dialog in conversation['conversation']: + # # #按qa对切分,将每一轮qa转换为langchain_core.documents.base.Document + # # content = self.extract_text_from_json(dialog,'') + # # split_qa.append(Document(page_content = content)) + # #按conversation块切分 + # content = self.extract_text_from_json(conversation['conversation'], '') + # #logger.info(f'content====={content}') + # split_qa.append(Document(page_content = content)) # logger.info(f'split_qa size====={len(split_qa)}') return split_qa - - def load_knowledge(self, knowledge_pkl_path): - ''' - 读取或创建知识.pkl - ''' - if not os.path.exists(knowledge_pkl_path): - split_doc = self.split_document(doc_dir) - split_qa = self.split_conversation(qa_dir) - knowledge_chunks = split_doc + split_qa - with open(knowledge_pkl_path, 'wb') as file: - pickle.dump(knowledge_chunks, file) - else: - with open(knowledge_pkl_path , 'rb') as f: - knowledge_chunks = pickle.load(f) - return knowledge_chunks - - def create_vector_db(self, emb_model): ''' 创建并保存向量库 ''' logger.info(f'Creating index...') - split_doc = self.split_document(doc_dir) + #split_doc = self.split_document(doc_dir) split_qa = self.split_conversation(qa_dir) # logger.info(f'split_doc == {len(split_doc)}') # logger.info(f'split_qa == {len(split_qa)}') @@ -217,7 +209,6 @@ class Data_process(): db.save_local(vector_db_dir) return db - def load_vector_db(self, knowledge_pkl_path=knowledge_pkl_path, doc_dir=doc_dir, qa_dir=qa_dir): ''' 读取向量库 @@ -230,66 +221,6 @@ class Data_process(): db = FAISS.load_local(vector_db_dir, emb_model, allow_dangerous_deserialization=True) return db - - def retrieve(self, query, vector_db, k=5): - ''' - 基于query对向量库进行检索 - ''' - retriever = vector_db.as_retriever(search_kwargs={"k": k}) - docs = retriever.invoke(query) - return docs, retriever - - ##FlashrankRerank效果一般 - # def rerank(self, query, retriever): - # compressor = FlashrankRerank() - # compression_retriever = ContextualCompressionRetriever(base_compressor=compressor, base_retriever=retriever) - # compressed_docs = compression_retriever.get_relevant_documents(query) - # return compressed_docs - - def rerank(self, query, docs): - reranker = self.load_rerank_model() - passages = [] - for doc in docs: - passages.append(str(doc.page_content)) - scores = reranker.compute_score([[query, passage] for passage in passages]) - sorted_pairs = sorted(zip(passages, scores), key=lambda x: x[1], reverse=True) - sorted_passages, sorted_scores = zip(*sorted_pairs) - return sorted_passages, sorted_scores - - -# def create_prompt(question, context): -# from langchain.prompts import PromptTemplate -# prompt_template = f"""请基于以下内容回答问题: - -# {context} - -# 问题: {question} -# 回答:""" -# prompt = PromptTemplate( -# template=prompt_template, input_variables=["context", "question"] -# ) -# logger.info(f'Prompt: {prompt}') -# return prompt - -def create_prompt(question, context): - prompt = f"""请基于以下内容: {context} 给出问题答案。问题如下: {question}。回答:""" - logger.info(f'Prompt: {prompt}') - return prompt - -def test_zhipu(prompt): - from zhipuai import ZhipuAI - api_key = "" # 填写您自己的APIKey - if api_key == "": - raise ValueError("请填写api_key") - client = ZhipuAI(api_key=api_key) - response = client.chat.completions.create( - model="glm-4", # 填写需要调用的模型名称 - messages=[ - {"role": "user", "content": prompt[:100]} - ], -) - print(response.choices[0].message) - if __name__ == "__main__": logger.info(data_dir) if not os.path.exists(data_dir): @@ -316,6 +247,4 @@ if __name__ == "__main__": logger.info("After reranking...") for i in range(len(scores)): logger.info(str(scores[i]) + '\n') - logger.info(passages[i]) - prompt = create_prompt(query, passages[0]) - test_zhipu(prompt) ## 如果显示'Server disconnected without sending a response.'可能是由于上下文窗口限制 \ No newline at end of file + logger.info(passages[i]) \ No newline at end of file diff --git a/rag/src/main.py b/rag/src/main.py index f324f50..6f926a2 100644 --- a/rag/src/main.py +++ b/rag/src/main.py @@ -13,8 +13,7 @@ from loguru import logger if __name__ == "__main__": query = """ - 我现在处于高三阶段,感到非常迷茫和害怕。我觉得自己从出生以来就是多余的,没有必要存在于这个世界。 - 无论是在家庭、学校、朋友还是老师面前,我都感到被否定。我非常难过,对高考充满期望但成绩却不理想 + 我现在经常会被别人催眠,做一些我不愿意做的事情,是什么原因? """ """