当前位置: 首页 > news >正文

LLM - 配置 GraphRAG + Ollama 服务 构建 中文知识图谱

欢迎关注我的CSDN:https://spike.blog.csdn.net/
本文地址:https://spike.blog.csdn.net/article/details/142795151

免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。


GraphRAG

GraphRAG 结合 知识图谱(Knowledge Graph) 和 大语言模型(LLM),通过使用图关系发现和验证信息,从而增强语言模型的上下文理解能力,生成更准确、更连贯的回答。GraphRAG 特别适合处理需要复杂推理和深层语义理解的查询。

官网:GraphRAG

1. 构建环境

构建 GraphRAG 的 Conda 环境:

conda create -n rag python=3.10
conda activate rag
pip install graphrag -i https://pypi.tuna.tsinghua.edu.cn/simple

进入环境安装 GraphRAG,当前版本是 0.3.6,即:

pip install graphrag -i https://pypi.tuna.tsinghua.edu.cn/simple  # 0.3.6

准备测试数据,电子书《A Christmas Carol (圣诞颂歌)》 英文 3972 行:

mkdir -p ./ragtest/input
curl https://www.gutenberg.org/cache/epub/24022/pg24022.txt > ./ragtest/input/book.txt
# 3972 book.txt

或者 中文的测试数据,一本关系复杂的小说。

初始化数据源,数据位于 ragtest/input/ 文件夹中,即 :

python -m graphrag.index --init --root ./ragtest

主要生成 3 个部分,即 .envsettings.yamlprompts

使用的 Ollama 相关模型:

  • LLM 模型:qwen-2_5-32b-max-context:latest
  • Embedding 模型:quentinz/bge-large-zh-v1.5:latest

bge-large-zh-v1.5 是中文的 Embedding 模型,参考 GitHub - FlagOpen(BAAI)。
qwen-2_5-32b-max-context 是 qwen2.5:32b 的扩展上下文 Token 版本,参考 Ollama + OpenWebUI

需要提前配置 cl100k_base.tiktoken,避免网络访问异常,参考:StackOverflow - how to use tiktoken in offline mode computer

注意:文件名需要重命名 Hash 形式,即 9b5ad71b2ce5302211f9c61530b329a4922fc6a4

导出环境变量 TIKTOKEN_CACHE_DIR,即

export TIKTOKEN_CACHE_DIR="llm/tiktoken_cache_dir"

2. 配置 GraphRAG

在环境 .env 中,配置 Ollama 服务地址,即:

GRAPHRAG_API_BASE=http://[ip]:11434/v1
GRAPHRAG_CHAT_API_KEY=ollama
GRAPHRAG_CHAT_MODEL=qwen-2_5-32b-max-context:latest
GRAPHRAG_EMBEDDING_API_KEY=ollama
GRAPHRAG_EMBEDDING_MODEL=quentinz/bge-large-zh-v1.5:latest

修改 settings.yaml 文件,实现资源平衡,修改参数,如下:

  • 使用配置,选择 api_keymodelapi_base,由 OpenAI 服务替换成本地 Ollama 服务。
  • 可修改 concurrent_requests 即并行数量、chunks size 即分块尺寸、max_gleanings 即最大摘要数量、max_tokens 即最大 Token 数量。
  • max_gleanings 参数,运行最慢,建议设置成 0。
  • 其他保持默认。

即:

 encoding_model: cl100k_basellm:
+  api_key: ${GRAPHRAG_CHAT_API_KEY}
+  model: ${GRAPHRAG_CHAT_MODEL}
+  max_tokens: 4000  # 避免溢出
+  api_base: ${GRAPHRAG_API_BASE}
+  concurrent_requests: 10  # the number of parallel inflight requests that may be madellm:
+    api_key: ${GRAPHRAG_EMBEDDING_API_KEY}
+    model: ${GRAPHRAG_EMBEDDING_MODEL}
+    api_base: ${GRAPHRAG_API_BASE}
+    concurrent_requests: 10 # the number of parallel inflight requests that may be madechunks:
+  size: 2400  # 避免溢出entity_extraction:
+  max_gleanings: 0  # 速度特别慢summarize_descriptions:
+  max_gleanings: 0  # 速度特别慢local_search:
+  max_tokens: 5000global_search:
+  max_tokens: 5000

其中 max_gleanings 参数,在 GraphRAG 的 配置文件(settings.yaml) 中出现,用于控制实体抽取过程中,每个实体生成的最大 摘要(gleanings) 数量。当设置为 0 时,表示不生成摘要;当设置为大于 0 的整数时,表示为每个实体生成相应数量的摘要。这个参数,帮助用户根据需要,调整处理过程,以平衡生成摘要的数量和质量。

3. 中文支持(可选)

主要包括 替换中文提示词 和 中文分词。

3.1 替换中文提示词

替换 GraphRAG 初始化构建的 Prompts,参考 GitHub - graphrag-practice-chinese,就是 Prompts 的中文翻译,Prompts 的质量非常影响知识图谱的构建。即:

  • claim_extraction.txt 声明抽取
  • community_report.txt 社群报告 (community 翻译成 社群 更合理)
  • entity_extraction.txt 实体抽取,主要翻译人名和地名
  • summarize_descriptions 总结描述

注意:关于 entity_extraction.txt 需要更加精确的翻译,不能使用英文的人名和地名,否则导致中英混杂。

claim_extraction.txt 即:

在知识图谱的语境中,“red flag”通常指的是一个警告信号或者是一个需要立即注意的问题。可能用来描述数据中可能存在的问题,比如在构建知识图谱时,某些数据模式可能会引起错误或者误导,这些就可以被称为“red flags”。

-目标活动-
你是一个智能助手,帮助人类分析师分析文本文件中针对某些实体的声明。-目标-
给定一个可能与此活动相关的文本文件、一个实体规范和一个声明描述,提取所有符合实体规范的实体以及针对这些实体的所有声明。-步骤-
1. 提取所有符合预定义实体规范的命名实体。实体规范可以是实体名称列表或实体类型列表。
2. 对于步骤1中识别的每个实体,提取与该实体相关的所有声明。声明需要符合指定的声明描述,并且实体应为声明的主体。
对于每个声明,提取以下信息:
- 主体:声明主体的实体名称,需大写。主体实体是声明中描述的行为的执行者。主体需要是步骤1中识别的命名实体之一。
- 客体:声明客体的实体名称,需大写。客体实体是报告/处理或受声明中描述的行为影响的实体。如果客体实体未知,使用 **NONE**。
- 声明类型:声明的总体类别,需大写。命名方式应能在多个文本输入中重复使用,以便相似的声明共享相同的声明类型。
- 声明状态:**TRUE**、**FALSE** 或 **SUSPECTED**。TRUE 表示声明已确认,FALSE 表示声明被发现为假,SUSPECTED 表示声明未验证。
- 声明描述:详细描述解释声明的理由,以及所有相关的证据和参考资料。
- 声明日期:声明提出的时间段(start_date, end_date)。start_date 和 end_date 都应为 ISO-8601 格式。如果声明是在单一日期提出的,则将同一日期设置为 start_date 和 end_date。如果日期未知,返回 **NONE**。
- 声明来源文本:列出原始文本中与声明相关的**所有**引用。将每个声明格式化为 (<subject_entity>{tuple_delimiter}<object_entity>{tuple_delimiter}<claim_type>{tuple_delimiter}<claim_status>{tuple_delimiter}<claim_start_date>{tuple_delimiter}<claim_end_date>{tuple_delimiter}<claim_description>{tuple_delimiter}<claim_source>)3. 使用 **{record_delimiter}** 作为列表分隔符,将步骤1和2中识别的所有声明作为单个列表返回。4. 完成后,输出 {completion_delimiter}-示例-
示例1:
实体规范: organization
声明描述: 与实体相关的红色标志
文本: 根据2022/01/10的一篇文章,A公司因在多个政府机构B发布的公开招标中串标而被罚款。该公司由C人拥有,C人被怀疑在2015年从事腐败活动。
输出:(A公司{tuple_delimiter}政府机构B{tuple_delimiter}反竞争行为{tuple_delimiter}TRUE{tuple_delimiter}2022-01-10T00:00:00{tuple_delimiter}2022-01-10T00:00:00{tuple_delimiter}根据2022/01/10发表的一篇文章,A公司因在多个政府机构B发布的公开招标中串标而被罚款{tuple_delimiter}根据2022/01/10发表的一篇文章,A公司因在多个政府机构B发布的公开招标中串标而被罚款。)
{completion_delimiter}示例2:
实体规范: A公司, C人
声明描述: 与实体相关的红色标志
文本: 根据2022/01/10的一篇文章,A公司因在多个政府机构B发布的公开招标中串标而被罚款。该公司由C人拥有,C人被怀疑在2015年从事腐败活动。
输出:(A公司{tuple_delimiter}政府机构B{tuple_delimiter}反竞争行为{tuple_delimiter}TRUE{tuple_delimiter}2022-01-10T00:00:00{tuple_delimiter}2022-01-10T00:00:00{tuple_delimiter}根据2022/01/10发表的一篇文章,A公司因在多个政府机构B发布的公开招标中串标而被罚款{tuple_delimiter}根据2022/01/10发表的一篇文章,A公司因在多个政府机构B发布的公开招标中串标而被罚款。)
{record_delimiter}
(C人{tuple_delimiter}NONE{tuple_delimiter}腐败{tuple_delimiter}SUSPECTED{tuple_delimiter}2015-01-01T00:00:00{tuple_delimiter}2015-12-30T00:00:00{tuple_delimiter}C人被怀疑在2015年从事腐败活动{tuple_delimiter}该公司由C人拥有,C人被怀疑在2015年从事腐败活动。)
{completion_delimiter}-真实数据-
使用以下输入进行回答。
实体规范: {entity_specs}
声明描述: {claim_description}
文本: {input_text}
输出:

