This commit is contained in:
xzw 2024-03-28 00:00:13 +08:00 committed by GitHub
commit a24eb52342
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 183 additions and 106 deletions

View File

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

0
agents/roles/__init__.py Normal file
View File

View File

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

View File

@ -8,7 +8,6 @@ from zhipuai import ZhipuAI
from metagpt.logs import logger
class BaiduAPI:
def __init__(self):
pass

View File

@ -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的经典评估框架通过以下三个方面进行评估:

View File

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

View File

@ -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.'可能是由于上下文窗口限制
logger.info(passages[i])

View File

@ -13,8 +13,7 @@ from loguru import logger
if __name__ == "__main__":
query = """
我现在处于高三阶段感到非常迷茫和害怕我觉得自己从出生以来就是多余的没有必要存在于这个世界
无论是在家庭学校朋友还是老师面前我都感到被否定我非常难过对高考充满期望但成绩却不理想
我现在经常会被别人催眠做一些我不愿意做的事情是什么原因
"""
"""