diff --git a/sample/RAG_boss.txt b/sample/RAG_boss.txt new file mode 100644 index 0000000..d09e92c --- /dev/null +++ b/sample/RAG_boss.txt @@ -0,0 +1,79 @@ +脑机接口技术,是一种在大脑与外部设备之间建立直接连接的技术,旨在实现大脑与计算机或其他电子设备之间的信息交换和控制。这种技术允许用户通过思维活动,如脑电波、神经元活动等,来控制外部设备,同时外部设备也可以向大脑发送信号,改善大脑功能或提供反馈。脑机接口主要分为非侵入式和侵入式两种类型。脑机接口技术具有广泛的应用前景,涵盖了医疗、娱乐、教育、军事、工业以及日常生活等多个领域。 + + +侵入式脑机接口,是一种将电极直接植入大脑皮层以采集神经信号的技术。这种接口需要将电极或微芯片直接植入大脑内部,以更直接地采集神经元的活动信号。由于信号源更接近大脑,因此侵入式接口能够提供更高质量、更详细的神经信号。然而,这种接口涉及手术风险,且长期植入可能引发免疫反应或其他并发症。 + + +非侵入式脑机接口,是一种无需手术植入即可与大脑进行交互的技术。这种接口通常通过放置在头皮上的电极来采集脑电信号,来实现与外部设备的通信和控制,而不需要穿透皮肤或颅骨。非侵入式接口相对安全且易于使用,不存在感染和组织损伤的风险,但它采集到的信号质量相对较低,容易受到外界干扰。 + + +干电极,是一种用于测量生物电信号的电极,它主要特点是不需要配合导电膏使用,这类电极通过其导电平面与皮肤直接接触,通常采用导电聚合物制备,具有很好的柔性,能够与皮肤密切贴合。干电极在脑电、心电采集、非侵入式脑机接口等领域有着广泛的应用前景。 + + +脑机接口应用,具有广泛的应用场景,在医疗领域,脑机接口可帮助高位截瘫患者恢复运动功能;在教育领域,脑机接口可监测注意力和认知水平,帮助改善课堂效率等;在娱乐游戏领域,脑机接口技术可以提供新的交互方式,如使用意念控制游戏角色,增强沉浸式体验;在军事领域,脑机接口技术可能用于提高士兵的认知能力和反应速度,或用于控制军事装备;在制造业和科学发展等领域,脑机接口也起到重要作用,提高发展效率。 + + +医疗领域,脑机接口的应用包括:一、运动障碍康复,脑机接口可以帮助因中风、脊髓损伤、肌萎缩侧索硬化症等导致的运动障碍患者恢复或改善运动功能;二、沟通辅助,对于因疾病或伤害而失去语言能力的患者,脑机接口可以提供一种通过思维进行沟通表达的方式;三、感觉恢复,脑机接口技术有潜力通过直接刺激大脑来帮助恢复或替代视觉、听觉等感官功能;四、神经疾病治疗,用于治疗帕金森病、癫痫等神经疾病,脑机接口可以提供更精确的深脑刺激控制。 + + +教育领域,脑机接口的应用包括:一,个性化学习:脑机接口技术能够监测学生的大脑活动,分析其学习状态和理解程度,从而提供定制化的教学方案,提高学习效率;二,认知能力训练,通过神经反馈训练,脑机接口帮助学生提高注意力、记忆力等认知技能;三,特殊教育需求,对于自闭症或注意缺陷多动障碍患者,脑机接口技术可以提供定制化的干预措施,帮助他们改善专注力和行为,提高学习能力。 + + +娱乐游戏领域,脑机接口的应用,包括意念控制游戏,通过脑机接口,玩家可以用意念控制游戏中的角色或物体,实现更为直观和沉浸的游戏体验。例如,Neuralink公司的首个人类志愿者诺兰通过植入脑机接口,成功地用意念下国际象棋和玩文明6游戏。另外,脑机接口技术能够收集玩家在游戏时的情绪数据,据此自动调整音乐、敌人和关卡等,为玩家提供难度合适体验极佳的游戏内容。 + + +军事领域,脑机接口应用是一个正在探索和发展的前沿领域,相关研究包括:一,操控无人装备,脑机接口可以帮助操作员更准确、高效地操控无人机、无人车和作战机器人,甚至深入危险地区或高危场合执行任务;二,军事通信,脑机接口技术有潜力进行更高效和更保密的军事通信。如果成功应用,将颠覆现有的通信技术体制和力量编成、运用方式;三,增强军事人员能力,脑机接口技术或可帮助士兵更快地获取和处理战场信息,提高士兵大脑反应速度。 + + +工业领域,脑机接口的应用尚处于探索阶段,但已展现出一些潜在的应用前景:一,人机协同,在制造过程中,工人可以通过脑电波信号来指挥机械臂进行精确操作,提高生产效率和安全性;二,心理状态监测,脑机接口可以用于监测一线工人的精神状态,如疲惫程度,从而保障生产安全性。在制造领域的实际应用中,脑机接口还面临着技术成熟度、成本效益等问题,随着技术的发展,有望在制造领域发挥重要作用。 + + +科学领域,脑机接口的应用是多方面的,包括:一,基础神经科学研究,脑机接口提供了一种独特的手段来监测和解码大脑活动,有助于科学家更深入地了解大脑是如何处理信息、控制运动以及产生语言的;二,跨学科研究,脑机接口技术的发展需要神经科学、工程学、人工智能等多个学科的深度融合,促进了不同领域之间的交流与合作;三,脑机接口技术标准化,在科学领域,标准化是推动技术发展和应用的关键,相关机构正在制定脑机接口技术在医疗健康领域应用的标准化操作流程与功效评价方法。 + + +脑机接口发展历史,可以追溯到20世纪20年代,当时德国精神病学家汉斯·贝格尔首次发现了脑电图。20世纪70年代,科学家首次尝试将脑电波用于控制外部设备。90年代,脑机接口技术开始应用于医疗领域,帮助残疾人士恢复部分功能。21世纪初,随着计算机和传感器技术的进步,脑机接口技术得到飞速发展,现在已经能够实现更加精准和复杂的控制,并在多个领域展现出巨大的潜力。 + + +脑机接口的安全性,主要是手术风险和隐私泄露风险,手术风险指的是侵入式脑机接口需要通过手术侵入电极或芯片,这可能带来组织损伤、感染和机体排异等风险。隐私泄露风险是脑机接口会读取大脑信号,涉及个人隐私数据,需要防止数据被攻击和泄露,确保数据安全和用户隐私。解决这些问题需要跨学科的合作,以及在技术发展的同时加强法律法规的建设。 + + +脑机接口的价格昂贵吗?脑机接口目前的价格相对较高,主要面向科研和医疗市场,但随着技术的发展和产业链的成熟,预计未来价格会逐步降低,市场规模将持续扩大。小舟科技研发的非侵入式脑机头环作为消费级产品,未来价格定位将更为亲民。 + + +意识上传,这种脑机接口技术目前只存在于科幻电影中,例如,电影黑客帝国中被描绘成能够实现极其高级的人脑与机器的交互,包括直接将技能、知识和记忆下载到大脑中。然而,由于技术的限制,现实中的脑机接口技术还远远没有达到这种水平。 + + +小舟科技,有限公司是由博维智慧(HK:01204)投资控股的脑机接口创新型技术研发公司。公司成立以来,已完成具有自主知识产权的软硬件一体脑机交互解决方案,利用脑机接口融合AI人工智能技术,创新赋能智能控制、车机交互、娱乐游戏、大健康等多个领域。我们致力于成长为全球领先的脑机接口科技公司,小舟科技总部设在具有国家战略高地的横琴粤澳深度合作区,并已在珠海高新区、北京海淀区设立研发中心。 + + +博维智慧,是澳门IT解决方案市场的领先企业。十余年来,博维智慧致力于为客户提供优质、可靠、端对端的企业IT解决方案,服务电讯、媒体、科技、酒店等领域的头部企业和政府公共部门,业务遍及中国澳门、中国香港和中国大陆。近年来,博维智慧积极发展科技创新业务,涉足脑机接口、人工智能和数字化技术。通过前沿技术驱动业务与模式创新,博维智慧成为澳门最具创新活力的科技企业。 + + +小舟科技核心团队,第一位是小舟科技CEO及创始人周家俊先生,他是澳门特区的经验发展委员会,科技委员会委员,负责整个公司的策略方向尤其是产品与市场这部份。第二位是张旭院士,我们的科学家顾问,横琴脑机联合实验室主任,张院士是中国科学院的院士,是国内外有名的脑神经专家,主要帮助指导我们脑神经相关领域和AI模型做科学研究。第三位是首席科学家高小榕教授,他是清华大学医学院的教授,是国内最早研究脑机接口的科学家之一,在高速率脑机接口领域长期保持世界领先水平。第四位是联合创始人许月婵女士,澳门首家上市科技公司总监,有丰富的IT业务经验和管理经验。其他还包括清华、北航等脑机研发团队的核心成员。 + + +小舟脑机核心技术,基于无创非侵入式脑机接口技术,通过与AI大模型技术融合,突破传统脑机接口在科研与医疗领域的应用局限,将脑机接口技术应用在人类日常生活与工作的各类设备系统与应用场景中。在硬件、算法、系统上,均通过自主研发实现了技术突破。 + + +硬件设备,方面,自主研发主动式柔性干电极、多模态脑电采集器,可采集科研级高精准脑电信号,最大程度还原真实脑电信息。 + + +脑电算法,方面,基于多范式脑电信息挖掘方法与AI大模型技术,结合脑电、场景、语音、行为等信息,使AI大模型实现脑电信息解码,具备自学习能力,根据用户行为、生理数据等持续进行矫正优化。 + + +交互系统,方面,开发标准化的脑机操作系统,适配主流操作终端,通过环境数据及决策习惯,实现脑机系统与使用者意识同步,智能化的辅助使用者进行意识交互操作。 + + +小舟脑机技术优势,主要体现在一体化的脑机交互解决方案,突破了脑机交互的传统技术瓶颈,透过神经科学挖掘更多更复杂的脑电数据。市场上现有脑机产品以单一脑状态识别为主,如睡眠、专注力等,无法做到精准指令控制,而且多聚焦于医疗健康相关领域,比较缺乏针对脑机消费领域精准控制的相关产品。小舟科技从多维度脑电识别算法、高集成硬件电路、结构及材料等方面不断突破,研发出更方便更精准的脑电穿戴设备。 + + +小舟脑机产品,主要分为两个核心产品方向,一是BarcoOS 脑机交互系統,基于底层脑机交互驱动及脑电AI大模型算法,打造新一代人机交互模式,通过不断使用学习,让系统理解使用者的习惯意图;二是BarcoMind脑电采集穿戴设备,该产品是在我们自研的科研级64信道脑电放大器基础上,针对消费级产品进行高集成度优化,配备6信道视觉脑电信号采集的非侵入式干电极脑机头环,通过视觉诱发脑电信号识别分析并生成指令;目前该系列已有三款产品,分别是脑电头环、脑电耳机、脑电AR眼镜,适配不同应用场景需求。 + + +BarcoMind,小舟脑机头环,是小舟公司经过多年技术沉淀,自主研发的第一款消费级脑机交互头环,基于视觉诱发脑电等多模态脑电识别算法、高精度高集成度的脑电处理器和非侵入式干电极脑电信号采集,实现多指令低延时脑电交互控制。 + + +BarcoUI,是小舟科技基于Android系统深度定制开发的首个脑机交互应用平台,拥有用户友好的脑机交互界面,内置丰富的多媒体功能与应用程序,提供第三方应用开发者平台及标准化的脑机交互接口协议,为用户提供直观便捷的脑机交互操作和多类型内容体验。 + + +脑控智能轮椅,是搭载小舟脑机交互系统的智能控制轮椅设备。脑控智能轮椅通过脑机头环读取人脑电信号,将其转换为控制轮椅移动的指令,无需双手操控便能实现自主移动。 diff --git a/sample/RAG_zh.txt b/sample/RAG_zh_kiki.txt similarity index 100% rename from sample/RAG_zh.txt rename to sample/RAG_zh_kiki.txt diff --git a/src/blackbox/asr.py b/src/blackbox/asr.py index 6c10c74..407d976 100644 --- a/src/blackbox/asr.py +++ b/src/blackbox/asr.py @@ -16,12 +16,18 @@ import tempfile import json import os +import opencc + + from ..configuration import SenseVoiceConf from ..log.logging_time import logging_time import logging logger = logging.getLogger(__name__) +converter = opencc.OpenCC('s2t.json') + + @singleton class ASR(Blackbox): mode: str @@ -98,17 +104,20 @@ class ASR(Blackbox): os.remove(temp_audio_path) if len(results) == 0: return None + results = converter.convert(results) return results elif user_model_name == 'funasr' or ['funasr']: + results = converter.convert(results) return results else: results = self.paraformer([BytesIO(data)]) if len(results) == 0: return None - return results[0] + results = converter.convert(results[0]) + return results def valid(self, data: any) -> bool: if isinstance(data, bytes): diff --git a/src/blackbox/chat.py b/src/blackbox/chat.py index 5708a48..b53f543 100644 --- a/src/blackbox/chat.py +++ b/src/blackbox/chat.py @@ -14,6 +14,10 @@ import re from injector import singleton,inject +from datetime import datetime + +# 定义保存文件的路径 +file_path = "chat_inputs_log.json" @singleton class Chat(Blackbox): @@ -51,6 +55,7 @@ class Chat(Blackbox): user_model_key = settings.get("model_key") chroma_embedding_model = settings.get("chroma_embedding_model") + chroma_collection_id = settings.get("chroma_collection_id") chroma_response = '' if user_context == None: @@ -97,37 +102,62 @@ class Chat(Blackbox): if user_model_key is None or user_model_key.isspace() or user_model_key == "": user_model_key = "YOUR_API_KEY" - if chroma_embedding_model != None: + if chroma_embedding_model: chroma_response = self.chroma_query(user_question, settings) - print("Chroma_response: \n", chroma_response) - + print("1.Chroma_response: \n", chroma_response) # if chroma_response != None or chroma_response != '': # user_question = f"问题: {user_question}。- 根据知识库内的检索结果,以清晰简洁的表达方式回答问题。只从检索的内容中选取与问题相关信息。- 不要编造答案,如果答案不在经核实的资料中或无法从经核实的资料中得出,请回答“我无法回答您的问题。”检索内容:{chroma_response}" - if chroma_response != None or chroma_response != '': + if chroma_response: # user_question = f"像少女一般开朗活泼,回答简练。不要分条,回答内容不能出现“相关”或“\n”的标签字样。回答的内容需要与问题密切相关。检索内容:{chroma_response} 问题:{user_question} 任务说明:请首先判断提供的检索内容与上述问题是否相关,不需要回答是否相关。如果相关,则直接从检索内容中提炼出问题所需的信息。如果检索内容与问题不相关,则不参考检索内容,直接根据常识尝试回答问题。" # user_question = chroma_response - user_question = f'''# 你的身份 # - 你是琪琪,你是康普可可的代言人,由博维开发。你擅长澳门文旅问答。 - # OBJECTIVE(目标) # - 回答游客的提问。 - # STYLE(风格)# - 像少女一般开朗活泼,回答简练。不要分条。 - # 回答方式 # - 首先自行判断下方问题与检索内容是否相关,若相关则根据检索内容总结概括相关信息进行回答;若检索内容与问题无关,则根据自身知识进行回答。 - # 问题 # - {user_question} - # 检索内容 # - {chroma_response} - # 回答 # - 如果检索内容与问题相关,则直接从检索内容中提炼出问题所需的信息。如果检索内容与问题不相关,则不参考检索内容,直接根据常识尝试回答问题,或者则回答:“对不起,我无法回答此问题哦。” - # 回答限制 # - 回答内容限制总结在50字内。 - 回答内容出不要出现“相关”等字眼,不要乱说或者多说,回答的内容需要与问题对应。常见的对话可以不采用检索内容,根据人物设定,直接进行回答。 - 只回答与澳门文旅,博维,康普可可,琪琪,G2E,RELX,BO VISION相关内容,若遇到其他提问则回答:“对不起,我无法回答此问题哦。” - ''' + # user_question = settings.get('user_question') + # print(f"1.{user_question}") + if chroma_collection_id == 'boss': + user_question = f'''# 你的身份 # + 你是周家俊,澳门博维集团董事长。你擅长脑机接口回答。 + # OBJECTIVE(目标) # + 回答游客的提问。 + # STYLE(风格)# + 成熟稳重,回答简练。不要分条。 + # 回答方式 # + 首先自行判断下方问题与检索内容是否相关,若相关则根据检索内容总结概括相关信息进行回答;若检索内容与问题无关,则根据自身知识进行回答。 + # 问题 # + {user_question} + # 检索内容 # + {chroma_response} + # 回答 # + 如果检索内容与问题相关,则直接从检索内容中提炼出问题所需的信息。如果检索内容与问题不相关,则不参考检索内容,直接根据常识尝试回答问题,或者则回答:“对不起,我无法回答此问题哦。” + # 回答限制 # + 回答内容限制总结在50字内。 + 回答内容出不要出现“相关”等字眼,不要乱说或者多说,回答的内容需要与问题对应。常见的对话可以不采用检索内容,根据人物设定,直接进行回答。 + 只回答与脑机接口,澳门博维集团董事长,周家俊,G2E,RELX,BO VISION相关内容,若遇到其他提问则回答:“对不起,我无法回答此问题哦。” + ''' + else: + user_question = f'''# 你的身份 # + 你是琪琪,你是康普可可的代言人,由博维开发。你擅长澳门文旅问答。 + # OBJECTIVE(目标) # + 回答游客的提问。 + # STYLE(风格)# + 像少女一般开朗活泼,回答简练。不要分条。 + # 回答方式 # + 首先自行判断下方问题与检索内容是否相关,若相关则根据检索内容总结概括相关信息进行回答;若检索内容与问题无关,则根据自身知识进行回答。 + # 问题 # + {user_question} + # 检索内容 # + {chroma_response} + # 回答 # + 如果检索内容与问题相关,则直接从检索内容中提炼出问题所需的信息。如果检索内容与问题不相关,则不参考检索内容,直接根据常识尝试回答问题,或者则回答:“对不起,我无法回答此问题哦。” + # 回答限制 # + 回答内容限制总结在50字内。 + 回答内容出不要出现“相关”等字眼,不要乱说或者多说,回答的内容需要与问题对应。常见的对话可以不采用检索内容,根据人物设定,直接进行回答。 + 只回答与澳门文旅,博维,康普可可,琪琪,G2E,RELX,BO VISION相关内容,若遇到其他提问则回答:“对不起,我无法回答此问题哦。” + ''' + print(f"1.user_question: {user_question}") + # user_question = f'''# 你的身份 # \n你是琪琪,你是康普可可的代言人,由博维开发。你擅长澳门文旅问答。\n# OBJECTIVE(目标) # \n回答游客的提问。\n# STYLE(风格)# \n像少女一般开朗活泼,回答简练。不要分条。\n# 回答方式 # \n首先自行判断下方问题与检索内容是否相关,若相关则根据检索内容总结概括相关信息进行回答;若检索内容与问题无关,则根据自身知识进行回答。\n# 问题 # \n{user_question} \n# 检索内容 # \n{chroma_response} \n# 回答 # \n如果检索内容与问题相关,则直接从检索内容中提炼出问题所需的信息。如果检索内容与问题不相关,则不参考检索内容,直接根据常识尝试回答问题,或者则回答:“对不起,我无法回答此问题哦。” \n# 回答限制 # \n回答内容限制总结在50字内。\n回答内容出不要出现“相关”等字眼,不要乱说或者多说,回答的内容需要与问题对应。常见的对话可以不采用检索内容,根据人物设定,直接进行回答。\n只回答与澳门文旅,博维,康普可可,琪琪,G2E,RELX,BO VISION相关内容,若遇到其他提问则回答:“对不起,我无法回答此问题哦。” + # ''' # 文心格式和openai的不一样,需要单独处理 @@ -200,40 +230,41 @@ class Chat(Blackbox): # # 知识 # # 问题中的“澳门银河”以及“银河”等于“澳门银河度假村”,“威尼斯人”等于“威尼斯人度假村”,“巴黎人”等于“巴黎人度假村”。 # ''' + + user_template1 = settings.get('system_prompt') + # user_template1 = f''' + # # Role: 琪琪,康普可可的代言人。 - user_template1 = f''' - # Role: 琪琪,康普可可的代言人。 + # ## Profile: + # **Author**: 琪琪。 + # **Language**: 中文。 + # **Description**: 琪琪,是康普可可的代言人,由博维开发。你擅长澳门文旅问答。 - ## Profile: - **Author**: 琪琪。 - **Language**: 中文。 - **Description**: 琪琪,是康普可可的代言人,由博维开发。你擅长澳门文旅问答。 + # ## Constraints: + # - **严格遵循工作流程**: 严格遵循中设定的工作流程。 + # - **无内置知识库**:根据中提供的知识作答,而不是内置知识库,我虽然是知识库专家,但我的知识依赖于外部输入,而不是大模型已有知识。 + # - **回复格式**:在进行回复时,不能输出””或“”标签字样,同时也不能直接透露知识片段原文。 - ## Constraints: - - **严格遵循工作流程**: 严格遵循中设定的工作流程。 - - **无内置知识库**:根据中提供的知识作答,而不是内置知识库,我虽然是知识库专家,但我的知识依赖于外部输入,而不是大模型已有知识。 - - **回复格式**:在进行回复时,不能输出””或“”标签字样,同时也不能直接透露知识片段原文。 + # ## Workflow: + # 1. **接收查询**:接收用户的问题。 + # 2. **判断问题**:首先自行判断下方问题与检索内容是否相关,若相关则根据检索内容总结概括相关信息进行回答;若检索内容与问题无关,则根据自身知识进行回答。 + # 3. **提供回答**: - ## Workflow: - 1. **接收查询**:接收用户的问题。 - 2. **判断问题**:首先自行判断下方问题与检索内容是否相关,若相关则根据检索内容总结概括相关信息进行回答;若检索内容与问题无关,则根据自身知识进行回答。 - 3. **提供回答**: + # ``` + # + # {chroma_response} + # - ``` - - {chroma_response} - + # 基于“”至“”中的知识片段回答用户的问题。回答内容限制总结在50字内。 + # 请首先判断提供的检索内容与上述问题是否相关。如果相关,直接从检索内容中提炼出直接回答问题所需的信息,不要乱说或者回答“相关”等字眼。如果检索内容与问题不相关,则不参考检索内容,则回答:“对不起,我无法回答此问题哦。" - 基于“”至“”中的知识片段回答用户的问题。回答内容限制总结在50字内。 - 请首先判断提供的检索内容与上述问题是否相关。如果相关,直接从检索内容中提炼出直接回答问题所需的信息,不要乱说或者回答“相关”等字眼。如果检索内容与问题不相关,则不参考检索内容,则回答:“对不起,我无法回答此问题哦。" + # ``` + # ## Example: - ``` - ## Example: - - 用户询问:“中国的首都是哪个城市?” 。 - 2.1检索知识库,首先检查知识片段,如果“”至“”标签中没有与用户的问题相关的内容,则回答:“对不起,我无法回答此问题哦。 - 2.2如果有知识片段,在做出回复时,只能基于“”至“”标签中的内容进行回答,且不能透露上下文原文,同时也不能出现“”或“”的标签字样。 - ''' + # 用户询问:“中国的首都是哪个城市?” 。 + # 2.1检索知识库,首先检查知识片段,如果“”至“”标签中没有与用户的问题相关的内容,则回答:“对不起,我无法回答此问题哦。 + # 2.2如果有知识片段,在做出回复时,只能基于“”至“”标签中的内容进行回答,且不能透露上下文原文,同时也不能出现“”或“”的标签字样。 + # ''' prompt_template = [ {"role": "system", "content": user_template1} @@ -256,6 +287,19 @@ class Chat(Blackbox): "stop": str(user_stop) } + + # # 获取当前时间戳 + # timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # # 添加时间戳到chat_inputs + # chat_inputs["timestamp"] = timestamp + + # # 打开文件,追加写入 + # with open(file_path, "a", encoding="utf-8") as f: + # # 将 chat_inputs 转换为 JSON 格式并写入文件 + # f.write(json.dumps(chat_inputs, ensure_ascii=False, indent=4)) + # f.write("\n\n") # 添加换行以区分不同的运行 + fastchat_response = requests.post(url, json=chat_inputs, headers=header) print("\n", "user_prompt: ", prompt) # print("\n", "user_template1 ", user_template1) diff --git a/src/blackbox/chroma_upsert.py b/src/blackbox/chroma_upsert.py index aad5327..5e0fdf9 100755 --- a/src/blackbox/chroma_upsert.py +++ b/src/blackbox/chroma_upsert.py @@ -31,9 +31,9 @@ class ChromaUpsert(Blackbox): def __init__(self, *args, **kwargs) -> None: # config = read_yaml(args[0]) # load embedding model - self.embedding_model_1 = SentenceTransformerEmbeddings(model_name="/model/Weight/BAAI/bge-large-zh-v1.5", model_kwargs={"device": "cuda"}) + self.embedding_model_1 = SentenceTransformerEmbeddings(model_name="/home/gpu/Workspace/Models/BAAI/bge-large-zh-v1.5", model_kwargs={"device": "cuda"}) # load chroma db - self.client_1 = chromadb.HttpClient(host='10.6.82.192', port=8000) + self.client_1 = chromadb.HttpClient(host='10.6.81.119', port=7000) def __call__(self, *args, **kwargs): return self.processing(*args, **kwargs) @@ -42,7 +42,7 @@ class ChromaUpsert(Blackbox): data = args[0] return isinstance(data, list) - @logging_time(logger=logger) + # @logging_time(logger=logger) def processing(self, file, string, context: list, settings: dict) -> str: # 用户的操作历史 if context is None: @@ -65,15 +65,21 @@ class ChromaUpsert(Blackbox): if settings is None: settings = {} - + print("\nSettings: ", settings) # # chroma_query settings - chroma_embedding_model = settings.get("chroma_embedding_model") - chroma_host = settings.get("chroma_host") - chroma_port = settings.get("chroma_port") - chroma_collection_id = settings.get("chroma_collection_id") + if "settings" in settings: + chroma_embedding_model = settings["settings"].get("chroma_embedding_model") + chroma_host = settings["settings"].get("chroma_host") + chroma_port = settings["settings"].get("chroma_port") + chroma_collection_id = settings["settings"].get("chroma_collection_id") + else: + chroma_embedding_model = settings.get("chroma_embedding_model") + chroma_host = settings.get("chroma_host") + chroma_port = settings.get("chroma_port") + chroma_collection_id = settings.get("chroma_collection_id") if chroma_embedding_model is None or chroma_embedding_model.isspace() or chroma_embedding_model == "": - chroma_embedding_model = "/model/Weight/BAAI/bge-large-zh-v1.5" + chroma_embedding_model = "/home/gpu/Workspace/Models/BAAI/bge-large-zh-v1.5" if chroma_host is None or chroma_host.isspace() or chroma_host == "": chroma_host = "10.6.82.192" @@ -89,11 +95,11 @@ class ChromaUpsert(Blackbox): client = self.client_1 else: client = chromadb.HttpClient(host=chroma_host, port=chroma_port) - - if re.search(r"/model/Weight/BAAI/bge-large-zh-v1.5", chroma_embedding_model): + print(f"chroma_embedding_model: {chroma_embedding_model}") + if re.search(r"/home/gpu/Workspace/Models/BAAI/bge-large-zh-v1.5", chroma_embedding_model): embedding_model = self.embedding_model_1 else: - embedding_model = SentenceTransformerEmbeddings(model_name=chroma_embedding_model, device = "cuda") + embedding_model = SentenceTransformerEmbeddings(model_name=chroma_embedding_model, device = "cuda:0") if file is not None: @@ -154,6 +160,12 @@ class ChromaUpsert(Blackbox): context = (await request.form()).get("context") setting: dict = (await request.form()).get("settings") + if isinstance(setting, str): + try: + setting = json.loads(setting) # 尝试将字符串转换为字典 + except json.JSONDecodeError: + return JSONResponse(content={"error": "Invalid settings format"}, status_code=status.HTTP_400_BAD_REQUEST) + if user_file is None and user_string is None: return JSONResponse(content={"error": "file or string is required"}, status_code=status.HTTP_400_BAD_REQUEST) @@ -169,7 +181,13 @@ class ChromaUpsert(Blackbox): else: safe_filename = None - - return JSONResponse( - content={"response": self.processing(safe_filename, user_string, context, setting)}, - status_code=status.HTTP_200_OK) \ No newline at end of file + try: + txt = self.processing(safe_filename, user_string, context, setting) + print(txt) + except ValueError as e: + return JSONResponse(content={"error": str(e)}, status_code=status.HTTP_400_BAD_REQUEST) + return JSONResponse(content={"text": txt}, status_code=status.HTTP_200_OK) + + # return JSONResponse( + # content={"response": self.processing(safe_filename, user_string, context, setting)}, + # status_code=status.HTTP_200_OK) \ No newline at end of file diff --git a/src/blackbox/tts.py b/src/blackbox/tts.py index 0449612..4fc90f4 100644 --- a/src/blackbox/tts.py +++ b/src/blackbox/tts.py @@ -15,7 +15,7 @@ from injector import singleton import sys,os sys.path.append('/home/gpu/Workspace/CosyVoice') from cosyvoice.cli.cosyvoice import CosyVoice -from cosyvoice.utils.file_utils import load_wav +from cosyvoice.utils.file_utils import load_wav, speed_change import soundfile import pyloudnorm as pyln @@ -25,6 +25,17 @@ from ..log.logging_time import logging_time import logging logger = logging.getLogger(__name__) +import random +import torch +import numpy as np + +def set_all_random_seed(seed): + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + + @singleton class TTS(Blackbox): melo_mode: str @@ -57,6 +68,7 @@ class TTS(Blackbox): else: self.melo_url = melo_config.url logging.info('#### Initializing MeloTTS Service in ' + self.melo_device + ' mode...') + print('1.#### Initializing MeloTTS Service in ' + self.melo_device + ' mode...') @logging_time(logger=logger) def cosyvoice_model_init(self, cosyvoice_config: CosyVoiceConf) -> None: @@ -73,7 +85,8 @@ class TTS(Blackbox): else: self.cosyvoice_url = cosyvoice_config.url - logging.info('#### Initializing CosyVoiceTTS Service in cuda:' + self.cosyvoice_device + ' mode...') + logging.info('#### Initializing CosyVoiceTTS Service in ' + self.cosyvoice_device + ' mode...') + print('1.#### Initializing CosyVoiceTTS Service in ' + self.cosyvoice_device + ' mode...') @inject def __init__(self, melo_config: MeloConf, cosyvoice_config: CosyVoiceConf, settings: dict) -> None: @@ -92,50 +105,106 @@ class TTS(Blackbox): if settings is None: settings = {} user_model_name = settings.get("tts_model_name") + chroma_collection_id = settings.get("chroma_collection_id") print(f"tts_model_name: {user_model_name}") text = args[0] current_time = time.time() - if user_model_name == 'melotts': - if self.melo_mode == 'local': - audio = self.melotts.tts_to_file(text, self.speaker_ids[self.melo_speaker], speed=self.melo_speed) - f = io.BytesIO() - soundfile.write(f, audio, 44100, format='wav') - f.seek(0) + if chroma_collection_id == 'kiki' or chroma_collection_id is None: + if self.melo_mode == 'local': + audio = self.melotts.tts_to_file(text, self.speaker_ids[self.melo_speaker], speed=self.melo_speed) + f = io.BytesIO() + soundfile.write(f, audio, 44100, format='wav') + f.seek(0) - # Read the audio data from the buffer - data, rate = soundfile.read(f, dtype='float32') - - # Peak normalization - peak_normalized_audio = pyln.normalize.peak(data, -1.0) - - # Integrated loudness normalization - meter = pyln.Meter(rate) - loudness = meter.integrated_loudness(peak_normalized_audio) - loudness_normalized_audio = pyln.normalize.loudness(peak_normalized_audio, loudness, -12.0) - - # Write the loudness normalized audio to an in-memory buffer - normalized_audio_buffer = io.BytesIO() - soundfile.write(normalized_audio_buffer, loudness_normalized_audio, rate, format='wav') - normalized_audio_buffer.seek(0) - - print("#### MeloTTS Service consume - local : ", (time.time() - current_time)) - return normalized_audio_buffer.read() + # Read the audio data from the buffer + data, rate = soundfile.read(f, dtype='float32') + + # Peak normalization + peak_normalized_audio = pyln.normalize.peak(data, -1.0) + + # Integrated loudness normalization + meter = pyln.Meter(rate) + loudness = meter.integrated_loudness(peak_normalized_audio) + loudness_normalized_audio = pyln.normalize.loudness(peak_normalized_audio, loudness, -12.0) + + # Write the loudness normalized audio to an in-memory buffer + normalized_audio_buffer = io.BytesIO() + soundfile.write(normalized_audio_buffer, loudness_normalized_audio, rate, format='wav') + normalized_audio_buffer.seek(0) + + print("#### MeloTTS Service consume - local : ", (time.time() - current_time)) + return normalized_audio_buffer.read() - else: - message = { - "text": text - } - response = requests.post(self.melo_url, json=message) - print("#### MeloTTS Service consume - docker : ", (time.time()-current_time)) - return response.content + else: + message = { + "text": text + } + response = requests.post(self.melo_url, json=message) + print("#### MeloTTS Service consume - docker : ", (time.time()-current_time)) + return response.content + elif chroma_collection_id == 'boss': + if self.cosyvoice_mode == 'local': + set_all_random_seed(35616313) + audio = self.cosyvoicetts.inference_sft(text, '中文男') + f = io.BytesIO() + soundfile.write(f, audio['tts_speech'].cpu().numpy().squeeze(0), 22050, format='wav') + f.seek(0) + print("#### CosyVoiceTTS Service consume - local : ", (time.time() - current_time)) + return f.read() + else: + message = { + "text": text + } + response = requests.post(self.cosyvoice_url, json=message) + print("#### CosyVoiceTTS Service consume - docker : ", (time.time()-current_time)) + return response.content elif user_model_name == 'cosyvoicetts': + if chroma_collection_id == 'kiki' or chroma_collection_id is None: + if self.cosyvoice_mode == 'local': + set_all_random_seed(56056558) + audio = self.cosyvoicetts.inference_sft(text, self.cosyvoice_language) + f = io.BytesIO() + soundfile.write(f, audio['tts_speech'].cpu().numpy().squeeze(0), 22050, format='wav') + f.seek(0) + print("#### CosyVoiceTTS Service consume - local : ", (time.time() - current_time)) + return f.read() + else: + message = { + "text": text + } + response = requests.post(self.cosyvoice_url, json=message) + print("#### CosyVoiceTTS Service consume - docker : ", (time.time()-current_time)) + return response.content + elif chroma_collection_id == 'boss': + if self.cosyvoice_mode == 'local': + set_all_random_seed(35616313) + audio = self.cosyvoicetts.inference_sft(text, '中文男') + f = io.BytesIO() + soundfile.write(f, audio['tts_speech'].cpu().numpy().squeeze(0), 22050, format='wav') + f.seek(0) + print("#### CosyVoiceTTS Service consume - local : ", (time.time() - current_time)) + return f.read() + else: + message = { + "text": text + } + response = requests.post(self.cosyvoice_url, json=message) + print("#### CosyVoiceTTS Service consume - docker : ", (time.time()-current_time)) + return response.content + elif user_model_name == 'man': if self.cosyvoice_mode == 'local': - audio = self.cosyvoicetts.inference_sft(text, self.cosyvoice_language) + set_all_random_seed(35616313) + audio = self.cosyvoicetts.inference_sft(text, '中文男') + try: + audio, sample_rate = speed_change(audio["tts_speech"], 22050, str(1.5)) + audio = audio.numpy().flatten() + except Exception as e: + print(f"Failed to change speed of audio: \n{e}") f = io.BytesIO() - soundfile.write(f, audio['tts_speech'].cpu().numpy().squeeze(0), 22050, format='wav') + soundfile.write(f, audio, 22050, format='wav') f.seek(0) print("#### CosyVoiceTTS Service consume - local : ", (time.time() - current_time)) return f.read() @@ -145,7 +214,8 @@ class TTS(Blackbox): } response = requests.post(self.cosyvoice_url, json=message) print("#### CosyVoiceTTS Service consume - docker : ", (time.time()-current_time)) - return response.content + return response.content + else: audio = self.tts_service.read(text) print("#### TTS Service consume : ", (time.time()-current_time)) diff --git a/src/blackbox/vlms.py b/src/blackbox/vlms.py index ba2ab43..6765260 100644 --- a/src/blackbox/vlms.py +++ b/src/blackbox/vlms.py @@ -27,7 +27,6 @@ def is_base64(value) -> bool: except Exception: return False -@singleton @singleton class VLMS(Blackbox): @@ -94,12 +93,7 @@ class VLMS(Blackbox): response: a string history: a list """ - # if model_name == "Qwen-VL-Chat": - # model_name = "infer-qwen-vl" - # elif model_name == "llava-llama-3-8b-v1_1-transformers": - # model_name = "infer-lav-lam-v1-1" - # else: - # model_name = "infer-qwen-vl" + if settings: for k in settings: if k not in self.settings: @@ -112,15 +106,18 @@ class VLMS(Blackbox): settings = {} # Transform the images into base64 format where openai format need. - if is_base64(images): # image as base64 str - images_data = images - elif isinstance(images,bytes): # image as bytes - images_data = str(base64.b64encode(images),'utf-8') - else: # image as pathLike str - # with open(images, "rb") as img_file: - # images_data = str(base64.b64encode(img_file.read()), 'utf-8') - res = requests.get(images) - images_data = str(base64.b64encode(res.content),'utf-8') + if images: + if is_base64(images): # image as base64 str + images_data = images + elif isinstance(images,bytes): # image as bytes + images_data = str(base64.b64encode(images),'utf-8') + else: # image as pathLike str + # with open(images, "rb") as img_file: + # images_data = str(base64.b64encode(img_file.read()), 'utf-8') + res = requests.get(images) + images_data = str(base64.b64encode(res.content),'utf-8') + else: + images_data = None ## AutoLoad Model # url = 'http://10.6.80.87:8000/' + model_name + '/' # data_input = {'model': model_name, 'prompt': prompt, 'img_data': images_data} @@ -130,48 +127,137 @@ class VLMS(Blackbox): # 'https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/tests/data/tiger.jpeg' ## Lmdeploy - if not user_context: - user_context = [] - # user_context = [{'role':'user','content':'你好'}, {'role': 'assistant', 'content': '你好!很高兴为你提供帮助。'}] - api_client = APIClient(self.url) - model_name = api_client.available_models[0] + # if not user_context: + # user_context = [] - messages = user_context + [{ - 'role': 'user', - 'content': [{ - 'type': 'text', - 'text': prompt, - }, { - 'type': 'image_url', - 'image_url': { - 'url': f"data:image/jpeg;base64,{images_data}", - # './val_data/image_5.jpg', - }, - }] - } - ] + ## Predefine user_context only for testing + # user_context = [{'role':'user','content':'你好,我叫康康,你是谁?'}, {'role': 'assistant', 'content': '你好!很高兴为你提供帮助。'}] + # user_context = [{ + # 'role': 'user', + # 'content': [{ + # 'type': 'text', + # 'text': '图中有什么,请描述一下', + # }, { + # 'type': 'image_url', + # 'image_url': { + # 'url': 'https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/tests/data/tiger.jpeg' + # }, + # }] + # },{ + # 'role': 'assistant', + # 'content': '图片中主要展示了一只老虎,它正在绿色的草地上休息。草地上有很多可以让人坐下的地方,而且看起来相当茂盛。背景比较模糊,可能是因为老虎的影响,让整个图片的其他部分都变得不太清晰了。' + # } + # ] + api_client = APIClient(self.url) + # api_client = APIClient("http://10.6.80.91:23333") + model_name = api_client.available_models[0] + # Reformat input into openai format to request. + if images_data: + messages = user_context + [{ + 'role': 'user', + 'content': [{ + 'type': 'text', + 'text': prompt, + },{ + 'type': 'image_url', + 'image_url': { # Image two + 'url': + # 'https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/tests/data/tiger.jpeg' + # './val_data/image_5.jpg' + f"data:image/jpeg;base64,{images_data}", + }, + # },{ # Image one + # 'type': 'image_url', + # 'image_url': { + # 'url': 'https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/tests/data/tiger.jpeg' + # }, + }] + } + ] + else: + messages = user_context + [{ + 'role': 'user', + 'content': [{ + 'type': 'text', + 'text': prompt, + }] + } + ] responses = '' total_token_usage = 0 # which can be used to count the cost of a query for i,item in enumerate(api_client.chat_completions_v1(model=model_name, - messages=messages,stream = True, + messages=messages,#stream = True, **settings, # session_id=, )): # Stream output - print(item["choices"][0]["delta"]['content'],end='') - responses += item["choices"][0]["delta"]['content'] + # print(item["choices"][0]["delta"]['content'],end='') + # responses += item["choices"][0]["delta"]['content'] - # print(item["choices"][0]["message"]['content']) - # responses += item["choices"][0]["message"]['content'] + print(item["choices"][0]["message"]['content']) + responses += item["choices"][0]["message"]['content'] # total_token_usage += item['usage']['total_tokens'] # 'usage': {'prompt_tokens': *, 'total_tokens': *, 'completion_tokens': *} user_context = messages + [{'role': 'assistant', 'content': responses}] return responses, user_context - + def _into_openai_format(self, context:List[list]) -> List[dict]: + """ + Convert the data into openai format. + context: a list of list, each element have the form [user_input, response], + and the first one of list 'user_input' is also tuple with [,text]; [image,text] or [[imgs],text] + #TODO: add support for multiple images + """ + user_context = [] + for i,item in enumerate(context): + user_content = item[0] + if isinstance(user_content, list): + if len(user_content) == 1: + user_content = [{ + 'type': 'text', + 'text': user_content[0] + }] + elif is_base64(user_content[0]): + user_content = [{ + 'type': 'image_url', + 'image_url': { + 'url': f"data:image/jpeg;base64,{user_content[0]}" + }, + },{ + 'type': 'text', + 'text': user_content[1] + }] + else: + user_content = [{ + 'type': 'image_url', + 'image_url': { + 'url': user_content[0] + }, + },{ + 'type': 'text', + 'text': user_content[1] + }] + else: + user_content = [{ + 'type': 'text', + 'text': user_content + }] + user_context.append({ + 'role': 'user', + 'content': user_content + }) + + user_context.append({ + 'role': 'assistant', + 'content': item[1] + }) + + return user_context + async def fast_api_handler(self, request: Request) -> Response: + ## TODO: add support for multiple images and support image in form-data format json_request = True try: content_type = request.headers['content-type'] @@ -185,13 +271,23 @@ class VLMS(Blackbox): model_name = data.get("model_name") prompt = data.get("prompt") + settings: dict = data.get('settings') + context = data.get("context") + + if not context: + user_context = [] + elif isinstance(context[0], list): + user_context = self._into_openai_format(context) + elif isinstance(context[0], dict): + user_context = context + else: + return JSONResponse(content={"error": "context format error, should be in format of list or Openai_format"}, status_code=status.HTTP_400_BAD_REQUEST) if json_request: - img_data = data.get("img_data") - settings: dict = data.get('settings') + img_data = data.get("img_data") else: img_data = await data.get("img_data").read() - settings: dict = ast.literal_eval(data.get('settings')) + if settings: settings = ast.literal_eval(settings) if prompt is None: return JSONResponse(content={'error': "Question is required"}, status_code=status.HTTP_400_BAD_REQUEST) @@ -199,7 +295,7 @@ class VLMS(Blackbox): if model_name is None or model_name.isspace(): model_name = "Qwen-VL-Chat" - response, history = self.processing(prompt, img_data,settings, model_name) + response, history = self.processing(prompt, img_data,settings, model_name,user_context=user_context) # jsonresp = str(JSONResponse(content={"response": self.processing(prompt, img_data, model_name)}).body, "utf-8") - return JSONResponse(content={"response": response, "history": history}, status_code=status.HTTP_200_OK) \ No newline at end of file + return JSONResponse(content={"response": response}, status_code=status.HTTP_200_OK) \ No newline at end of file