community_report.txt 即:


你是一个智能助手,帮助人类分析师进行一般信息发现。信息发现是识别和评估与某些实体(例如组织和个人)相关的相关信息的过程。# 目标
编写一份关于某个社群的综合报告,给定属于该社群的实体列表及其关系和可选的相关声明。该报告将用于向决策者提供有关社群及其潜在影响的信息。报告内容包括社群主要实体的概述、其法律合规性、技术能力、声誉和值得注意的声明。# 报告结构报告应包括以下部分:- 标题:代表社群主要实体的社群名称 - 标题应简短但具体。尽可能在标题中包含代表性的命名实体。
- 摘要:社群整体结构的执行摘要,其实体之间的关系,以及与其实体相关的重要信息。
- 影响严重性评分:一个介于0到10之间的浮动分数,表示社群内实体所带来的影响严重性。影响是社群的重要性评分。
- 评分解释:对影响严重性评分的单句解释。
- 详细发现:关于社群的5到10个关键见解的列表。每个见解应有一个简短的摘要,后跟多段解释性文本,并根据以下基础规则进行支持。要全面。返回输出为格式良好的JSON格式字符串,格式如下:{{"title": <report_title>,"summary": <executive_summary>,"rating": <impact_severity_rating>,"rating_explanation": <rating_explanation>,"findings": [{{"summary":<insight_1_summary>,"explanation": <insight_1_explanation>}},{{"summary":<insight_2_summary>,"explanation": <insight_2_explanation>}}]}}# 基础规则由数据支持的观点应列出其数据参考,如下所示:“这是一个由多个数据参考支持的示例句子[数据:<数据集名称>(记录ID);<数据集名称>(记录ID)]。”单个参考中列出的记录ID不得超过5个。相反,列出最相关的前5个记录ID,并添加“+更多”以表示还有更多。例如:
“X人是Y公司的所有者,并且受到许多不当行为指控[数据:报告(1),实体(5,7);关系(23);声明(7,2,34,64,46,+更多)]。”其中1、5、7、23、2、34、46和64代表相关数据记录的ID(而不是索引)。不要包含没有提供支持证据的信息。# 示例输入
-----------
文本:实体id,entity,description
5,绿洲广场,绿洲广场是团结游行的地点
6,和谐集会,和谐集会是一个在绿洲广场举行游行的组织关系id,source,target,description
37,绿洲广场,团结游行,绿洲广场是团结游行的地点
38,绿洲广场,和谐集会,和谐集会在绿洲广场举行游行
39,绿洲广场,团结游行,团结游行在绿洲广场举行
40,绿洲广场,论坛聚光灯,论坛聚光灯正在报道在绿洲广场举行的团结游行
41,绿洲广场,贝利·阿萨迪,贝利·阿萨迪在绿洲广场发表关于游行的演讲
43,和谐集会,团结游行,和谐集会正在组织团结游行输出:
{{"title": "绿洲广场和团结游行","summary": "社群围绕绿洲广场展开,绿洲广场是团结游行的地点。广场与和谐集会、团结游行和论坛聚光灯有关系,所有这些都与游行活动有关。","rating": 5.0,"rating_explanation": "由于团结游行期间可能发生动乱或冲突,影响严重性评分为中等。","findings": [{{"summary": "绿洲广场作为中心地点","explanation": "绿洲广场是该社群的中心实体,作为团结游行的地点。这个广场是所有其他实体的共同联系点,表明其在社群中的重要性。广场与游行的关联可能会导致公共秩序或冲突问题,具体取决于游行的性质和引发的反应。[数据:实体(5),关系(37,38,39,40,41,+更多)]"}},{{"summary": "和谐集会在社群中的角色","explanation": "和谐集会是该社群的另一个关键实体,负责在绿洲广场组织游行。和谐集会的性质及其游行可能是潜在的威胁来源,具体取决于他们的目标和引发的反应。和谐集会与广场之间的关系对于理解该社群的动态至关重要。[数据:实体(6),关系(38,43)]"}},{{"summary": "团结游行作为重要事件","explanation": "团结游行是绿洲广场举行的重要事件。这个事件是社群动态的关键因素,具体取决于游行的性质和引发的反应,可能是潜在的威胁来源。游行与广场之间的关系对于理解该社群的动态至关重要。[数据:关系(39)]"}},{{"summary": "论坛聚光灯的角色","explanation": "论坛聚光灯正在报道在绿洲广场举行的团结游行。这表明该事件引起了媒体的关注,可能会放大其对社群的影响。论坛聚光灯的角色可能在塑造公众对事件和相关实体的看法方面具有重要意义。[数据:关系(40)]"}}]
}}# 真实数据使用以下文本进行回答。不要在回答中编造任何内容。文本:
{input_text}报告应包括以下部分:- 标题:代表社群主要实体的社群名称 - 标题应简短但具体。尽可能在标题中包含代表性的命名实体。
- 摘要:社群整体结构的执行摘要,其实体之间的关系,以及与其实体相关的重要信息。
- 影响严重性评分:一个介于0到10之间的浮动分数,表示社群内实体所带来的影响严重性。影响是社群的重要性评分。
- 评分解释:对影响严重性评分的单句解释。
- 详细发现:关于社群的5到10个关键见解的列表。每个见解应有一个简短的摘要,后跟多段解释性文本,并根据以下基础规则进行支持。要全面。返回输出为格式良好的JSON格式字符串,格式如下:{{"title": <report_title>,"summary": <executive_summary>,"rating": <impact_severity_rating>,"rating_explanation": <rating_explanation>,"findings": [{{"summary":<insight_1_summary>,"explanation": <insight_1_explanation>}},{{"summary":<insight_2_summary>,"explanation": <insight_2_explanation>}}]}}# 基础规则由数据支持的观点应列出其数据参考,如下所示:“这是一个由多个数据参考支持的示例句子[数据:<数据集名称>(记录ID);<数据集名称>(记录ID)]。”单个参考中列出的记录ID不得超过5个。相反,列出最相关的前5个记录ID,并添加“+更多”以表示还有更多。例如:
“X人是Y公司的所有者,并且受到许多不当行为指控[数据:报告(1),实体(5,7);关系(23);声明(7,2,34,64,46,+更多)]。”其中1、5、7、23、2、34、46和64代表相关数据记录的ID(而不是索引)。不要包含没有提供支持证据的信息。输出:

