项目介绍 Python实现基于知识图谱的中药方剂智能问答系统(含模型描述及部分示例代码)专栏近期有大量优惠 还请多多点一下关注 加油 谢谢 你的鼓励是我前行的动力 谢谢支持 加油 谢谢
目录
Python实现基于知识图谱的中药方剂智能问答系统的详细项目实例... 2
请注意此篇内容只是一个项目介绍 更多详细内容可直接联系博主本人 或者访问对应标题的完整博客或者文档下载页面(含完整的程序,GUI设计和代码详解)... 2
Python实现基于知识图谱的中药方剂智能问答系统的详细项目实例
请注意此篇内容只是一个项目介绍 更多详细内容可直接联系博主本人
或者访问对应标题的完整博客或者文档下载页面(含完整的程序,GUI设计和代码详解)
基于知识图谱的中药方剂智能问答系统,立足于中医药这一具有数千年历史沉淀的复杂知识体系,尝试用结构化、可计算的方式重构方剂相关知识,并通过自然语言问答的形式,为学习者、研究者以及临床决策提供辅助。中药方剂本身具有高度结构化的特点:每一首方剂包含方名、组成药物、剂量比例、君臣佐使配伍关系、主治病证、功效特点、禁忌人群、加减变化及典籍来源等多维信息。传统学习途径主要依赖纸质书籍与教师讲解,检索效率低、知识联结松散、难以形成全局视图。一旦需要跨方剂比较、按功效筛选、按症状反向查询合适方剂,就会耗费大量时间查阅资料并人工归纳。知识图谱技术则为这一问题提供了天然匹配的解决思路:通过图数据库形式将方剂、药物、症状、病机等构建成“实体-关系-属性”的网络,使各个点之间的联系清晰可见,并支持多跳推理和复杂查询。
在人工智能与自然语言处理快速发展的背景下,构建面向中药方剂的知识图谱,并在其之上叠加问答系统,不仅可以显著提升知识获取效率,还能帮助理解中医方剂的配伍逻辑与辨证思维。传统的文本检索系统多依赖关键字匹配,遇到用户表达模糊、语义不完整、同义词或口语化描述时,往往无法准确定位需求。中药领域又存在大量专有名词、古汉语表达以及多种命名方式(如同一方剂在不同典籍中的异名),这使得纯文本检索的局限更加明显。而知识图谱结合语义解析之后,可以通过将自然语言问题转化为结构化查询,直接在图中找到对应节点及其关联信息,有效缓解语义歧义和表达差异带来的问题。
对于中医药教育和科研而言,这样的系统能够成为一种新型的数字化教学和辅助研究工具。学习者可以提出诸如“治疗风寒感冒的经典方剂有哪些”“四君子汤的组成和各药作用是什么”“哪些方剂含有黄芪且具有补气升阳功效”等问题,系统会在图谱中检索相关实体与关系,并给出结构化结果及简要文字解释。在此过程中,学习者不仅能获得答案,还能逐步形成对“方剂-药物-功效-证候-病机”之间网状联系的直观认知,这比单纯死记硬背更有助于理解与记忆。同时,科研工作者也可以通过图谱进行数据级探索,例如分析某类药物在不同方剂中的配伍模式,或者统计特定病机类别对应的高频方剂组合,为后续数据挖掘和临床研究提供基础。
从行业应用层面看,中药方剂智能问答系统有望嵌入互联网医疗平台、中医馆导诊系统、智能随访工具等多种场景中,为医生、药师以及患者提供不同粒度的知识服务。对于专业人员,系统可以提供更细致的组成、功效、经典原文及现代研究链接;对于普通用户,则可以输出经过简化和安全过滤的解释,帮助理解处方的基本作用及注意事项。尤其在中医走向国际化的大背景下,如何将复杂的中医知识以结构化、可交互、可多语言扩展的方式呈现,是一个具有现实意义的课题,基于知识图谱的问答系统则是具有代表性的实践路径之一。
从技术栈角度看,该项目以Python为主要开发语言,综合运用图数据库(如Neo4j)、中文分词与实体识别工具(如jieba、自定义词典)、简单规则或轻量级语义解析、以及基于检索的问答逻辑,实现从数据构建、图谱建模到问答交互的完整流程。通过这类实践,不仅可以验证知识图谱在垂直专业领域的实际效果,也为后续引入深度学习模型(如预训练语言模型与图神经网络)奠定基础。更重要的是,这个项目可以形成一个可扩展的架构:初期以典型方剂为主,后续逐步引入更多方剂、单味中药、经络穴位、现代药理研究等信息,最终构建出一个面向中医临床与教育的综合知识图谱平台。
项目目标与意义
学术与教育价值
从学术与教学角度出发,中药方剂智能问答系统的首要目标是将零散的知识进行系统化组织,并以互动方式辅助学习。传统中医方剂学教学中,学习内容庞杂且记忆压力巨大,例如常见学习要求包括掌握数百首方剂的组成、功效、主治、配伍特点及加减运用。依靠纸质教材和课堂讲授,学习者往往难以在短时间内形成清晰的知识网络,只能通过机械记忆应付考试,长期记忆效果较差。构建基于知识图谱的系统,通过将方剂、药物、证候、病机等信息以节点和关系的形式呈现,使原本抽象的内容变成可视化的网络结构,有助于学习者从全局视角理解知识。同时,通过自然语言问答形式,学习者可以随时提出问题,系统返回精准的结构化答案,并可进一步点击相关节点进行延伸浏览,形成“问题-答案-探索”的学习闭环。这样不仅提升了学习兴趣,也提升了自学效率和知识整合能力。
临床辅助与知识传承
中医临床实践极度依赖经验积累与知识传承,许多名家名方背后蕴含着深刻的辨证思路和配伍原则。项目的一项重要目标,是在尊重临床复杂性的前提下,通过知识图谱将这些经验以结构化形式保存和关联,形成一个可检索、可扩展、可持续更新的方剂知识库。对于临床医生或中医药师而言,系统可以作为知识查询的辅助工具,例如在遇到某些少见证型或疑难杂症时,可以快速检索相关病机、经典记载及常用方剂,为临床思考提供参考。同时,系统还可以帮助年轻医生理解经典方剂的配伍逻辑,而不仅仅停留在背诵层面。对于中医药传承来说,将方剂知识数字化、结构化并与现代信息技术结合,有助于防止知识断层,并为未来的数据驱动型中医研究提供基础资源,从而在现代语境中延续与发展传统中医理论。
中医知识的普及与公众健康教育
在大众健康意识不断提升的背景下,社会大众对中医药知识的兴趣与需求明显增加,但普及渠道仍以碎片化文章、短视频等为主,内容质量参差不齐,容易出现误读甚至误用。项目的另一个重要意义,在于借助知识图谱的严谨结构与可控内容输出,为公众提供一个相对可信、可追溯来源的中药方剂科普问答通道。普通用户可以用自然语言提问,如“感冒喝感冒冲剂以外还有哪些经典中药方”“四物汤是不是适合所有女性长期喝”“哪些方剂孕妇需要特别注意”等,系统可以在知识图谱中查找相关方剂及注意事项,再通过人性化表达输出回答,同时强调“仅供参考,具体用药需在专业医师指导下进行”,在满足好奇心的同时减小误导风险。通过这种方式,中医知识在大众中的传播将更加系统化、理性化,有助于纠正一些民间误区,提升公众健康素养。
推动传统医药与现代人工智能融合
从技术发展和产业创新视角看,中药方剂智能问答项目是探索传统医药与现代人工智能技术深度融合的重要实践之一。知识图谱技术正是人工智能领域从“感知智能”走向“认知智能”的关键支撑之一,中医药领域由于知识高度结构化、逻辑性强、概念体系丰富,非常适合作为知识图谱应用的典型场景。通过在中医方剂领域构建高质量知识图谱并实现自然语言问答,有助于验证技术路线的可行性,并为后续在经络腧穴、疾病-方剂匹配、个体化方案推荐等更复杂任务上拓展应用提供经验。此外,这类项目还可促进多学科协同创新:中医药专家提供知识体系与校验标准,人工智能工程师提供建模与系统实现能力,教育工作者提供教学场景需求输入,从而在实践中形成跨界合作的创新生态,为传统医药走向数字化、智能化提供切实路径。
项目挑战及解决方案
中医知识结构复杂与语义多义问题
中医理论体系自身具有高度抽象和概念性,如“气血阴阳”、“寒热虚实”、“表里内外”等概念均存在较强的理论内涵和语义弹性,同一术语在不同语境下含义可能存在差异。方剂命名中还存在大量历史沿革与异名,如某些方剂在不同医家体系中名字不同但组成近似,或者名字相同但加减略有差异,这就给知识建模带来语义歧义问题。此外,中医古籍表达多为文言文,与现代汉语或医疗用语之间存在较大差距,直接文本抽取往往难以理解重点,更难直接用于结构化建模。面对这些挑战,需要在知识抽取与建模阶段进行精细设计,例如建立标准化命名体系,将方剂的标准名称、常用别名、典籍名称统一映射到同一实体节点;对于关键概念如证候、病机等,需要结合权威教材定义标准术语,并在图谱中统一编码,避免语义混乱。对于存在多义的术语,则可以通过增加上下位关系、类别标签等方式进行 disambiguation,使系统在回答问题时能够根据上下文进行正确的语义选择。语义多义问题还体现在自然语言问答阶段,用户提问中可能使用口语化描述(如“上火”“体虚”“湿气重”),系统需要通过自定义词典和规则,将这些通俗说法映射到相对标准的中医术语,例如“内热”“气虚”“湿盛”,这样才能在知识图谱中准确检索相关内容。
数据来源多样性与结构化建模难度
构建高质量中药方剂知识图谱,必须面对数据来源多样的问题。数据可能来自权威教材、临床路径指南、中医古籍整理本、现代研究论文以及线上公开数据库等,这些资料在书写方式、细节粒度、术语标准方面存在不一致。例如,同一方剂的组成在不同文献中可能出现剂量差异或药材名称用字差异(“姜”与“生姜”“干姜”区分等),某些资料可能只给出主治病证而不详述功效与配伍关系。为确保图谱结构清晰、语义统一,需要制定详细的数据建模规范,从实体类型设计(方剂、药物、证候、病机、症状、经典来源)到属性字段定义(拼音、别名、剂量、性味归经等),再到关系类型定义(治疗、包含、对应、出自等),都需要形成可执行的 schema。在数据录入与清洗过程中,可使用Python脚本对原始Excel、CSV、文本文件进行规范化处理,通过正则表达式、固定格式解析、人工校验结合的方式,将关键信息提取为结构化表格。随后通过批量导入工具写入图数据库,利用图查询语言检查数据的一致性与完整性。对于存在冲突或不确定的信息,应在数据中明确记录来源与版本,以便后续扩展与修订。同时,在建模时可以适当预留扩展字段,方便未来引入更多维度的信息,如现代药理作用、临床试验结果等,使图谱能够随时间演化,而不至于在初期设计中被定死结构。
自然语言问题解析与图谱查询映射
中药方剂智能问答的核心难点之一,在于如何将用户用自然语言提出的问题转化为可在知识图谱上运行的结构化查询。中医问题表达形式丰富,例如“治疗风寒感冒的方剂有什么”“含有黄芪并且补气升阳的方剂有哪些”“四君子汤适合什么体质的人服用”等,这些问题中既包含实体(“风寒感冒”“黄芪”“四君子汤”),也包含意图(“治疗”“功效”“适合体质”),还包含过滤条件(“补气升阳”“含有某药物”)。要解决这一问题,可以采用“规则模板解析+实体识别+简单语义分类”的组合策略。首先构建自定义词典,将方剂名称、药物名称、常见功效、常见证候等关键词加入分词词库,以提升分词与实体识别准确率;其次设计若干类问题模板,如“治疗X的方剂”“含有Y的方剂”“Z方剂的组成”“Z方剂的功效与主治”等,通过关键字定位问题类型,再根据类型生成对应图查询语句(例如Neo4j中的Cypher查询)。在预处理阶段,可以先对用户问题进行分词、停用词过滤、实体识别,然后根据识别结果填充到模板中,构造安全的查询语句。如果遇到无法匹配的复杂问题,可以退回到较为宽松的检索模式,如只匹配方剂名称并返回其基本信息,避免系统出现“无解”或错误回答。在这一过程中,规则与模板需要反复调试和完善,通过不断收集实际问题样本进行优化,使解析模块在可靠性与覆盖率之间取得平衡。
项目模型架构
知识图谱数据层设计
知识图谱的基础是清晰、合理的数据层设计。针对中药方剂领域,数据层主要包括实体类型、关系类型以及实体属性三个方面。实体类型可以分为若干核心类别:方剂实体(表示具体的中药方)、中药材实体(表示单味中药,如黄芪、党参)、证候实体(如“气虚”“血瘀”)、病症或疾病实体(如“风寒感冒”“失眠”)、功效实体(如“补气”“活血化瘀”)、经典文献实体(如“伤寒论”“金匮要略”)等。关系类型则用于刻画实体之间的连接,例如“包含关系”(方剂包含药物)、“主治关系”(方剂治疗证候或疾病)、“具有功效”(方剂或单药具有某种功效)、“出自”(方剂出自某典籍)、“对应病机”(证候对应某种病机理论)等。每种实体与关系在设计时都应遵循语义清晰、命名规范的原则,避免出现含糊不清的泛化关系。
在实体属性方面,需要为不同类型的实体设计各自合适的字段。例如,方剂实体可以包含名称(name)、拼音(pinyin)、别名(aliases)、类型(如补益方、表里双解方)、功效描述(effect_desc)、主治描述(indication_desc)、方解(explanation)、注意事项(caution)、来源书籍(source_book)等;中药材实体则可以包含性味(四气五味)、归经、主要功效、常用剂量范围、常见配伍禁忌等。证候实体可以记录其定义、主要症状表现、舌脉特征、病机分析等。通过这样的细粒度属性设计,知识图谱不仅能支持结构化查询,也能为未来的文本生成或深度学习任务提供丰富特征。
数据层的设计还需考虑规范化与扩展性。一方面,尽量使用统一编码和术语,如采用统一的方剂编码、中药编码,以方便跨数据库对接与数据维护;另一方面,要为未来扩展预留空间,例如为实体预留 external_id 字段以链接到外部数据库,为属性预留可选字段以记录多版本信息(例如不同典籍中的方剂变体),为关系预留权重或来源字段,以表达不同文献对某一关系的支持程度。通过这些设计,整个知识图谱可以在保持结构稳定的同时,持续吸纳新数据并与外部资源互联。
图数据库与存储结构
知识图谱需要落地到具体的存储系统中,常用方案是选择图数据库。图数据库以“节点-边-属性”的天然图结构存储数据,非常适合表达实体与实体之间复杂、多样的关系,并支持高效的图遍历与关系查询。在中药方剂场景中,选用如Neo4j这类成熟的图数据库,可以利用其图查询语言(Cypher)方便地描绘查询意图,例如“找出包含某味药材的所有方剂”“查询某个证候相关的所有方剂和它们的主药”等,从而将自然语言问题解析后的结构化需求映射为具体查询语句。
在存储结构设计上,需要根据项目访问模式进行优化。方剂相关查询往往涉及“从方剂出发”或“从症状/证候反向寻找方剂”两种方向。例如,从方剂名称查询其组成、功效、主治等属于单跳或双跳查询;从症状或证候出发查询相关方剂,则可能涉及多跳搜索(如症状→证候→方剂)。为提高查询性能,可以为常用的索引字段建立索引,例如方剂名称、药材名称、证候名称等,便于快速定位起始节点。同时,存储结构中可以适当冗余某些关系,或者引入中间节点,如“方剂类别节点”“功效类别节点”,使某些聚类查询更为高效。
在技术实现层面,可采用Python官方的Neo4j驱动库进行连接与操作,通过会话(session)与事务(transaction)执行写入与读取操作。批量导入阶段,可以将清洗好的结构化数据转换为Cypher的批量创建语句,或者使用Neo4j提供的批量导入工具;运行阶段则主要通过参数化查询实现安全的动态查询,避免字符串拼接引起的注入风险。通过良好的索引与查询模式设计,图数据库能够在中等规模数据量下保持较好的性能表现,为智能问答的实时响应提供支撑。
问题解析与意图识别模块
在知识图谱架构之上,问题解析与意图识别模块扮演着桥梁角色:一端接收用户的自然语言输入,另一端生成可执行的图查询请求。该模块通常由三个子步骤构成:分词与实体识别、问题类型分类、参数抽取与模板匹配。对于中医场景,普通分词工具难以正确处理大量专业术语和方剂名,因此需要在通用分词库基础上加载自定义词典,将常见方剂名、药材名、证候术语加入词库,以提升断词准确度。实体识别可以采用基于词典匹配与规则的方式,从分词结果中识别出方剂实体、中药材实体、症状/证候词等,并标注对应类型。
问题类型分类则是根据句式和关键词判断用户意图,如“是什么”“有什么用”“治疗什么”“有哪些方剂”“组成是什么”“含有哪些药”等语义线索,可以归纳为若干问题类型:查询方剂基本信息、查询方剂组成、查询方剂主治与功效、根据症状或证候反向查询方剂、根据中药材查询相关方剂、根据功效查询方剂等。每种问题类型对应一到多个查询模板。例如,对于“X方剂的组成是什么”这类问题,可以对应模板“匹配名称为X的方剂节点,查找与其通过包含关系相连的药材节点,并返回药材名称及用量属性”;而“治疗Y的方剂有哪些”则对应模板“匹配与证候或疾病Y存在主治关系的方剂节点,并返回方剂名称”等。
在参数抽取阶段,需要从实体识别结果中选择与问题类型匹配的实体作为查询参数,并将其填充进模板,生成具体的图查询语句。为提高鲁棒性,可以在实体匹配失败时尝试模糊匹配,如使用包含匹配、拼音匹配或别名表匹配等手段,尽可能覆盖用户不完全准确的输入。整个解析模块在实现时可以采用规则+配置文件的方式,将问题类型与查询模板的映射写入配置,便于后续维护与扩展。当问题类型无法识别或参数缺失时,可以返回提示信息,例如要求用户提供更具体的名称,或展示相关的候选实体列表,引导用户继续对话。
图谱查询与推理模块
在完成问题解析后,图谱查询与推理模块负责执行具体查询并对结果进行适当的整合。图谱查询核心是根据意图模块生成的Cypher语句访问图数据库,并获取结构化结果,包括节点及其属性、节点间的关系等。查询模块需要封装统一接口,对上层屏蔽图数据库的细节,提供如“查找方剂组成”“根据症状查方剂”“查询方剂功效”等函数调用。每个接口内部通过参数化Cypher语句进行实际查询,并对结果进行格式化,如将药材节点列表转为Python字典数组,包含名称、剂量、角色(君臣佐使)等字段,便于后续回答生成模块使用。
推理模块则属于更高层的能力,主要基于图结构进行简单推理与关联。例如,根据用户的描述“气虚发热、乏力”,系统可以解析到“气虚”与“发热”两个特征,并在图谱中找到与这两个特征同时相关的方剂或证候节点,进行交集过滤,从而推荐更契合的问题答案。另一个典型推理场景是根据药物功效进行间接推荐,例如用户提问“有哪些方剂可以补气升阳”,系统可以先在图谱中查找具有“补气升阳”功效的药材或功效节点,再通过这些节点找到相关方剂,形成一个基于功效的方剂集合结果。这种推理不一定需要复杂的逻辑推理引擎,而可以通过多步图查询和集合操作实现。
在扩展方向上,还可以考虑在图谱上叠加权重或统计信息,如记录方剂使用频率、文献引用次数、现代临床研究支持度等,在推荐结果排序时参考这些权重,从而优先展示更经典或证据更充分的方剂。随着数据和模型的丰富,推理模块也可以逐步引入简单的规则引擎或基于图神经网络的关系预测模型,探索未显式记录的潜在关联,为科研和知识发现提供更多可能。
回答生成与交互层
完成图谱查询与推理之后,回答生成与交互层负责将结构化结果转换为自然语言输出,并以清晰、友好的方式呈现给用户。该层的设计需要兼顾专业性与可读性:对于专业用户,可能希望看到较为详尽的组成、剂量、功效、主治、方解等;对于非专业用户,则需要使用更通俗易懂的语言,并在关键位置强调安全与禁忌信息。回答生成可以采用模板加填充的方式,即根据问题类型选择对应的回答模板,再将查询结果填入模板中的占位符,从而实现可控且易维护的输出策略。例如对于“某方剂的组成”问题,可以使用模板“某方剂由以下药物组成:……,其中X为君药,Y为臣药,Z为佐药,W为使药”,而对于“治疗某病的方剂有哪些”问题,则可以使用模板“常用于治疗某病的方剂包括:A、B、C……,具体使用需结合个人体质与病情,由专业中医师辨证施治”。
交互层还需要处理异常情况和边界场景,如查询结果为空、存在多个候选实体、问题解析不成功等。在这些情况下,应及时给出明确且有帮助的提示,而不是简单返回“无结果”。例如,当方剂名称存在多种近似时,可以返回候选列表供用户选择;当查询为空时,可以建议用户调整表达方式或提供更具体信息。对于长列表结果,还需要进行分页或简要摘要,避免信息过载。交互层的实现可以采用命令行界面、Web接口或者集成到聊天机器人中,具体取决于应用场景。无论采用哪种形式,核心目标都是在保持专业严谨的前提下,使中药方剂知识以更友好的方式走近使用者,提升系统的实用性与可接受度。
项目模型描述及代码示例
数据建模与入库示例代码
class TCMKGBuilder: # 定义一个用于构建中药知识图谱的类,封装连接、建模和数据写入等功能
def __init__(self, uri, user, password): # 定义初始化方法,用于接收数据库连接参数并建立驱动
self.driver = GraphDatabase.driver(uri, auth=(user, password)) # 使用提供的地址和账号密码创建Neo4j驱动对象,后续通过它打开会话
def close(self): # 定义关闭方法,用于在程序结束时释放数据库连接资源
def create_indexes(self): # 定义创建索引的方法,用于提高查询性能和保证名称唯一性
cypher_list = [ # 定义一个Cypher语句列表,包含多个索引或唯一约束的创建语句
"CREATE CONSTRAINT IF NOT EXISTS FOR (f:Formula) REQUIRE f.name IS UNIQUE", # 为方剂节点的name属性建立唯一约束,避免重复创建
"CREATE CONSTRAINT IF NOT EXISTS FOR (h:Herb) REQUIRE h.name IS UNIQUE", # 为中药材节点的name属性建立唯一约束,保证同名药材只有一个节点
"CREATE CONSTRAINT IF NOT EXISTS FOR (s:Symptom) REQUIRE s.name IS UNIQUE", # 为症状节点的name属性建立唯一约束,便于快速定位
] # 结束Cypher语句列表的定义
with self.driver.session() as session: # 打开一个与Neo4j的会话,使用上下文管理器保证用完自动关闭
session.run(cypher) # 在当前会话中运行Cypher语句,将索引/约束配置写入数据库
def create_formula(self, name, pinyin=None, alias=None, effect_desc=None, indication_desc=None): # 定义创建方剂节点的方法,接收名称及若干可选属性
cypher = """ # 使用三引号定义多行Cypher语句模板,便于在其中编写参数化查询
MERGE (f:Formula {name: $name}) # 通过MERGE保证如果存在同名方剂则重用,否则创建新的方剂节点
ON CREATE SET # 指定当节点第一次被创建时要设置的属性列表
f.pinyin = $pinyin, # 设置方剂节点的拼音字段,便于拼音检索
f.alias = $alias, # 设置方剂节点的别名字段,以支持异名查询
f.effect_desc = $effect_desc, # 设置方剂节点的功效描述字段,用于自然语言回答生成
f.indication_desc = $indication_desc # 设置方剂节点的主治证候描述字段,帮助理解适应症
RETURN f # 返回刚创建或取得的方剂节点,以便调用者进一步使用
""" # 结束Cypher语句定义
with self.driver.session() as session: # 打开一个数据库会话,用于执行写入操作
result = session.run( # 在会话中运行Cypher语句,通过参数传入具体方剂属性值
cypher,
name=name, # 将函数参数name绑定到Cypher语句中的$name占位符
alias=alias, # 将方剂别名作为参数传入,使图谱记录更多名称信息
effect_desc=effect_desc, # 将功效描述传入数据库,支持查询时直接展示
return result.single()[0] # 取得查询结果中的第一条记录的第一个字段(即返回的方剂节点对象)
def create_herb(self, name, property_desc=None, meridian=None, effect_desc=None): # 定义创建中药材节点的方法,记录性味、归经和功效
cypher = """ # 定义一段Cypher语句用于创建或获取中药材节点
MERGE (h:Herb {name: $name}) # 使用MERGE根据药材名称查找或创建Herb节点,避免重复
ON CREATE SET # 当药材节点首次创建时设置若干基础属性
h.meridian = $meridian, # 设置归经字段,记录药物作用的经络归属
h.effect_desc = $effect_desc # 设置主要功效描述字段,方便后续展示药物作用
RETURN h # 返回新建或已存在的药材节点对象
""" # 结束Cypher语句字符串
with self.driver.session() as session: # 打开Neo4j会话,以执行写操作
result = session.run( # 运行Cypher语句并传入药材相关参数
cypher,
name=name, # 将药材名称绑定到$name占位符
property_desc=property_desc, # 将性味说明作为参数传入数据库
meridian=meridian, # 将归经信息作为参数传入数据库
) # 完成执行调用
cypher = """ # 定义执行创建关系的Cypher语句模板
MATCH (f:Formula {name: $formula_name}) # 匹配名称为指定方剂名的Formula节点作为关系的起点
MATCH (h:Herb {name: $herb_name}) # 匹配名称为指定药材名的Herb节点作为关系的终点
MERGE (f)-[r:CONTAINS]->(h) # 使用MERGE创建或复用从方剂到药材的CONTAINS关系
ON CREATE SET # 当该关系第一次创建时设置其附加属性
r.dosage = $dosage # 设置药材在方剂中的剂量信息,例如“9g”“3钱”等
RETURN r # 返回创建或获取的关系对象,用于调试或后续使用
with self.driver.session() as session: # 打开会话,以便对数据库执行写操作
cypher,
formula_name=formula_name, # 将方剂名称作为查询条件传入
herb_name=herb_name, # 将药材名称作为查询条件传入
role=role, # 将角色信息传入,便于在图谱中区分药物地位
dosage=dosage # 将剂量信息传入,帮助更完整表达方剂配伍
) # 结束运行
return result.single()[0] # 返回关系对象,便于检查创建是否成功
def create_symptom(self, name, desc=None): # 定义方法用于创建症状节点,记录名称和描述
cypher = """ # 定义症状节点创建的Cypher语句模板
MERGE (s:Symptom {name: $name}) # 根据症状名称合并或创建Symptom节点,保持唯一性
s.desc = $desc # 设置症状描述字段,用自然语言解释症状表现
RETURN s # 返回症状节点对象,以便调用方使用或调试
""" # 结束Cypher字符串
with self.driver.session() as session: # 打开数据库会话
result = session.run(cypher, name=name, desc=desc) # 执行Cypher并传入症状名称和描述参数
return result.single()[0] # 返回结果中的Symptom节点对象
cypher = """ # 定义创建关系的Cypher查询模板
MATCH (f:Formula {name: $formula_name}) # 匹配指定名称的方剂节点,作为关系起点
MATCH (s:Symptom {name: $symptom_name}) # 匹配指定名称的症状节点,作为关系终点
MERGE (f)-[r:TREATS]->(s) # 创建或合并从方剂到症状的TREATS关系,表示治疗关联
RETURN r # 返回该关系对象,用于确认是否创建成功
""" # 结束Cypher语句定义
with self.driver.session() as session: # 打开会话
result = session.run( # 执行Cypher语句并注入参数
cypher,
formula_name=formula_name, # 传入方剂名称以匹配Formula节点
symptom_name=symptom_name # 传入症状名称以匹配Symptom节点
) # 结束执行调用
return result.single()[0] # 返回关系对象
def demo_build_basic_kg(): # 定义一个演示函数,用于构建一个基础的示例中药方剂知识图谱
builder = TCMKGBuilder("bolt://localhost:7687", "neo4j", "password") # 创建知识图谱构建器实例,并传入Neo4j连接地址和账号密码
builder.create_indexes() # 调用方法为方剂、药材、症状、证候等节点建立唯一约束,保证数据规范
builder.create_formula( # 创建一个示例方剂节点,展示如何录入基础信息
name="四君子汤", # 设置方剂名称为“四君子汤”,一种经典补气方剂
pinyin="Si Jun Zi Tang", # 设置方剂拼音,便于拼音检索与展示
alias="四君汤", # 设置方剂别名,记录常见简称
effect_desc="补气健脾,用于脾胃气虚证。", # 填写方剂的主要功效描述,说明适应证类型
indication_desc="面色萎黄,食少乏力,舌淡脉虚等脾胃气虚表现。" # 填写主治描述,便于问答时说明适应症
) # 完成方剂节点创建调用
builder.create_herb( # 创建第一个药材节点,人参,作为补气要药
name="人参", # 药材名称为人参
property_desc="甘、微苦,微温。", # 性味描述,说明味甘微苦、性微温
meridian="脾、肺经", # 归经描述,人参多入脾肺二经
effect_desc="大补元气,补脾益肺,生津安神。" # 功效描述,说明补气生津等作用
) # 完成人参节点创建
builder.create_herb( # 创建第二个药材节点,白术,为健脾燥湿要药
name="白术", # 药材名称白术
property_desc="苦、甘,温。", # 性味苦甘,性温
meridian="脾、胃经", # 归经为脾胃二经
) # 完成白术节点创建
builder.create_herb( # 创建第三个药材节点,茯苓
name="茯苓", # 药材名称茯苓
meridian="心、脾、肾经", # 归经为心脾肾
effect_desc="健脾渗湿,宁心安神。" # 功效描述,说明利水渗湿和安神作用
) # 完成茯苓节点创建
builder.create_herb( # 创建第四个药材节点,炙甘草
name="炙甘草", # 药材名称炙甘草
effect_desc="补气健脾,缓急止痛,调和诸药。" # 功效描述,强调调和诸药的作用
) # 完成炙甘草节点创建
builder.create_contains_relation("四君子汤", "人参", role="君", dosage="9g") # 创建方剂与人参的包含关系,标记为君药,并记录剂量
builder.create_contains_relation("四君子汤", "白术", role="臣", dosage="9g") # 创建方剂与白术的包含关系,标记为臣药,记录剂量
builder.create_contains_relation("四君子汤", "茯苓", role="佐", dosage="9g") # 创建方剂与茯苓的包含关系,标记为佐药,记录剂量
builder.create_contains_relation("四君子汤", "炙甘草", role="使", dosage="6g") # 创建方剂与炙甘草的包含关系,标记为使药,记录剂量
builder.create_symptom("脾胃气虚", desc="食少、乏力、面色萎黄、舌淡脉虚等表现。") # 创建症状节点“脾胃气虚”,并给出典型症状描述
builder.create_treats_relation("四君子汤", "脾胃气虚") # 创建四君子汤治疗脾胃气虚的关系节点,表明主治方向
builder.close() # 调用关闭方法,释放Neo4j连接资源,结束演示构建过程
if __name__ == "__main__": # 判断当前模块是否作为主程序运行,如果是则执行演示函数
demo_build_basic_kg() # 调用演示函数,运行一遍基础知识图谱构建流程,创建示例方剂与药材
问题解析与Cypher生成示例代码
import re # 导入正则表达式模块,用于对中文问题进行模式匹配和清洗
import jieba # 导入jieba中文分词库,用于将问题句子切分为词语序列
class QuestionParser: # 定义问题解析类,负责将自然语言问题转换为内部结构化表示
def __init__(self, formula_names, herb_names, symptom_names): # 初始化方法接收方剂名、药材名、症状名列表,用于实体识别
self.formula_names = set(formula_names) # 将方剂名称列表转为集合,加快“是否存在”检查速度
self.herb_names = set(herb_names) # 将药材名称列表转为集合,便于快速判断某个词是否为药材
self.symptom_names = set(symptom_names) # 将症状名称列表转为集合,用于从分词结果中识别症状实体
for name in formula_names: # 遍历所有方剂名称
jieba.add_word(name, tag="FORMULA") # 将方剂名称添加到jieba词典中,并标注自定义词性FORMULA,提高识别率
for name in herb_names: # 遍历所有药材名称
jieba.add_word(name, tag="HERB") # 将药材名称加入jieba词典并标注为HERB,使分词能识别整词
jieba.add_word(name, tag="SYMPTOM") # 将症状名称加入词典并标记为SYMPTOM,增强症状识别能力
def parse(self, question): # 定义解析方法,输入自然语言问题字符串,输出结构化解析结果
q = question.strip() # 去除问题前后的空白字符,确保处理干净的文本
q = re.sub(r"[??!.!。]", "", q) # 使用正则表达式移除句末常见标点,避免干扰模式匹配
words = list(jieba.cut(q)) # 使用jieba对问题进行分词,将句子切分成词语列表
formula_candidates = [w for w in words if w in self.formula_names] # 从分词结果中挑出在方剂名称集合中的词语作为候选方剂实体
herb_candidates = [w for w in words if w in self.herb_names] # 从分词结果中挑出被识别为药材名称的词语
symptom_candidates = [w for w in words if w in self.symptom_names] # 从分词结果中提取疑似症状的词语
has_comp = any(key in q for key in ["组成", "配伍", "含有", "有哪些药", "包括哪些药"]) # 检查问题中是否包含与组成相关的关键词,判断是否询问方剂组成
has_treat = any(key in q for key in ["治疗", "治", "用于"]) # 检查问题中是否出现治疗类词汇,判断是否为反向查询方剂
parsed = {"type": None, "formula": None, "herb": None, "symptom": None} # 初始化解析结果字典,预留问题类型与实体字段
if formula_candidates and has_comp: # 若识别到方剂名称并且问题包含组成词汇,则判定为“方剂组成查询”
parsed["type"] = "FORMULA_COMPOSITION" # 设置问题类型为方剂组成查询
parsed["formula"] = formula_candidates[0] # 选取第一个候选方剂作为目标方剂,后续可扩展为歧义消解
elif formula_candidates and has_effect: # 若有方剂实体并出现功效类词汇,则判定为“方剂功效查询”
parsed["type"] = "FORMULA_EFFECT" # 设置问题类型为方剂功效与主治查询
parsed["formula"] = formula_candidates[0] # 使用第一个识别到的方剂名称作为查询对象
elif has_treat and symptom_candidates: # 若包含治疗类动词且识别出症状实体,则判定为“根据症状反向查方剂”
parsed["type"] = "SYMPTOM_TO_FORMULA" # 设置问题类型为由症状寻找方剂
elif herb_candidates and any(key in q for key in ["方剂", "处方"]): # 若识别到药材名称且提到方剂或处方,认为是在询问“含某药的方剂”
parsed["type"] = "HERB_TO_FORMULA" # 设置问题类型为通过药材反向查找方剂
parsed["herb"] = herb_candidates[0] # 使用识别到的药材名作为反向查询条件
parsed["type"] = "FORMULA_INFO" # 设置问题类型为方剂基本信息查询
parsed["formula"] = formula_candidates[0] # 使用识别到的方剂作为查询主体
else: # 若未匹配任何模式或实体,则归为“未知类型”
class CypherGenerator: # 定义Cypher语句生成器类,用于根据解析结果构造具体图查询语句
def __init__(self): # 初始化方法,目前不需要外部参数
pass # 使用pass占位,表示此处无具体初始化逻辑
def generate(self, parsed): # 定义生成方法,根据问题解析结果字典生成对应的Cypher查询和参数
qtype = parsed.get("type") # 读取解析结果中的问题类型字段,作为分支条件
if qtype == "FORMULA_COMPOSITION": # 若是方剂组成查询,则构造查询该方剂包含的所有药材
cypher = """ # 定义适用于组成查询的Cypher语句
MATCH (f:Formula {name: $name})- [r:CONTAINS]->(h:Herb) # 匹配指定名称的方剂及其连接的药材节点和包含关系
""" # 结束Cypher字符串
return cypher, params # 返回Cypher语句和参数供上层执行
if qtype == "FORMULA_EFFECT": # 若是方剂功效查询,则查询方剂的功效与主治描述
cypher = """ # 定义方剂功效查询的Cypher语句
MATCH (f:Formula {name: $name}) # 匹配名称为指定值的方剂节点
RETURN f.name AS formula, f.effect_desc AS effect, f.indication_desc AS indication # 返回方剂名、功效描述和主治描述
""" # 结束Cypher字符串
params = {"name": parsed["formula"]} # 将解析出的方剂名称设置为参数
return cypher, params # 返回语句及参数
if qtype == "SYMPTOM_TO_FORMULA": # 若是由症状反向查方剂类型
cypher = """ # 定义基于症状检索方剂的Cypher语句
RETURN DISTINCT f.name AS formula # 返回不重复的方剂名称列表,作为候选治疗方案
""" # 结束Cypher字符串
params = {"symptom": parsed["symptom"]} # 设置症状名称为查询参数
return cypher, params # 返回Cypher语句及参数字典
cypher = """ # 定义通过药材检索相关方剂的Cypher语句
MATCH (f:Formula)-[:CONTAINS]->(h:Herb {name: $herb}) # 匹配包含指定药材的所有方剂节点
RETURN DISTINCT f.name AS formula # 返回不重复的方剂名称,说明可能配伍此药材
""" # 结束Cypher字符串
params = {"herb": parsed["herb"]} # 将药材名称设为参数
return cypher, params # 返回语句及参数
if qtype == "FORMULA_INFO": # 若是方剂基本信息查询类型
cypher = """ # 定义查询方剂基本信息的Cypher语句
MATCH (f:Formula {name: $name}) # 匹配指定名称的方剂节点
OPTIONAL MATCH (f)-[r:CONTAINS]->(h:Herb) # 可选匹配方剂与药材的包含关系,允许没有配伍信息时也能返回基本属性
RETURN f.name AS formula, f.effect_desc AS effect, # 返回方剂名称和功效描述
f.indication_desc AS indication, # 返回方剂主治描述
collect({herb:h.name, role:r.role, dosage:r.dosage}) AS herbs # 将所有相关药材组合成列表字典,包含名称、角色和剂量
""" # 结束Cypher语句
params = {"name": parsed["formula"]} # 设置方剂名称参数
return cypher, params # 返回该查询语句及参数
return None, {} # 若问题类型未知或未支持,则返回空语句和空参数字典,供上层进行异常处理
查询执行与回答生成示例代码
from neo4j import GraphDatabase # 再次导入Neo4j驱动,以便在问答阶段执行只读查询
class TCMQASystem: # 定义中药方剂智能问答系统核心类,整合解析、查询和回答生成功能
self.driver = GraphDatabase.driver(uri, auth=(user, password)) # 创建Neo4j驱动对象,用于建立与图数据库的连接
self.parser = QuestionParser(formula_names, herb_names, symptom_names) # 初始化问题解析器实例,传入方剂、药材、症状名称用于实体识别
self.cypher_gen = CypherGenerator() # 初始化Cypher生成器实例,用于将解析结果转换为查询语句
def close(self): # 定义关闭方法,用于释放图数据库连接资源
self.driver.close() # 调用驱动的close方法,断开与Neo4j服务的连接
parsed = self.parser.parse(question) # 调用解析器对问题进行结构化解析,获取类型与实体信息
if parsed["type"] == "UNKNOWN": # 若解析结果表明问题类型未知
cypher, params = self.cypher_gen.generate(parsed) # 根据解析结果生成对应的Cypher查询语句和参数
if not cypher: # 若未生成有效的Cypher语句,说明暂未支持该类型问题
return "目前尚未支持这种类型的提问,后续将逐步完善更多问答能力。" # 返回能力范围提示,避免系统给出错误答案
with self.driver.session() as session: # 打开一个与Neo4j的会话,用于执行只读查询
result = session.run(cypher, **params) # 运行生成的Cypher语句,并传入参数字典作为查询条件
records = list(result) # 将查询结果转换为列表,便于多次遍历与长度判断
if not records: # 若结果列表为空,表示图谱中没有匹配节点或关系
qtype = parsed["type"] # 获取问题类型,用于根据不同类别编排回答内容
if qtype == "FORMULA_COMPOSITION": # 若问题类型为方剂组成查询
formula_name = records[0]["formula"] # 从第一条返回记录中提取方剂名称,以用于组织回答框架
parts = [] # 初始化一个列表,用于存储每味药材的组成描述字符串
for rec in records: # 遍历查询得到的每一条记录,每条代表一味药材的信息
herb = rec["herb"] # 提取药材名称字段
dosage = rec["dosage"] or "" # 提取剂量字段,如果为None则替换为空串
if role: # 如果角色信息存在
segment += f"({role})" # 在药材名称后附加角色说明,用括号标注君臣佐使
if dosage: # 如果剂量信息存在
parts.append(segment) # 将组装好的药材描述片段加入列表
comp_str = "、".join(parts) # 使用顿号将所有药材描述片段连接成一段文字
answer_text = f"{formula_name}的常用组成包括:{comp_str}。具体剂量可依据体质与病情在医师指导下调整。" # 组合完整回答,描述方剂组方并强调需专业指导调整剂量
return answer_text # 返回生成好的回答文本
if qtype == "FORMULA_EFFECT": # 若问题类型为方剂功效与主治查询
formula_name = rec["formula"] # 获取方剂名称字符串
effect = rec["effect"] or "功效信息暂缺" # 获取功效描述,若为空则填入提示语“功效信息暂缺”
answer_text = f"{formula_name}的主要功效是:{effect} 主治范围主要包括:{indication} 实际运用时需结合具体证候,由专业中医师辨证施治。" # 按模板组合功效和主治说明,并提醒需要辨证使用
return answer_text # 返回完整的功效回答
if qtype == "SYMPTOM_TO_FORMULA": # 若问题类型为根据症状推荐方剂
symptom_name = parsed["symptom"] # 从解析结果中取出症状名称字符串
formulas = [rec["formula"] for rec in records] # 将所有返回记录中的方剂名称汇总成列表
formulas_str = "、".join(formulas) # 用顿号将方剂名称列表连接成字符串
return answer_text # 返回推荐方剂的回答文本
herb_name = parsed["herb"] # 提取药材名称,用于在回答中说明查询对象
formulas = [rec["formula"] for rec in records] # 汇总所有匹配到的方剂名称
formulas_str = "、".join(formulas) # 使用顿号组合成简洁的方剂名称串
answer_text = f"常见含有“{herb_name}”的方剂有:{formulas_str}。不同方剂中该药所处剂量和配伍对象不同,具体作用亦有差异。" # 生成回答文本,说明含该药材的方剂列表并提示配伍差异性
return answer_text # 返回关于药材相关方剂的回答
rec = records[0] # 取第一条记录,其中包含方剂基本信息和组合后的药材列表
formula_name = rec["formula"] # 获取方剂名称
effect = rec["effect"] or "功效信息暂缺" # 获取功效描述,若无则提示暂缺
indication = rec["indication"] or "主治信息暂缺" # 获取主治信息,若无则提示暂缺
herbs_info = rec["herbs"] or [] # 获取药材组合信息列表,若无则用空列表占位
herb_parts = [] # 初始化列表,用于构造药材描述片段
for item in herbs_info: # 遍历列表中的每个药材条目,每个条目是包含名称、角色、剂量的字典
herb = item.get("herb") # 从字典中取出药材名称
continue # 继续处理下一条
role = item.get("role") or "" # 取出配伍角色字段,若无则置为空串
dosage = item.get("dosage") or "" # 取出剂量字段,若无则置为空串
seg = herb # 初始化药材描述片段,以药材名字开始
if role: # 若存在角色信息
seg += f"({role})" # 在药材名称后加上角色说明
if dosage: # 若存在剂量信息
seg += f"{dosage}" # 在药材描述末尾加上剂量内容
herb_parts.append(seg) # 将组装好的药材描述添加到列表中
answer_text = f"{formula_name}的基本信息概述:功效:{effect} 主治:{indication} 常用组成:{herbs_str} 实际使用时需由专业中医师根据证候具体调整用量及加减。" # 组合方剂概况,包括功效、主治和组成概要,并强调个体化调整
return answer_text # 返回综合信息的回答文本
formulas = ["四君子汤"] # 构造一个方剂名称列表,此处仅包含示例方剂“四君子汤”
herbs = ["人参", "白术", "茯苓", "炙甘草"] # 构造一个常用药材名称列表,对应前面建模中用到的四味药
symptoms = ["脾胃气虚"] # 构造一个症状名称列表,此处以“脾胃气虚”作为示例
qa = TCMQASystem("bolt://localhost:7687", "neo4j", "password", formulas, herbs, symptoms) # 创建问答系统实例,传入数据库连接和实体名称列表以完成初始化
q1 = "四君子汤的组成是什么?" # 定义第一个示例提问,询问四君子汤的组成情况
print("提问:", q1) # 在控制台输出当前提问内容,便于观察问答效果
print("回答:", qa.answer(q1)) # 调用问答系统的answer方法获取回答,并打印出生成的答案文本
q2 = "四君子汤有什么功效?" # 定义第二个示例提问,询问四君子汤的功效
print("提问:", q2) # 输出第二个提问字符串
print("回答:", qa.answer(q2)) # 调用answer并打印对应回答,用于展示功效查询效果
q3 = "脾胃气虚可以用什么方剂?" # 定义第三个示例问题,从症状角度反向查询可用方剂
print("回答:", qa.answer(q3)) # 获取并打印系统针对该症状推荐的方剂回答
qa.close() # 调用关闭方法,释放Neo4j连接资源,结束问答演示过程
demo_qa_interaction() # 若是主程序,则调用演示函数,运行一遍问答交互示例
数据建模与入库示例代码
class TCMKGBuilder: # 定义一个用于构建中药知识图谱的类,封装连接、建模和数据写入等功能
def __init__(self, uri, user, password): # 定义初始化方法,用于接收数据库连接参数并建立驱动
self.driver = GraphDatabase.driver(uri, auth=(user, password)) # 使用提供的地址和账号密码创建Neo4j驱动对象,后续通过它打开会话
def close(self): # 定义关闭方法,用于在程序结束时释放数据库连接资源
def create_indexes(self): # 定义创建索引的方法,用于提高查询性能和保证名称唯一性
cypher_list = [ # 定义一个Cypher语句列表,包含多个索引或唯一约束的创建语句
"CREATE CONSTRAINT IF NOT EXISTS FOR (f:Formula) REQUIRE f.name IS UNIQUE", # 为方剂节点的name属性建立唯一约束,避免重复创建
"CREATE CONSTRAINT IF NOT EXISTS FOR (h:Herb) REQUIRE h.name IS UNIQUE", # 为中药材节点的name属性建立唯一约束,保证同名药材只有一个节点
"CREATE CONSTRAINT IF NOT EXISTS FOR (s:Symptom) REQUIRE s.name IS UNIQUE", # 为症状节点的name属性建立唯一约束,便于快速定位
] # 结束Cypher语句列表的定义
with self.driver.session() as session: # 打开一个与Neo4j的会话,使用上下文管理器保证用完自动关闭
session.run(cypher) # 在当前会话中运行Cypher语句,将索引/约束配置写入数据库
def create_formula(self, name, pinyin=None, alias=None, effect_desc=None, indication_desc=None): # 定义创建方剂节点的方法,接收名称及若干可选属性
cypher = """ # 使用三引号定义多行Cypher语句模板,便于在其中编写参数化查询
MERGE (f:Formula {name: $name}) # 通过MERGE保证如果存在同名方剂则重用,否则创建新的方剂节点
ON CREATE SET # 指定当节点第一次被创建时要设置的属性列表
f.pinyin = $pinyin, # 设置方剂节点的拼音字段,便于拼音检索
f.alias = $alias, # 设置方剂节点的别名字段,以支持异名查询
f.effect_desc = $effect_desc, # 设置方剂节点的功效描述字段,用于自然语言回答生成
f.indication_desc = $indication_desc # 设置方剂节点的主治证候描述字段,帮助理解适应症
RETURN f # 返回刚创建或取得的方剂节点,以便调用者进一步使用
""" # 结束Cypher语句定义
with self.driver.session() as session: # 打开一个数据库会话,用于执行写入操作
result = session.run( # 在会话中运行Cypher语句,通过参数传入具体方剂属性值
cypher,
name=name, # 将函数参数name绑定到Cypher语句中的$name占位符
alias=alias, # 将方剂别名作为参数传入,使图谱记录更多名称信息
effect_desc=effect_desc, # 将功效描述传入数据库,支持查询时直接展示
return result.single()[0] # 取得查询结果中的第一条记录的第一个字段(即返回的方剂节点对象)
def create_herb(self, name, property_desc=None, meridian=None, effect_desc=None): # 定义创建中药材节点的方法,记录性味、归经和功效
cypher = """ # 定义一段Cypher语句用于创建或获取中药材节点
MERGE (h:Herb {name: $name}) # 使用MERGE根据药材名称查找或创建Herb节点,避免重复
ON CREATE SET # 当药材节点首次创建时设置若干基础属性
h.meridian = $meridian, # 设置归经字段,记录药物作用的经络归属
h.effect_desc = $effect_desc # 设置主要功效描述字段,方便后续展示药物作用
RETURN h # 返回新建或已存在的药材节点对象
""" # 结束Cypher语句字符串
with self.driver.session() as session: # 打开Neo4j会话,以执行写操作
result = session.run( # 运行Cypher语句并传入药材相关参数
cypher,
name=name, # 将药材名称绑定到$name占位符
property_desc=property_desc, # 将性味说明作为参数传入数据库
meridian=meridian, # 将归经信息作为参数传入数据库
) # 完成执行调用
cypher = """ # 定义执行创建关系的Cypher语句模板
MATCH (f:Formula {name: $formula_name}) # 匹配名称为指定方剂名的Formula节点作为关系的起点
MATCH (h:Herb {name: $herb_name}) # 匹配名称为指定药材名的Herb节点作为关系的终点
MERGE (f)-[r:CONTAINS]->(h) # 使用MERGE创建或复用从方剂到药材的CONTAINS关系
ON CREATE SET # 当该关系第一次创建时设置其附加属性
r.dosage = $dosage # 设置药材在方剂中的剂量信息,例如“9g”“3钱”等
RETURN r # 返回创建或获取的关系对象,用于调试或后续使用
with self.driver.session() as session: # 打开会话,以便对数据库执行写操作
cypher,
formula_name=formula_name, # 将方剂名称作为查询条件传入
herb_name=herb_name, # 将药材名称作为查询条件传入
role=role, # 将角色信息传入,便于在图谱中区分药物地位
dosage=dosage # 将剂量信息传入,帮助更完整表达方剂配伍
) # 结束运行
return result.single()[0] # 返回关系对象,便于检查创建是否成功
def create_symptom(self, name, desc=None): # 定义方法用于创建症状节点,记录名称和描述
cypher = """ # 定义症状节点创建的Cypher语句模板
MERGE (s:Symptom {name: $name}) # 根据症状名称合并或创建Symptom节点,保持唯一性
s.desc = $desc # 设置症状描述字段,用自然语言解释症状表现
RETURN s # 返回症状节点对象,以便调用方使用或调试
""" # 结束Cypher字符串
with self.driver.session() as session: # 打开数据库会话
result = session.run(cypher, name=name, desc=desc) # 执行Cypher并传入症状名称和描述参数
return result.single()[0] # 返回结果中的Symptom节点对象
cypher = """ # 定义创建关系的Cypher查询模板
MATCH (f:Formula {name: $formula_name}) # 匹配指定名称的方剂节点,作为关系起点
MATCH (s:Symptom {name: $symptom_name}) # 匹配指定名称的症状节点,作为关系终点
MERGE (f)-[r:TREATS]->(s) # 创建或合并从方剂到症状的TREATS关系,表示治疗关联
RETURN r # 返回该关系对象,用于确认是否创建成功
""" # 结束Cypher语句定义
with self.driver.session() as session: # 打开会话
result = session.run( # 执行Cypher语句并注入参数
cypher,
formula_name=formula_name, # 传入方剂名称以匹配Formula节点
symptom_name=symptom_name # 传入症状名称以匹配Symptom节点
) # 结束执行调用
return result.single()[0] # 返回关系对象
def demo_build_basic_kg(): # 定义一个演示函数,用于构建一个基础的示例中药方剂知识图谱
builder = TCMKGBuilder("bolt://localhost:7687", "neo4j", "password") # 创建知识图谱构建器实例,并传入Neo4j连接地址和账号密码
builder.create_indexes() # 调用方法为方剂、药材、症状、证候等节点建立唯一约束,保证数据规范
builder.create_formula( # 创建一个示例方剂节点,展示如何录入基础信息
name="四君子汤", # 设置方剂名称为“四君子汤”,一种经典补气方剂
pinyin="Si Jun Zi Tang", # 设置方剂拼音,便于拼音检索与展示
alias="四君汤", # 设置方剂别名,记录常见简称
effect_desc="补气健脾,用于脾胃气虚证。", # 填写方剂的主要功效描述,说明适应证类型
indication_desc="面色萎黄,食少乏力,舌淡脉虚等脾胃气虚表现。" # 填写主治描述,便于问答时说明适应症
) # 完成方剂节点创建调用
builder.create_herb( # 创建第一个药材节点,人参,作为补气要药
name="人参", # 药材名称为人参
property_desc="甘、微苦,微温。", # 性味描述,说明味甘微苦、性微温
meridian="脾、肺经", # 归经描述,人参多入脾肺二经
effect_desc="大补元气,补脾益肺,生津安神。" # 功效描述,说明补气生津等作用
) # 完成人参节点创建
builder.create_herb( # 创建第二个药材节点,白术,为健脾燥湿要药
name="白术", # 药材名称白术
property_desc="苦、甘,温。", # 性味苦甘,性温
meridian="脾、胃经", # 归经为脾胃二经
) # 完成白术节点创建
builder.create_herb( # 创建第三个药材节点,茯苓
name="茯苓", # 药材名称茯苓
meridian="心、脾、肾经", # 归经为心脾肾
effect_desc="健脾渗湿,宁心安神。" # 功效描述,说明利水渗湿和安神作用
) # 完成茯苓节点创建
builder.create_herb( # 创建第四个药材节点,炙甘草
name="炙甘草", # 药材名称炙甘草
effect_desc="补气健脾,缓急止痛,调和诸药。" # 功效描述,强调调和诸药的作用
) # 完成炙甘草节点创建
builder.create_contains_relation("四君子汤", "人参", role="君", dosage="9g") # 创建方剂与人参的包含关系,标记为君药,并记录剂量
builder.create_contains_relation("四君子汤", "白术", role="臣", dosage="9g") # 创建方剂与白术的包含关系,标记为臣药,记录剂量
builder.create_contains_relation("四君子汤", "茯苓", role="佐", dosage="9g") # 创建方剂与茯苓的包含关系,标记为佐药,记录剂量
builder.create_contains_relation("四君子汤", "炙甘草", role="使", dosage="6g") # 创建方剂与炙甘草的包含关系,标记为使药,记录剂量
builder.create_symptom("脾胃气虚", desc="食少、乏力、面色萎黄、舌淡脉虚等表现。") # 创建症状节点“脾胃气虚”,并给出典型症状描述
builder.create_treats_relation("四君子汤", "脾胃气虚") # 创建四君子汤治疗脾胃气虚的关系节点,表明主治方向
builder.close() # 调用关闭方法,释放Neo4j连接资源,结束演示构建过程
if __name__ == "__main__": # 判断当前模块是否作为主程序运行,如果是则执行演示函数
demo_build_basic_kg() # 调用演示函数,运行一遍基础知识图谱构建流程,创建示例方剂与药材
问题解析与Cypher生成示例代码
import re # 导入正则表达式模块,用于对中文问题进行模式匹配和清洗
import jieba # 导入jieba中文分词库,用于将问题句子切分为词语序列
class QuestionParser: # 定义问题解析类,负责将自然语言问题转换为内部结构化表示
def __init__(self, formula_names, herb_names, symptom_names): # 初始化方法接收方剂名、药材名、症状名列表,用于实体识别
self.formula_names = set(formula_names) # 将方剂名称列表转为集合,加快“是否存在”检查速度
self.herb_names = set(herb_names) # 将药材名称列表转为集合,便于快速判断某个词是否为药材
self.symptom_names = set(symptom_names) # 将症状名称列表转为集合,用于从分词结果中识别症状实体
for name in formula_names: # 遍历所有方剂名称
jieba.add_word(name, tag="FORMULA") # 将方剂名称添加到jieba词典中,并标注自定义词性FORMULA,提高识别率
for name in herb_names: # 遍历所有药材名称
jieba.add_word(name, tag="HERB") # 将药材名称加入jieba词典并标注为HERB,使分词能识别整词
jieba.add_word(name, tag="SYMPTOM") # 将症状名称加入词典并标记为SYMPTOM,增强症状识别能力
def parse(self, question): # 定义解析方法,输入自然语言问题字符串,输出结构化解析结果
q = question.strip() # 去除问题前后的空白字符,确保处理干净的文本
q = re.sub(r"[??!.!。]", "", q) # 使用正则表达式移除句末常见标点,避免干扰模式匹配
words = list(jieba.cut(q)) # 使用jieba对问题进行分词,将句子切分成词语列表
formula_candidates = [w for w in words if w in self.formula_names] # 从分词结果中挑出在方剂名称集合中的词语作为候选方剂实体
herb_candidates = [w for w in words if w in self.herb_names] # 从分词结果中挑出被识别为药材名称的词语
symptom_candidates = [w for w in words if w in self.symptom_names] # 从分词结果中提取疑似症状的词语
has_comp = any(key in q for key in ["组成", "配伍", "含有", "有哪些药", "包括哪些药"]) # 检查问题中是否包含与组成相关的关键词,判断是否询问方剂组成
has_treat = any(key in q for key in ["治疗", "治", "用于"]) # 检查问题中是否出现治疗类词汇,判断是否为反向查询方剂
parsed = {"type": None, "formula": None, "herb": None, "symptom": None} # 初始化解析结果字典,预留问题类型与实体字段
if formula_candidates and has_comp: # 若识别到方剂名称并且问题包含组成词汇,则判定为“方剂组成查询”
parsed["type"] = "FORMULA_COMPOSITION" # 设置问题类型为方剂组成查询
parsed["formula"] = formula_candidates[0] # 选取第一个候选方剂作为目标方剂,后续可扩展为歧义消解
elif formula_candidates and has_effect: # 若有方剂实体并出现功效类词汇,则判定为“方剂功效查询”
parsed["type"] = "FORMULA_EFFECT" # 设置问题类型为方剂功效与主治查询
parsed["formula"] = formula_candidates[0] # 使用第一个识别到的方剂名称作为查询对象
elif has_treat and symptom_candidates: # 若包含治疗类动词且识别出症状实体,则判定为“根据症状反向查方剂”
parsed["type"] = "SYMPTOM_TO_FORMULA" # 设置问题类型为由症状寻找方剂
elif herb_candidates and any(key in q for key in ["方剂", "处方"]): # 若识别到药材名称且提到方剂或处方,认为是在询问“含某药的方剂”
parsed["type"] = "HERB_TO_FORMULA" # 设置问题类型为通过药材反向查找方剂
parsed["herb"] = herb_candidates[0] # 使用识别到的药材名作为反向查询条件
parsed["type"] = "FORMULA_INFO" # 设置问题类型为方剂基本信息查询
parsed["formula"] = formula_candidates[0] # 使用识别到的方剂作为查询主体
else: # 若未匹配任何模式或实体,则归为“未知类型”
class CypherGenerator: # 定义Cypher语句生成器类,用于根据解析结果构造具体图查询语句
def __init__(self): # 初始化方法,目前不需要外部参数
pass # 使用pass占位,表示此处无具体初始化逻辑
def generate(self, parsed): # 定义生成方法,根据问题解析结果字典生成对应的Cypher查询和参数
qtype = parsed.get("type") # 读取解析结果中的问题类型字段,作为分支条件
if qtype == "FORMULA_COMPOSITION": # 若是方剂组成查询,则构造查询该方剂包含的所有药材
cypher = """ # 定义适用于组成查询的Cypher语句
MATCH (f:Formula {name: $name})- [r:CONTAINS]->(h:Herb) # 匹配指定名称的方剂及其连接的药材节点和包含关系
""" # 结束Cypher字符串
return cypher, params # 返回Cypher语句和参数供上层执行
if qtype == "FORMULA_EFFECT": # 若是方剂功效查询,则查询方剂的功效与主治描述
cypher = """ # 定义方剂功效查询的Cypher语句
MATCH (f:Formula {name: $name}) # 匹配名称为指定值的方剂节点
RETURN f.name AS formula, f.effect_desc AS effect, f.indication_desc AS indication # 返回方剂名、功效描述和主治描述
""" # 结束Cypher字符串
params = {"name": parsed["formula"]} # 将解析出的方剂名称设置为参数
return cypher, params # 返回语句及参数
if qtype == "SYMPTOM_TO_FORMULA": # 若是由症状反向查方剂类型
cypher = """ # 定义基于症状检索方剂的Cypher语句
RETURN DISTINCT f.name AS formula # 返回不重复的方剂名称列表,作为候选治疗方案
""" # 结束Cypher字符串
params = {"symptom": parsed["symptom"]} # 设置症状名称为查询参数
return cypher, params # 返回Cypher语句及参数字典
cypher = """ # 定义通过药材检索相关方剂的Cypher语句
MATCH (f:Formula)-[:CONTAINS]->(h:Herb {name: $herb}) # 匹配包含指定药材的所有方剂节点
RETURN DISTINCT f.name AS formula # 返回不重复的方剂名称,说明可能配伍此药材
""" # 结束Cypher字符串
params = {"herb": parsed["herb"]} # 将药材名称设为参数
return cypher, params # 返回语句及参数
if qtype == "FORMULA_INFO": # 若是方剂基本信息查询类型
cypher = """ # 定义查询方剂基本信息的Cypher语句
MATCH (f:Formula {name: $name}) # 匹配指定名称的方剂节点
OPTIONAL MATCH (f)-[r:CONTAINS]->(h:Herb) # 可选匹配方剂与药材的包含关系,允许没有配伍信息时也能返回基本属性
RETURN f.name AS formula, f.effect_desc AS effect, # 返回方剂名称和功效描述
f.indication_desc AS indication, # 返回方剂主治描述
collect({herb:h.name, role:r.role, dosage:r.dosage}) AS herbs # 将所有相关药材组合成列表字典,包含名称、角色和剂量
""" # 结束Cypher语句
params = {"name": parsed["formula"]} # 设置方剂名称参数
return cypher, params # 返回该查询语句及参数
return None, {} # 若问题类型未知或未支持,则返回空语句和空参数字典,供上层进行异常处理
查询执行与回答生成示例代码
from neo4j import GraphDatabase # 再次导入Neo4j驱动,以便在问答阶段执行只读查询
class TCMQASystem: # 定义中药方剂智能问答系统核心类,整合解析、查询和回答生成功能
self.driver = GraphDatabase.driver(uri, auth=(user, password)) # 创建Neo4j驱动对象,用于建立与图数据库的连接
self.parser = QuestionParser(formula_names, herb_names, symptom_names) # 初始化问题解析器实例,传入方剂、药材、症状名称用于实体识别
self.cypher_gen = CypherGenerator() # 初始化Cypher生成器实例,用于将解析结果转换为查询语句
def close(self): # 定义关闭方法,用于释放图数据库连接资源
self.driver.close() # 调用驱动的close方法,断开与Neo4j服务的连接
parsed = self.parser.parse(question) # 调用解析器对问题进行结构化解析,获取类型与实体信息
if parsed["type"] == "UNKNOWN": # 若解析结果表明问题类型未知
cypher, params = self.cypher_gen.generate(parsed) # 根据解析结果生成对应的Cypher查询语句和参数
if not cypher: # 若未生成有效的Cypher语句,说明暂未支持该类型问题
return "目前尚未支持这种类型的提问,后续将逐步完善更多问答能力。" # 返回能力范围提示,避免系统给出错误答案
with self.driver.session() as session: # 打开一个与Neo4j的会话,用于执行只读查询
result = session.run(cypher, **params) # 运行生成的Cypher语句,并传入参数字典作为查询条件
records = list(result) # 将查询结果转换为列表,便于多次遍历与长度判断
if not records: # 若结果列表为空,表示图谱中没有匹配节点或关系
qtype = parsed["type"] # 获取问题类型,用于根据不同类别编排回答内容
if qtype == "FORMULA_COMPOSITION": # 若问题类型为方剂组成查询
formula_name = records[0]["formula"] # 从第一条返回记录中提取方剂名称,以用于组织回答框架
parts = [] # 初始化一个列表,用于存储每味药材的组成描述字符串
for rec in records: # 遍历查询得到的每一条记录,每条代表一味药材的信息
herb = rec["herb"] # 提取药材名称字段
dosage = rec["dosage"] or "" # 提取剂量字段,如果为None则替换为空串
if role: # 如果角色信息存在
segment += f"({role})" # 在药材名称后附加角色说明,用括号标注君臣佐使
if dosage: # 如果剂量信息存在
parts.append(segment) # 将组装好的药材描述片段加入列表
comp_str = "、".join(parts) # 使用顿号将所有药材描述片段连接成一段文字
answer_text = f"{formula_name}的常用组成包括:{comp_str}。具体剂量可依据体质与病情在医师指导下调整。" # 组合完整回答,描述方剂组方并强调需专业指导调整剂量
return answer_text # 返回生成好的回答文本
if qtype == "FORMULA_EFFECT": # 若问题类型为方剂功效与主治查询
formula_name = rec["formula"] # 获取方剂名称字符串
effect = rec["effect"] or "功效信息暂缺" # 获取功效描述,若为空则填入提示语“功效信息暂缺”
answer_text = f"{formula_name}的主要功效是:{effect} 主治范围主要包括:{indication} 实际运用时需结合具体证候,由专业中医师辨证施治。" # 按模板组合功效和主治说明,并提醒需要辨证使用
return answer_text # 返回完整的功效回答
if qtype == "SYMPTOM_TO_FORMULA": # 若问题类型为根据症状推荐方剂
symptom_name = parsed["symptom"] # 从解析结果中取出症状名称字符串
formulas = [rec["formula"] for rec in records] # 将所有返回记录中的方剂名称汇总成列表
formulas_str = "、".join(formulas) # 用顿号将方剂名称列表连接成字符串
return answer_text # 返回推荐方剂的回答文本
herb_name = parsed["herb"] # 提取药材名称,用于在回答中说明查询对象
formulas = [rec["formula"] for rec in records] # 汇总所有匹配到的方剂名称
formulas_str = "、".join(formulas) # 使用顿号组合成简洁的方剂名称串
answer_text = f"常见含有“{herb_name}”的方剂有:{formulas_str}。不同方剂中该药所处剂量和配伍对象不同,具体作用亦有差异。" # 生成回答文本,说明含该药材的方剂列表并提示配伍差异性
return answer_text # 返回关于药材相关方剂的回答
rec = records[0] # 取第一条记录,其中包含方剂基本信息和组合后的药材列表
formula_name = rec["formula"] # 获取方剂名称
effect = rec["effect"] or "功效信息暂缺" # 获取功效描述,若无则提示暂缺
indication = rec["indication"] or "主治信息暂缺" # 获取主治信息,若无则提示暂缺
herbs_info = rec["herbs"] or [] # 获取药材组合信息列表,若无则用空列表占位
herb_parts = [] # 初始化列表,用于构造药材描述片段
for item in herbs_info: # 遍历列表中的每个药材条目,每个条目是包含名称、角色、剂量的字典
herb = item.get("herb") # 从字典中取出药材名称
continue # 继续处理下一条
role = item.get("role") or "" # 取出配伍角色字段,若无则置为空串
dosage = item.get("dosage") or "" # 取出剂量字段,若无则置为空串
seg = herb # 初始化药材描述片段,以药材名字开始
if role: # 若存在角色信息
seg += f"({role})" # 在药材名称后加上角色说明
if dosage: # 若存在剂量信息
seg += f"{dosage}" # 在药材描述末尾加上剂量内容
herb_parts.append(seg) # 将组装好的药材描述添加到列表中
answer_text = f"{formula_name}的基本信息概述:功效:{effect} 主治:{indication} 常用组成:{herbs_str} 实际使用时需由专业中医师根据证候具体调整用量及加减。" # 组合方剂概况,包括功效、主治和组成概要,并强调个体化调整
return answer_text # 返回综合信息的回答文本
formulas = ["四君子汤"] # 构造一个方剂名称列表,此处仅包含示例方剂“四君子汤”
herbs = ["人参", "白术", "茯苓", "炙甘草"] # 构造一个常用药材名称列表,对应前面建模中用到的四味药
symptoms = ["脾胃气虚"] # 构造一个症状名称列表,此处以“脾胃气虚”作为示例
qa = TCMQASystem("bolt://localhost:7687", "neo4j", "password", formulas, herbs, symptoms) # 创建问答系统实例,传入数据库连接和实体名称列表以完成初始化
q1 = "四君子汤的组成是什么?" # 定义第一个示例提问,询问四君子汤的组成情况
print("提问:", q1) # 在控制台输出当前提问内容,便于观察问答效果
print("回答:", qa.answer(q1)) # 调用问答系统的answer方法获取回答,并打印出生成的答案文本
q2 = "四君子汤有什么功效?" # 定义第二个示例提问,询问四君子汤的功效
print("提问:", q2) # 输出第二个提问字符串
print("回答:", qa.answer(q2)) # 调用answer并打印对应回答,用于展示功效查询效果
q3 = "脾胃气虚可以用什么方剂?" # 定义第三个示例问题,从症状角度反向查询可用方剂
print("回答:", qa.answer(q3)) # 获取并打印系统针对该症状推荐的方剂回答
qa.close() # 调用关闭方法,释放Neo4j连接资源,结束问答演示过程
demo_qa_interaction() # 若是主程序,则调用演示函数,运行一遍问答交互示例
更多详细内容请访问
http://【中医药知识图谱】基于Python的中药方剂智能问答系统Python实现基于知识图谱的中药方剂智能问答系统的详细项目实例(含完整的程序,数据库和GUI设计,代码详解)_Adaboost-SVM集成分类预测资源-CSDN下载 https://download.csdn.net/download/xiaoxingkongyuxi/90130552
http:// https://download.csdn.net/download/xiaoxingkongyuxi/90130552
https://download.csdn.net/download/xiaoxingkongyuxi/90130552
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐




所有评论(0)