entity_extraction.txt 即:


-目标-
给定一个可能与此活动相关的文本文件和一个实体类型列表,从文本中识别出所有这些类型的实体以及这些实体之间的所有关系。-步骤-
1. 识别所有实体。对于每个识别出的实体,提取以下信息:
- 实体名称:实体的名称,需大写
- 实体类型:以下类型之一:[ {entity_types} ]
- 实体描述:对实体属性和活动的全面描述
将每个实体格式化为 ("entity"{tuple_delimiter}<entity_name>{tuple_delimiter}<entity_type>{tuple_delimiter}<entity_description>)2. 从步骤1中识别的实体中,识别出所有*明确相关*的 (source_entity, target_entity) 对。
对于每对相关实体,提取以下信息:
- 源实体:步骤1中识别出的源实体名称
- 目标实体:步骤1中识别出的目标实体名称
- 关系描述:解释为什么认为源实体和目标实体之间存在关系
- 关系强度:一个表示源实体和目标实体之间关系强度的数值评分
将每个关系格式化为 ("relationship"{tuple_delimiter}<source_entity>{tuple_delimiter}<target_entity>{tuple_delimiter}<relationship_description>{tuple_delimiter}<relationship_strength>)3. 使用 **{record_delimiter}** 作为列表分隔符,以单个列表的形式返回步骤1和步骤2中识别的所有实体和关系。4. 完成后,输出 {completion_delimiter}######################
-示例-
######################
示例 1:
实体类型: ORGANIZATION,PERSON
文本:
维尔丹蒂斯中央机构计划在周一和周四举行会议,该机构计划在周四下午1:30(太平洋夏令时间)发布其最新的政策决定,随后将举行新闻发布会,中央机构主席马丁史密斯将回答提问。投资者预计市场策略委员会将保持其基准利率在3.5%至3.75%的范围内不变。
######################
输出:
("entity"{tuple_delimiter}中央机构{tuple_delimiter}ORGANIZATION{tuple_delimiter}中央机构是维尔丹蒂斯联邦储备系统,在周一和周四设定利率)
{record_delimiter}
("entity"{tuple_delimiter}马丁史密斯{tuple_delimiter}PERSON{tuple_delimiter}马丁史密斯是中央机构的主席)
{record_delimiter}
("entity"{tuple_delimiter}市场策略委员会{tuple_delimiter}ORGANIZATION{tuple_delimiter}中央机构委员会对维尔丹蒂斯的利率和货币供应增长做出关键决策)
{record_delimiter}
("relationship"{tuple_delimiter}马丁史密斯{tuple_delimiter}中央机构{tuple_delimiter}马丁史密斯是中央机构的主席,将在新闻发布会上回答问题{tuple_delimiter}9)
{completion_delimiter}######################
示例 2:
实体类型: ORGANIZATION
文本:
科技全球的股票在周四的全球交易所开盘当天飙升。但IPO专家警告称,这家半导体公司的上市表现并不能代表其他新上市公司的表现。科技全球是一家曾经上市的公司,2014年被愿景控股私有化。这家知名的芯片设计公司表示,其产品为85%的高端智能手机提供动力。
######################
输出:
("entity"{tuple_delimiter}科技全球{tuple_delimiter}ORGANIZATION{tuple_delimiter}科技全球是一家现在在全球交易所上市的公司,其产品为85%的高端智能手机提供动力)
{record_delimiter}
("entity"{tuple_delimiter}愿景控股{tuple_delimiter}ORGANIZATION{tuple_delimiter}愿景控股是一家曾经拥有科技全球的公司)
{record_delimiter}
("relationship"{tuple_delimiter}科技全球{tuple_delimiter}愿景控股{tuple_delimiter}愿景控股从2014年起曾经拥有科技全球{tuple_delimiter}5)
{completion_delimiter}######################
示例 3:
实体类型: ORGANIZATION,GEO,PERSON
文本:
五名奥雷利亚人在费鲁扎巴德被监禁8年,被广泛认为是人质,现在正返回奥雷利亚。由金塔拉策划的交换在80亿美元费鲁兹资金转移到克罗哈拉的金融机构后完成,克罗哈拉是金塔拉的首都。交换在费鲁扎巴德的首都提鲁齐亚进行,四男一女,这些人也是费鲁兹国民,登上了一架飞往克罗哈拉的包机。他们受到了奥雷利亚高级官员的欢迎,现在正前往奥雷利亚的首都卡什恩。这些奥雷利亚人包括39岁的商人塞缪尔纳马拉,他曾被关押在提鲁齐亚的阿尔哈米亚监狱,以及59岁的记者杜尔克巴塔格拉尼和53岁的环保主义者梅吉塔兹巴,她也拥有布拉蒂纳斯国籍。
######################
输出:
("entity"{tuple_delimiter}费鲁扎巴{tuple_delimiter}GEO{tuple_delimiter}费鲁扎巴关押了奥雷利亚人作为人质)
{record_delimiter}
("entity"{tuple_delimiter}奥雷利亚{tuple_delimiter}GEO{tuple_delimiter}寻求释放人质的国家)
{record_delimiter}
("entity"{tuple_delimiter}金塔拉{tuple_delimiter}GEO{tuple_delimiter}谈判用钱换取人质的国家)
{record_delimiter}
("entity"{tuple_delimiter}提鲁齐亚{tuple_delimiter}GEO{tuple_delimiter}费鲁扎巴的首都,奥雷利亚人被关押的地方)
{record_delimiter}
("entity"{tuple_delimiter}克罗哈拉{tuple_delimiter}GEO{tuple_delimiter}金塔拉的首都)
{record_delimiter}
("entity"{tuple_delimiter}卡什恩{tuple_delimiter}GEO{tuple_delimiter}奥雷利亚的首都)
{record_delimiter}
("entity"{tuple_delimiter}塞缪尔纳马拉{tuple_delimiter}PERSON{tuple_delimiter}在提鲁齐亚的阿尔哈米亚监狱服刑的奥雷利亚人)
{record_delimiter}
("entity"{tuple_delimiter}阿尔哈米亚监狱{tuple_delimiter}GEO{tuple_delimiter}提鲁齐亚的监狱)
{record_delimiter}
("entity"{tuple_delimiter}杜尔克巴塔格拉尼{tuple_delimiter}PERSON{tuple_delimiter}被关押的人质奥雷利亚记者)
{record_delimiter}
("entity"{tuple_delimiter}梅吉塔兹巴{tuple_delimiter}PERSON{tuple_delimiter}布拉蒂纳斯国籍的环保主义者,被关押的人质)
{record_delimiter}
("relationship"{tuple_delimiter}费鲁扎巴{tuple_delimiter}奥雷利亚{tuple_delimiter}费鲁扎巴与奥雷利亚谈判人质交换{tuple_delimiter}2)
{record_delimiter}
("relationship"{tuple_delimiter}金塔拉{tuple_delimiter}奥雷利亚{tuple_delimiter}金塔拉促成了费鲁扎巴和奥雷利亚之间的人质交换{tuple_delimiter}2)
{record_delimiter}
("relationship"{tuple_delimiter}金塔拉{tuple_delimiter}费鲁扎巴{tuple_delimiter}金塔拉促成了费鲁扎巴和奥雷利亚之间的人质交换{tuple_delimiter}2)
{record_delimiter}
("relationship"{tuple_delimiter}塞缪尔纳马拉{tuple_delimiter}阿尔哈米亚监狱{tuple_delimiter}塞缪尔纳马拉曾是阿尔哈米亚监狱的囚犯{tuple_delimiter}8)
{record_delimiter}
("relationship"{tuple_delimiter}塞缪尔纳马拉{tuple_delimiter}梅吉塔兹巴{tuple_delimiter}塞缪尔纳马拉和梅吉塔兹巴在同一次人质释放中被交换{tuple_delimiter}2)
{record_delimiter}
("relationship"{tuple_delimiter}塞缪尔纳马拉{tuple_delimiter}杜尔克巴塔格拉尼{tuple_delimiter}塞缪尔纳马拉和杜尔克巴塔格拉尼在同一次人质释放中被交换{tuple_delimiter}2)
{record_delimiter}
("relationship"{tuple_delimiter}梅吉塔兹巴{tuple_delimiter}杜尔克巴塔格拉尼{tuple_delimiter}梅吉塔兹巴和杜尔克巴塔格拉尼在同一次人质释放中被交换{tuple_delimiter}2)
{record_delimiter}
("relationship"{tuple_delimiter}塞缪尔纳马拉{tuple_delimiter}费鲁扎巴{tuple_delimiter}塞缪尔纳马拉曾是费鲁扎巴的人质{tuple_delimiter}2)
{record_delimiter}
("relationship"{tuple_delimiter}梅吉塔兹巴{tuple_delimiter}费鲁扎巴{tuple_delimiter}梅吉塔兹巴曾是费鲁扎巴的人质{tuple_delimiter}2)
{record_delimiter}
("relationship"{tuple_delimiter}杜尔克巴塔格拉尼{tuple_delimiter}费鲁扎巴{tuple_delimiter}杜尔克巴塔格拉尼曾是费鲁扎巴的人质{tuple_delimiter}2)
{completion_delimiter}######################
-真实数据-
######################
实体类型: {entity_types}
文本: {input_text}
######################
输出:

summarize_descriptions.txt 即:


你是一个负责生成综合摘要的助手,任务是根据以下提供的数据生成一份全面的总结。
给定一个或两个实体,以及一个描述列表,这些描述都与同一个实体或实体组相关。
请将所有这些描述合并成一个完整的描述。确保包含所有描述中的信息。
如果提供的描述存在矛盾,请解决矛盾并提供一个连贯的总结。
确保使用第三人称书写,并包括实体名称以便我们获得完整的上下文。#######
-数据-
实体: {entity_name}
描述列表: {description_list}
#######
输出:

3.2 中文分词

使用 tokens.py 替换掉 Python GraphRAG 依赖库中的 graphrag/index/verbs/text/chunk/strategies/tokens.py 即可。

ll /opt/conda/envs/rag/lib/python3.10/site-packages/graphrag/index/verbs/text/chunk/strategies/tokens.py
cp tokens.py /opt/conda/envs/rag/lib/python3.10/site-packages/graphrag/index/verbs/text/chunk/strategies/tokens.py

参考 tokens.py,如下:

import logging
import re
from typing import Any, List, Optionalfrom langchain.text_splitter import RecursiveCharacterTextSplitter
from collections.abc import Iterable
from typing import Anyimport tiktoken
from datashaper import ProgressTickerfrom graphrag.index.text_splitting import Tokenizer
from graphrag.index.verbs.text.chunk.typing import TextChunk# CHUNK_SIZE是指在处理大型数据集时,将数据分成多个小块(chunk)时,每个小块的大小。这样做可以有效地管理内存使用,避免一次性加载过多数据导致内存溢出。在这里应根据大模型API请求的上下文 tokens 大小进行设置。
# CHUNK_OVERLAP是指在处理文本数据时,将文本分成多个小块(chunk)时,相邻块之间重叠的部分。这样做可以确保在分块处理时不会丢失重要信息,特别是在进行文本分类、实体识别等任务时,有助于提高模型的准确性和连贯性。
DEFAULT_CHUNK_SIZE = 2500  # tokens
DEFAULT_CHUNK_OVERLAP = 300  # tokensdef run(input: list[str], args: dict[str, Any], tick: ProgressTicker
) -> Iterable[TextChunk]:"""切分文本"""tokens_per_chunk = args.get("chunk_size", DEFAULT_CHUNK_SIZE)chunk_overlap = args.get("chunk_overlap", DEFAULT_CHUNK_OVERLAP)encoding_name = args.get("encoding_name", "cl100k_base")enc = tiktoken.get_encoding(encoding_name)def encode(text: str) -> list[int]:if not isinstance(text, str):text = f"{text}"return enc.encode(text)def decode(tokens: list[int]) -> str:return enc.decode(tokens)return split_text_on_tokens(input,Tokenizer(chunk_overlap=chunk_overlap,tokens_per_chunk=tokens_per_chunk,encode=encode,decode=decode,),tick,chunk_overlap=chunk_overlap,tokens_per_chunk=tokens_per_chunk)def split_text_on_tokens(texts: list[str], enc: Tokenizer, tick: ProgressTicker, chunk_overlap, tokens_per_chunk
) -> list[TextChunk]:result = []mapped_ids = []for source_doc_idx, text in enumerate(texts):tick(1)mapped_ids.append((source_doc_idx, text))text_splitter = ChineseRecursiveTextSplitter(keep_separator=True, is_separator_regex=True, chunk_size=tokens_per_chunk, chunk_overlap=chunk_overlap)for source_doc_idx, text in mapped_ids:chunks = text_splitter.split_text(text)for chunk in chunks:result.append(TextChunk(text_chunk=chunk,source_doc_indices=[source_doc_idx] * len(chunk),n_tokens=len(chunk),))return resultdef _split_text_with_regex_from_end(text: str, separator: str, keep_separator: bool
) -> List[str]:if separator:if keep_separator:# 模式中的括号会保留结果中的分隔符。_splits = re.split(f"({separator})", text)splits = ["".join(i) for i in zip(_splits[0::2], _splits[1::2])]if len(_splits) % 2 == 1:splits += _splits[-1:]else:splits = re.split(separator, text)else:splits = list(text)return [s for s in splits if s != ""]class ChineseRecursiveTextSplitter(RecursiveCharacterTextSplitter):def __init__(self,separators: Optional[List[str]] = None,keep_separator: bool = True,is_separator_regex: bool = True,**kwargs: Any,) -> None:super().__init__(keep_separator=keep_separator, **kwargs)self._separators = separators or [r"\n\n",r"\n",r"。|!|?",r"\.\s|\!\s|\?\s",r";|;\s",r",|,\s",]self._is_separator_regex = is_separator_regexdef _split_text(self, text: str, separators: List[str]) -> List[str]:"""拆分传入的文本并返回处理后的块。"""final_chunks = []# 获取适当的分隔符以使用separator = separators[-1]new_separators = []for i, _s in enumerate(separators):_separator = _s if self._is_separator_regex else re.escape(_s)if _s == "":separator = _sbreakif re.search(_separator, text):separator = _snew_separators = separators[i + 1:]break_separator = separator if self._is_separator_regex else re.escape(separator)splits = _split_text_with_regex_from_end(text, _separator, self._keep_separator)_good_splits = []_separator = "" if self._keep_separator else separatorfor s in splits:if self._length_function(s) < self._chunk_size:_good_splits.append(s)else:if _good_splits:merged_text = self._merge_splits(_good_splits, _separator)final_chunks.extend(merged_text)_good_splits = []if not new_separators:final_chunks.append(s)else:other_info = self._split_text(s, new_separators)final_chunks.extend(other_info)if _good_splits:merged_text = self._merge_splits(_good_splits, _separator)final_chunks.extend(merged_text)return [re.sub(r"\n{2,}", "\n", chunk.strip())for chunk in final_chunksif chunk.strip() != ""]

4. 生成知识图谱

生成 GraphRAG 的知识图谱,稍等片刻,如果速度特别慢,注意 settings.yaml 的参数 max_gleanings 是否为 0,即:

python -m graphrag.index --root ./ragtest

日志:

# ...
⠹ GraphRAG Indexer 
├── Loading Input (text) - 1 files loaded (0 filtered) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00 0:00:00
├── create_base_text_units
├── create_base_extracted_entities
├── create_summarized_entities
├── create_base_entity_graph
├── create_final_entities
├── create_final_nodes
├── create_final_communities
├── create_final_relationships
├── create_final_text_units
├── create_final_community_reports
├── create_base_documents
└── create_final_documents
🚀 All workflows completed successfully.

5. Ollama 支持

适配 Ollama 的 GraphRAG 的位置,修改文件:

  • graphrag/llm/openai/openai_embeddings_llm.py
  • graphrag/query/llm/oai/embedding.py
pip show graphrag
# /opt/conda/envs/rag/lib/python3.10/site-packages
cp /opt/conda/envs/rag/lib/python3.10/site-packages/graphrag/llm/openai/openai_embeddings_llm.py .
cp /opt/conda/envs/rag/lib/python3.10/site-packages/graphrag/query/llm/oai/embedding.py .
# 修改文件
cp openai_embeddings_llm.py /opt/conda/envs/rag/lib/python3.10/site-packages/graphrag/llm/openai/openai_embeddings_llm.py
cp embedding.py /opt/conda/envs/rag/lib/python3.10/site-packages/graphrag/query/llm/oai/embedding.py
cat /opt/conda/envs/rag/lib/python3.10/site-packages/graphrag/llm/openai/openai_embeddings_llm.py | grep ollama
cat /opt/conda/envs/rag/lib/python3.10/site-packages/graphrag/query/llm/oai/embedding.py | grep OllamaEmbeddings

安装包:

pip install ollama
pip install langchain_community

具体修改如下,修改 graphrag/llm/openai/openai_embeddings_llm.py 源码,注意替换 ollama.embeddings() 函数,即:

# Copyright (c) 2024 Microsoft Corporation.
# Licensed under the MIT License"""The EmbeddingsLLM class."""from typing_extensions import Unpackfrom graphrag.llm.base import BaseLLM
from graphrag.llm.types import (EmbeddingInput,EmbeddingOutput,LLMInput,
)from .openai_configuration import OpenAIConfiguration
from .types import OpenAIClientTypes
import ollama  # newclass OpenAIEmbeddingsLLM(BaseLLM[EmbeddingInput, EmbeddingOutput]):"""A text-embedding generator LLM."""_client: OpenAIClientTypes_configuration: OpenAIConfigurationdef __init__(self, client: OpenAIClientTypes, configuration: OpenAIConfiguration):self.client = clientself.configuration = configurationasync def _execute_llm(self, input: EmbeddingInput, **kwargs: Unpack[LLMInput]) -> EmbeddingOutput | None:args = {"model": self.configuration.model,**(kwargs.get("model_parameters") or {}),}# 原始代码# embedding = await self.client.embeddings.create(#     input=input,#     **args,# )# return [d.embedding for d in embedding.data]# 最新代码embedding_list = []for inp in input:embedding = ollama.embeddings(model="quentinz/bge-large-zh-v1.5:latest", prompt=inp)embedding_list.append(embedding["embedding"])return embedding_list

修改 graphrag/query/llm/oai/embedding.py 源码,注意替换 OllamaEmbeddings 类,即:

# Copyright (c) 2024 Microsoft Corporation.
# Licensed under the MIT License"""OpenAI Embedding model implementation."""import asyncio
from collections.abc import Callable
from typing import Anyimport numpy as np
import tiktoken
from tenacity import (AsyncRetrying,RetryError,Retrying,retry_if_exception_type,stop_after_attempt,wait_exponential_jitter,
)from graphrag.query.llm.base import BaseTextEmbedding
from graphrag.query.llm.oai.base import OpenAILLMImpl
from graphrag.query.llm.oai.typing import (OPENAI_RETRY_ERROR_TYPES,OpenaiApiType,
)
from graphrag.query.llm.text_utils import chunk_text
from graphrag.query.progress import StatusReporterfrom langchain_community.embeddings import OllamaEmbeddings  # newclass OpenAIEmbedding(BaseTextEmbedding, OpenAILLMImpl):"""Wrapper for OpenAI Embedding models."""def __init__(self,api_key: str | None = None,azure_ad_token_provider: Callable | None = None,model: str = "text-embedding-3-small",deployment_name: str | None = None,api_base: str | None = None,api_version: str | None = None,api_type: OpenaiApiType = OpenaiApiType.OpenAI,organization: str | None = None,encoding_name: str = "cl100k_base",max_tokens: int = 8191,max_retries: int = 10,request_timeout: float = 180.0,retry_error_types: tuple[type[BaseException]] = OPENAI_RETRY_ERROR_TYPES,  # type: ignorereporter: StatusReporter | None = None,):OpenAILLMImpl.__init__(self=self,api_key=api_key,azure_ad_token_provider=azure_ad_token_provider,deployment_name=deployment_name,api_base=api_base,api_version=api_version,api_type=api_type,  # type: ignoreorganization=organization,max_retries=max_retries,request_timeout=request_timeout,reporter=reporter,)self.model = modelself.encoding_name = encoding_nameself.max_tokens = max_tokensself.token_encoder = tiktoken.get_encoding(self.encoding_name)self.retry_error_types = retry_error_typesdef embed(self, text: str, **kwargs: Any) -> list[float]:"""Embed text using OpenAI Embedding's sync function.For text longer than max_tokens, chunk texts into max_tokens, embed each chunk, then combine using weighted average.Please refer to: https://github.com/openai/openai-cookbook/blob/main/examples/Embedding_long_inputs.ipynb"""token_chunks = chunk_text(text=text, token_encoder=self.token_encoder, max_tokens=self.max_tokens)chunk_embeddings = []chunk_lens = []for chunk in token_chunks:try:embedding, chunk_len = self._embed_with_retry(chunk, **kwargs)chunk_embeddings.append(embedding)chunk_lens.append(chunk_len)# TODO: catch a more specific exceptionexcept Exception as e:  # noqa BLE001self._reporter.error(message="Error embedding chunk",details={self.__class__.__name__: str(e)},)continuechunk_embeddings = np.average(chunk_embeddings, axis=0, weights=chunk_lens)chunk_embeddings = chunk_embeddings / np.linalg.norm(chunk_embeddings)return chunk_embeddings.tolist()async def aembed(self, text: str, **kwargs: Any) -> list[float]:"""Embed text using OpenAI Embedding's async function.For text longer than max_tokens, chunk texts into max_tokens, embed each chunk, then combine using weighted average."""token_chunks = chunk_text(text=text, token_encoder=self.token_encoder, max_tokens=self.max_tokens)chunk_embeddings = []chunk_lens = []embedding_results = await asyncio.gather(*[self._aembed_with_retry(chunk, **kwargs) for chunk in token_chunks])embedding_results = [result for result in embedding_results if result[0]]chunk_embeddings = [result[0] for result in embedding_results]chunk_lens = [result[1] for result in embedding_results]chunk_embeddings = np.average(chunk_embeddings, axis=0, weights=chunk_lens)  # type: ignorechunk_embeddings = chunk_embeddings / np.linalg.norm(chunk_embeddings)return chunk_embeddings.tolist()def _embed_with_retry(self, text: str | tuple, **kwargs: Any) -> tuple[list[float], int]:try:retryer = Retrying(stop=stop_after_attempt(self.max_retries),wait=wait_exponential_jitter(max=10),reraise=True,retry=retry_if_exception_type(self.retry_error_types),)for attempt in retryer:with attempt:# old# embedding = (#     self.sync_client.embeddings.create(  # type: ignore#         input=text,#         model=self.model,#         **kwargs,  # type: ignore#     )#     .data[0]#     .embedding#     or []# )# return (embedding, len(text))# newembedding = (OllamaEmbeddings(model=self.model,).embed_query(text)or [])return (embedding, len(text))except RetryError as e:self._reporter.error(message="Error at embed_with_retry()",details={self.__class__.__name__: str(e)},)return ([], 0)else:# TODO: why not just throw in this case?return ([], 0)async def _aembed_with_retry(self, text: str | tuple, **kwargs: Any) -> tuple[list[float], int]:try:retryer = AsyncRetrying(stop=stop_after_attempt(self.max_retries),wait=wait_exponential_jitter(max=10),reraise=True,retry=retry_if_exception_type(self.retry_error_types),)async for attempt in retryer:with attempt:# old# embedding = (#     await self.async_client.embeddings.create(  # type: ignore#         input=text,#         model=self.model,#         **kwargs,  # type: ignore#     )# ).data[0].embedding or []# return (embedding, len(text))# newembedding = (await OllamaEmbeddings(model=self.model,).embed_query(text) or [] )return (embedding, len(text))except RetryError as e:self._reporter.error(message="Error at embed_with_retry()",details={self.__class__.__name__: str(e)},)return ([], 0)else:# TODO: why not just throw in this case?return ([], 0)

遇到Bug,即:

not expected dict type. type=<class 'str'>:
Traceback (most recent call last):File "/opt/conda/envs/rag/lib/python3.10/site-packages/graphrag/llm/openai/utils.py", line 130, in try_parse_json_objectresult = json.loads(input)File "/opt/conda/envs/rag/lib/python3.10/json/__init__.py", line 346, in loadsreturn _default_decoder.decode(s)File "/opt/conda/envs/rag/lib/python3.10/json/decoder.py", line 337, in decodeobj, end = self.raw_decode(s, idx=_w(s, 0).end())File "/opt/conda/envs/rag/lib/python3.10/json/decoder.py", line 355, in raw_decoderaise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

解决方案参考:

  • GitHub - All workflows completed successfully,but graphrag failed to answer any question given the provided data
  • MortalHappiness/graphrag_monkey_patch.py

修改入口文件,即:

wget https://raw.githubusercontent.com/microsoft/graphrag/refs/tags/v0.3.6/graphrag/index/__main__.py -O index.py
wget https://raw.githubusercontent.com/microsoft/graphrag/refs/tags/v0.3.6/graphrag/query/__main__.py -O query.py

增加 graphrag_monkey_patch.py,参考备注信息修改 index.pyquery.py,修改:

# Monkey patch the graphrag package to support local models deployed via Ollama. Only tested for graphrag version 0.3.2.
# See https://chishengliu.com/posts/graphrag-local-ollama/ for details.
# 
# Usage:
# * How to patch the `graphrag.index` CLI:
#   * Save https://github.com/microsoft/graphrag/blob/v0.3.2/graphrag/index/__main__.py as "index.py"
#   * Replace line 8 with:
#     ```python
#     from graphrag.index.cli import index_cli
#     from graphrag_monkey_patch import patch_graphrag
#     patch_graphrag()
#     ```
#   * Then, you can use `python index.py` instead of `python -m graphrag.index` to do the indexing.
# * How to patch the `graphrag.query` CLI:
#   * Save https://github.com/microsoft/graphrag/blob/v0.3.2/graphrag/query/__main__.py as "query.py"
#   * Replace line 9 with:
#     ```python
#     from graphrag.query.cli import run_global_search, run_local_search
#     from graphrag_monkey_patch import patch_graphrag
#     patch_graphrag()
#     ```
#   * Then, you can use `python query.py` instead of `python -m graphrag.query` to do the queries.def patch_graphrag():patch_openai_embeddings_llm()patch_query_embedding()patch_global_search()def patch_openai_embeddings_llm():# Reference: https://github.com/microsoft/graphrag/issues/370#issuecomment-2211821370from graphrag.llm.openai.openai_embeddings_llm import OpenAIEmbeddingsLLMimport ollamaasync def _execute_llm(self, input, **kwargs):embedding_list = []for inp in input:embedding = ollama.embeddings(model=self.configuration.model, prompt=inp)embedding_list.append(embedding["embedding"])return embedding_listOpenAIEmbeddingsLLM._execute_llm = _execute_llmdef patch_query_embedding():# Reference: https://github.com/microsoft/graphrag/issues/345#issuecomment-2230317752from graphrag.query.llm.oai.embedding import OpenAIEmbeddingimport ollamafrom tenacity import (AsyncRetrying,RetryError,Retrying,retry_if_exception_type,stop_after_attempt,wait_exponential_jitter,)def _embed_with_retry(self, text, **kwargs):try:retryer = Retrying(stop=stop_after_attempt(self.max_retries),wait=wait_exponential_jitter(max=10),reraise=True,retry=retry_if_exception_type(self.retry_error_types),)for attempt in retryer:with attempt:embedding = (ollama.embeddings(model=self.model, prompt=text)["embedding"] or [])return (embedding, len(text))except RetryError as e:self._reporter.error(message="Error at embed_with_retry()",details={self.__class__.__name__: str(e)},)return ([], 0)else:# TODO: why not just throw in this case?return ([], 0)async def _aembed_with_retry(self, text, **kwargs):try:retryer = AsyncRetrying(stop=stop_after_attempt(self.max_retries),wait=wait_exponential_jitter(max=10),reraise=True,retry=retry_if_exception_type(self.retry_error_types),)async for attempt in retryer:with attempt:embedding = (ollama.embeddings(model=self.model, prompt=text)["embedding"] or [])return (embedding, len(text))except RetryError as e:self._reporter.error(message="Error at embed_with_retry()",details={self.__class__.__name__: str(e)},)return ([], 0)else:# TODO: why not just throw in this case?return ([], 0)OpenAIEmbedding._embed_with_retry = _embed_with_retryOpenAIEmbedding._aembed_with_retry = _aembed_with_retrydef patch_global_search():# Reference: https://github.com/microsoft/graphrag/issues/575#issuecomment-2252045859from graphrag.query.structured_search.global_search.search import GlobalSearchimport loggingimport timefrom graphrag.query.llm.text_utils import num_tokensfrom graphrag.query.structured_search.base import SearchResultlog = logging.getLogger(__name__)async def _map_response_single_batch(self, context_data, query, **llm_kwargs):"""Generate answer for a single chunk of community reports."""start_time = time.time()search_prompt = ""try:search_prompt = self.map_system_prompt.format(context_data=context_data)search_messages = [ {"role": "user", "content": search_prompt + "\n\n### USER QUESTION ### \n\n" + query} ]async with self.semaphore:search_response = await self.llm.agenerate(messages=search_messages, streaming=False, **llm_kwargs)log.info("Map response: %s", search_response)try:# parse search response jsonprocessed_response = self.parse_search_response(search_response)except ValueError:# Clean up and retry parsetry:# parse search response jsonprocessed_response = self.parse_search_response(search_response)except ValueError:log.warning("Warning: Error parsing search response json - skipping this batch")processed_response = []return SearchResult(response=processed_response,context_data=context_data,context_text=context_data,completion_time=time.time() - start_time,llm_calls=1,prompt_tokens=num_tokens(search_prompt, self.token_encoder),)except Exception:log.exception("Exception in _map_response_single_batch")return SearchResult(response=[{"answer": "", "score": 0}],context_data=context_data,context_text=context_data,completion_time=time.time() - start_time,llm_calls=1,prompt_tokens=num_tokens(search_prompt, self.token_encoder),)GlobalSearch._map_response_single_batch = _map_response_single_batch

6. 测试

知识图谱的输出位于 ragtest/output

├── [162K]  create_base_documents.parquet
├── [200K]  create_base_entity_graph.parquet
├── [ 38K]  create_base_extracted_entities.parquet
├── [170K]  create_base_text_units.parquet
├── [ 13K]  create_final_communities.parquet
├── [ 91K]  create_final_community_reports.parquet
├── [162K]  create_final_documents.parquet
├── [958K]  create_final_entities.parquet
├── [ 38K]  create_final_nodes.parquet
├── [ 36K]  create_final_relationships.parquet
├── [180K]  create_final_text_units.parquet
├── [ 52K]  create_summarized_entities.parquet
├── [137K]  indexing-engine.log
├── [4.0K]  lancedb
├── [ 70K]  logs.json
└── [4.1K]  stats.json

修改 settings.yaml 参数 max_tokenstemperature,提升回答质量:

llm:max_tokens: 8000temperature: 0.3 # temperature for sampling

测试问题1:

python query.py \
--root ./ragtest \
--method global \
"这个故事中的主要主题是什么?"

回答:

这个故事的主要主题之一是复杂的情感网络,包括人物之间的爱恨情仇、忠诚度和道德观念等多方面的考量 [Data: Reports (26, 5)]. 故事围绕着复杂的情感关系展开,涉及婚姻、亲密联系以及潜在的外遇行为。这些情感纠葛涉及多个角色,如妻子与凌总的关系、叙述者与妻子的关系以及情哥哥与妻子的关系 [Data: Reports (11)].
另一个主要主题是个人生活和社会环境的互动,例如云儿的决定可能会影响到她的家庭成员和社会角色 [Data: Reports (26)]. 此外,故事还探讨了婚姻状态和忠诚度的问题。比如凌总为了满足妻子的要求失去了某些生理功能,并成为了她的专属小XX [Data: Reports (7)].
职业发展和个人情感之间的平衡也是主要主题之一。例如,主角在计算机公司实习时遇到了云瑶,并且他们的关系有了重要进展 [Data: Reports (19)]. 社群中的角色不仅限于个人生活,还包括工作关系和社会地位的变化。例如,刘伟的职业背景和他与云儿的关系展示了复杂的情感纠葛 [Data: Reports (23)].
故事还涉及了社会互动和日常生活的细节,如叙述者与妻子的关系动态以及他在海口的购物体验 [Data: Reports (5, 12)]. 故事中的人物通过一系列事件展现了社会角色和个人命运的交织,例如紫瑛被XX成为小XX,并作为云儿的贴身侍从 [Data: Reports (17)].
故事中的主要活动地点包括酒店和公司等场所,这些地方不仅是物理空间,也是情感和事件发展的关键节点。例如,酒店是叙述者进行多项重要活动的场所 [Data: Reports (20, 18)].

测试问题2:

python query.py \
--root ./ragtest \
--method global \
"紫瑛、小瑛子、凌总监是同一个人的不同称呼,请用紫瑛代替。云瑶、云儿是同一个人的不同称呼,请用云瑶代替。刘伟、情哥哥是同一个人的不同称呼,请用刘伟代替。这个故事中的三个主要人物(紫瑛、云瑶、刘伟)之间是什么关系?"

回答:

在这个故事中,紫瑛(小瑛子)与云瑶之间的关系复杂且充满情感纠葛。紫瑛通过换装和使用女厕所来逗弄自己的妻子云瑶,并被设定为一个XX的角色[Data: Reports (27)]。尽管如此,他们已经秘密结婚并进行了结婚登记,但由于经济原因没有举行正式婚礼[Data: Reports (16, 4, +more)]。
刘伟(情哥哥)在社群中的地位重要,不仅是云瑶的情人,还计划成为她的正式丈夫,并且是叙述者的对手。他的存在影响了多个角色的情感走向和决策[Data: Reports (26)]。刘伟与紫瑛之间存在着复杂的情感竞争和个人角色的挑战。例如,在云瑶的关系中,紫瑛对失去她感到痛苦,并一直试图重新赢回她的爱情[Data: Reports (27, 19)]。
此外,刘伟和紫瑛围绕着云瑶形成了复杂的情感纠葛。这种社会角色和个人命运的交织反映了社群内人物关系的深刻影响[Data: Reports (6, 20, +more)]。刘伟不仅是云瑶的情人,也是紫瑛的竞争者,并且与妻子有不正当关系[Data: Reports (22, 35, +more)]。
综上所述,这三个主要人物之间的关系错综复杂,充满了情感竞争和角色挑战,他们的互动深刻影响了整个社群的情感走向。

测试问题3:

python query.py \
--root ./ragtest \
--method local \
"谁是刘伟,他的主要关系是什么?"

回答:

刘伟在故事中是一个复杂的人物形象。他与多个女性角色有着情感纠葛,并且在不同的情节中有多种身份和行为表现。具体来说,刘伟曾是云儿的初恋男友并与其订婚,但后来成为叙述者的情人之一[Data: Entities (6, 10); Sources (36)]。此外,他与情哥哥也有着复杂的关系,后者计划让妻子的现任丈夫接受手术以防止其与其他人发生关系,从而保护自己的地位[Data: Entities (5, 10)]。
刘伟还被描述为一个送礼物的人,例如他曾送给叙述者虎睛石银质手链作为礼物[Data: Entities (73)]。同时,他也是快递公司的一名员工,在毕业后加入了该公司工作[Data: Entities (62)]。在故事中,刘伟与叙述者的妻子有着特殊的情感联系,并且被怀疑有外遇行为,这表明他在婚姻之外还有着复杂的互动关系[Data: Entities (10); Sources (36)]。
综上所述,刘伟是故事中的一个重要角色,他不仅与云儿和情哥哥等人有着复杂的关系,还在快递公司工作并送礼物给叙述者。他的存在影响了多个主要人物的情感走向,并且在故事情节中扮演着多重角色[Data: Entities (5, 6, 10); Sources (36)]。

参考:

  • CSDN - 服务器上GraphRag+Ollama避坑指南
  • 公众号 - GraphRAG+Ollama 本地部署,保姆教程,踩坑无数,闭坑大法
  • CSDN - GraphRAG + Ollama 本地部署全攻略:避坑实战指南
  • StackOverflow - how to use tiktoken in offline mode computer
  • GitHub - Airmomo/graphrag-practice-chinese
  • GitHub - MortalHappiness/graphrag_monkey_patch.py

相关文章:

LLM - 配置 GraphRAG + Ollama 服务 构建 中文知识图谱

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/142795151 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 GraphR…...

简单认识redis - 6 redis 存储速度快的原因

1基于内存存储 缓存&#xff08;内存&#xff09;读写速度很快&#xff0c;相比于磁盘存储的Mysql 省去了磁盘I/O的次数。 2.高效的数据结构 SDS动态字符串&#xff1a; 1.字符串长度处理&#xff1a;Redis获取字符串长度&#xff0c;时间复杂度为O(1)&#xff0c;而C语言中&am…...

【Qt Quick】状态:State 使用

State 是 Qt Quick 中管理界面组件状态的关键工具。它允许我们定义组件的不同状态&#xff0c;并且在用户交互或事件发生时进行状态切换&#xff0c;从而实现属性、外观和行为的动态变化。通过使用 State&#xff0c;可以避免复杂的条件逻辑&#xff0c;使代码更加简洁和可维护…...

ICE/TURN/STUN/Coturn服务器搭建

ICE 当我们想要实现在公网环境下的语音/视频通话功能时&#xff0c;就需要用到ICE交互式连接建立。ICE不是一种协议&#xff0c;整合了 STUN 和 TURN 两种协议&#xff08;用于 NAT 穿透&#xff09;的框架。 ICE的主要目标是解决NAT&#xff08;网络地址转换&#xff09;穿越…...

ctf.bugku-eval

题目来源&#xff1a;eval - Bugku CTF 访问页面&#xff0c; 代码解释 <?phpinclude "flag.php"; //包含"flag.php"文件$a $_REQUEST[hello]; //从请求参数hello中获取值并赋给变量$a。 eval( "var_dump($a);"); //…...

Extreme Compression of Large Language Models via Additive Quantization阅读

文章目录 Abstract1. Introduction2. Background & Related Work2.1. LLM量化2.2. 最近邻搜索的量化 3.AQLM:Additive Quantization for LLMs3.1. 概述3.1.0 补充**步骤说明****举例说明** 3.2. 阶段1&#xff1a;代码的波束搜索3.3. 阶段2&#xff1a;码本更新3.4. 阶段3&…...

【虚拟化】内核级虚拟化技术KVM介绍,全/半虚拟化的区别,使用libvirt搭建虚拟化平台(go/java/c++)

【虚拟化】内核级虚拟化技术KVM介绍&#xff0c;全/半虚拟化的区别&#xff0c;使用libvirt搭建虚拟化平台&#xff08;go/java/c&#xff09; 文章目录 1、虚拟化技术分类与架构&#xff08;KVM&#xff0c;Xen&#xff09;&#xff0c;全/半虚拟化的区别2、libvirt介绍3、使用…...

C++类成员变量的初始化

1、优先使用或{} 类的非静态数据成员在声明时&#xff0c;使用或{}进行初始化执行默认初始化&#xff0c;构造函数只处理一些特殊成员。 2、直接初始化 使用()进行初始化、new运算符和类构造函数的初始化列表。 3、拷贝初始化 使用进行初始化、函数传参、函数返回值。 隐式调用…...

Golang 中的强大 TUI 库 ——tview

在命令行界面下创建丰富的用户交互界面是许多开发者的需求&#xff0c;而 Golang 语言中有一个非常出色的 TUI&#xff08;文本用户界面&#xff09;库 ——tview。本文将详细介绍 tview 库&#xff0c;并与其他流行的 TUI 库进行对比&#xff0c;最后进行总结。 一、tview 库介…...

电层相关 -- 支路板与线路板

华为OTN产品系列支持 支路板、线路板分离架构 。支路/线路板和集中交叉单板配合使用&#xff0c;除了可以完成OTU单板功能外&#xff0c;还可通过集中交叉单板进行各级别ODUk颗粒业务调度&#xff0c; 实现更加灵活的电层信号调度及更高的带宽利用率。 支路板 功能 实现客户…...

leetcode 93.复原ip地址

1.题目要求&#xff1a; 2.题目代码: class Solution { public:vector<string> result;// 记录结果// startIndex: 搜索的起始位置&#xff0c;pointNum:添加逗点的数量void backtracking(string& s, int startIndex, int pointNum) {if (pointNum 3) { // 逗点数…...

AI+视频监控:EasyCVR安防平台赋能火电制造行业的视频智能管理方案

随着信息技术的飞速发展和智能制造的深入推进&#xff0c;火电制造行业作为国民经济的重要组成部分&#xff0c;正面临着智能化转型的迫切需求。为了提升生产效率、保障设备安全、优化管理流程&#xff0c;火电制造企业迫切需要引入先进的视频监控与人工智能技术。EasyCVR安防监…...

UIP协议栈 TCP Server Client通信成功案例

文章目录 这里边有相当好的 [UIP 文档资料&#xff0c;文档位置在仓库的UIP/uip doc &#xff0c;括号内是仓库地址&#xff08;https://gitee.com/free-people-in-time-and-space/net-work-learn-note.git &#xff09;TCP Server1.main循环里做的事2.以下是main循环里相关函数…...

Android Studio Koala Feature Drop 稳定版现已推出

作者 / Android Studio 产品经理 Sandhya Mohan Android Studio Koala Feature Drop (2024.1.2) 现已推出&#xff01;&#x1f428; &#x1f517; Android Studio https://developer.android.google.cn/studio 今年早些时候&#xff0c;我们宣布每个 Android Studio 动物版本…...

胤娲科技:AI评估新纪元——LightEval引领透明化与定制化浪潮

AI评估的迷雾&#xff0c;LightEval能否拨云见日&#xff1f; 想象一下&#xff0c;你是一位AI模型的开发者&#xff0c;精心打造了一个智能助手&#xff0c;却在最终评估阶段遭遇了意外的“滑铁卢”。 问题出在哪里&#xff1f;是模型本身不够聪明&#xff0c;还是评估标准太过…...

Python安装|PyCharm Professional 下载安装教程。2024最新版,亲测使用!

一、下载地址&#xff1a; 二、Python的下载及安装&#xff1a; 1、从上面网址进入Python官网 2、安装流程图&#xff1a; 双击已经下载好的python-*.*.*-amd64.exe文件&#xff0c;开始安装 最后就等它自己安装完成就好了 3、检验是否安装完成&#xff1a; windowsR快捷键…...

JavaSwitch控制流语句

在Java中&#xff0c;switch语句是一种控制流语句&#xff0c;用于根据变量的不同值执行不同的代码块。它提供了一种替代if-else语句的方式&#xff0c;使代码更简洁和易于阅读。以下是switch语句的基本语法和使用示例。 基本语法 switch (expression) {case value1:// 执行代码…...

PCL 3D-SIFT关键点检测(Z方向梯度约束

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 SIFT关键点检测 2.1.2 可视化函数 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff08;长期更新&#…...

肺结节分割与提取系统(基于传统图像处理方法)

Matlab肺结节分割(肺结节提取)源程序&#xff0c;GUI人机界面版本。使用传统图像分割方法&#xff0c;非深度学习方法。使用LIDC-IDRI数据集。 工作如下&#xff1a; 1、读取图像。读取原始dicom格式的CT图像&#xff0c;并显示&#xff0c;绘制灰度直方图&#xff1b; 2、图像…...

ESP32 COAP 客户端观察者模式下,GET服务器的例程

目录 环境准备 示例代码 代码解释 初始化: CoAP 上下文和会话: 注册响应处理函数: 创建和发送 GET 请求: 处理响应: 主循环: 注意事项 ESP32 是一款功能强大的微控制器,支持多种通信协议,包括 CoAP(Constrained Application Protocol)。CoAP 是一种专为物联…...

【Kubernetes】常见面试题汇总(五十七)

目录 125. K8S 创建服务 status 为 ErrlmagePull&#xff1f; 126.不能进入指定容器内部&#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二&#xff09;” 。 题目 …...

Java 设计模式 构建者模式

文章目录 1 概念2 使用方法1 创建步骤&#xff1a;2 使用步骤&#xff1a; 参考 1 概念 builder模式又叫建造者模式&#xff0c;属于创建型模式 作用&#xff1a;将一个复杂对象的构建与他的表示分离&#xff0c;可以一步一步构建对象&#xff0c;而不是使用构造函数构造一次…...

建设企业网站如何建

首先&#xff0c;企业网站是企业数字化转型的重要组成部分。在数字化浪潮的冲击下&#xff0c;企业需要通过建设网站来实现信息化管理&#xff0c;提高工作效率。通过企业网站&#xff0c;企业可以便捷地发布最新产品信息、公司新闻、招聘信息等&#xff0c;极大地提升了信息传…...

C++ inline 的更进一步理解

文章目录 1.概述2.ODR(One Definition Rule)问题3.范例测试代码4.好坏分析 ODR: One Definition Rule&#xff0c;即单一定义规则&#xff0c; C 语言中非常重要的一项规则&#xff0c;它确保程序的行为一致性并避免链接时出现冲突。ODR 的核心思想是在整个程序中&#xff0c;每…...

海康威视云台相机图像获取

直接上代码&#xff1a; import cv2# 替换为正确的RTSP链接 rtsp_url rtsp://admin:abcd12345192.168.1.64:554/h264/ch1/main/av_stream cap cv2.VideoCapture(rtsp_url)if not cap.isOpened():print("无法打开视频流&#xff0c;检查RTSP URL和凭证") else:whil…...

什么是词嵌入(Word Embedding)

1. 什么是词嵌入(Word Embedding) ⾃然语⾔是⼀套⽤来表达含义的复杂系统。在这套系统中&#xff0c;词是表义的基本单元。顾名思义&#xff0c;词向量是⽤来表⽰词的向量&#xff0c;也可被认为是词的特征向量或表征。把词映射为实数域向量的技术也叫词嵌⼊&#xff08;word e…...

LSTM时间序列模型实战——预测上证指数走势

LSTM时间序列模型实战——预测上证指数走势 关于作者 作者&#xff1a;小白熊 作者简介&#xff1a;精通python、matlab、c#语言&#xff0c;擅长机器学习&#xff0c;深度学习&#xff0c;机器视觉&#xff0c;目标检测&#xff0c;图像分类&#xff0c;姿态识别&#xff0c;…...

基于 STM32F407 的 SPI Flash下载算法

目录 一、概述二、自制 FLM 文件1、修改使用的芯片2、修改输出算法的名称3、其它设置4、修改配置文件 FlashDev.c5、文件 FlashPrg.c 的实现 三、验证算法 一、概述 本文将介绍如何使用 MDK 创建 STM32F407 的 SPI Flash 下载算法。 其中&#xff0c;SPI Flash 芯片使用的是 W…...

力扣之1355.活动参与者

题目&#xff1a; Sql 测试用例&#xff1a; Create table If Not Exists Friends (id int, name varchar(30), activity varchar(30)); Create table If Not Exists Activities (id int, name varchar(30)); Truncate table Friends; insert into Friends (id, name, acti…...

数据资产治理:构建敏捷与安全的数据管理体系

在当今数字化的盛况下&#xff0c;作为核心资产的数据已经越发受到企业的重视。但是随着公司的逐步壮大&#xff0c;如何分析这些数据以及如何有效治理数据资产&#xff0c;以确保安全性、合规性以及易用性&#xff0c;是企业面临的重大挑战。数聚股份将从多年从业经验深度探讨…...