构建基于知识图谱的医药智能体Agent - 基于Llama 3.1、NVIDIA NIM 和 LangChain实践

使用ChatGPT创建

虽然大多数人关注RAG与非结构化文本的关系,比如公司文档或技术文档,但我对基于结构化信息的检索系统持乐观态度,特别是知识图谱。关于GraphRAG,特别是微软的实现,[重磅 - 微软官宣正式在GitHub开源GraphRAG],令人非常兴奋。然而,在他们的实现中,输入数据是以文档形式存在的非结构化文本,这些文本通过大型语言模型(LLM)转换为知识图谱。

在这篇博客文章中,我们将展示如何在包含来自FDA不良事件报告系统(FAERS-https://www.fda.gov/drugs/questions-and-answers-fdas-adverse-event-reporting-system-faers/fda-adverse-event-reporting-system-faers-public-dashboard)的结构化信息的知识图谱上实现检索器,该系统提供关于药物不良事件AE的信息。如果你曾经尝试过知识图谱和检索,你的第一反应可能是使用LLM生成数据库查询,从知识图谱中检索相关信息以回答给定的问题。然而,使用LLM生成数据库查询仍在发展中,可能尚未提供最一致或最强大的解决方案。那么,目前可行的替代方案是什么呢?

我认为目前最好的解决方案是所谓的动态查询生成。此方法并不完全依赖于LLM生成完整的查询,而是利用一个逻辑层从预定义的输入参数确定性地生成数据库查询。该解决方案可以使用支持函数调用的LLM来实现。使用函数调用特性的优势在于能向LLM定义它应如何为函数准备结构化输入。这种方法确保了查询生成过程的可控性和一致性,同时允许用户输入的灵活性。

动态查询生成流程 作者提供的图像

该图示展示了理解用户提问以获取特定信息的过程。该流程包括三个主要步骤:

  1. 用户询问关于35岁以下人群使用药物Lyrica的常见副作用的问题。

  2. 大型语言模型决定调用哪个函数以及所需的参数。在这个例子中,它选择了一个名为“side_effects”的函数,参数包括药物“Lyrica”和最大年龄35岁。

  3. 识别出的函数和参数被用来确定性和动态地生成数据库查询(Cypher)语句,以检索相关信息。

函数调用支持对高级大型语言模型用例至关重要,例如允许大型语言模型根据用户意图使用多个检索器或构建多代理流程。我写了一些关于使用商业大型语言模型及其原生函数调用支持的文章。然而,在这篇博客文章中,我们将使用Llama-3.1,这是一种出色的开源大型语言模型,具备原生函数调用支持,并且刚刚发布-[开源引领新时代 -Meta重磅发布Llama3.1 405B,可媲美GPT-4o级 及 社评]。

代码可在GitHub上获得

https://github.com/tomasonjo/blogs/blob/master/llm/nvidia_neo4j_langchain.ipynb

建立知识图谱

我们将使用Neo4j,这是一个本地图形数据库,用于存储不良事件信息。您可以通过以下链接(https://sandbox.neo4j.com/?usecase=healthcare-analytics)设置一个免费的云Sandbox项目,该项目配备了预先填充的FAERS。实例化的数据库实例具有以下架构的图。

不良事件图谱模式 作者提供的图片

该模式以案例节点为中心,该节点连接药物安全报告的各个方面,包括相关药物、经历的反应、结果和开处方的治疗。每种药物的特征在于它是主药、辅药、合并用药还是相互作用药物。案例还与药企信息、患者年龄组以及报告来源相关联。该模式允许以结构化的方式跟踪和分析药物、其反应和结果之间的关系。

我们将通过实例化一个Neo4jGraph对象来创建与数据库的连接。

os.environ\["NEO4J\_URI"\] = "bolt://18.206.157.187:7687"  
os.environ\["NEO4J\_USERNAME"\] = "neo4j"  
os.environ\["NEO4J\_PASSWORD"\] = "elevation-reservist-thousands"  
  
graph = Neo4jGraph(refresh\_schema=False)

搭建LLM环境

有许多选项可以托管开源的LLM,比如Llama-3.1。在这篇博客中,我们将使用NVIDIA API目录,它提供NVIDIA NIM推理微服务,并支持Llama 3.1模型的函数调用。当您创建一个帐户时,您将获得1000个令牌,这足够完成这篇博客。您需要创建一个API密钥并将其复制到笔记本中。

os.environ\["NVIDIA\_API\_KEY"\] = "nvapi-"  
llm = ChatNVIDIA(model="meta/llama-3.1-70b-instruct")

我们将使用llama-3.1–70b,因为8b版本在函数定义中的可选参数上存在一些问题。NVIDIA NIM微服务的一个好处是,如果您有安全或其他担忧,可以轻松地在本地托管它们,因此它可以非常方便地替换,您只需向LLM配置中添加一个url参数。

\# connect to an local NIM running at localhost:8000,   
\# specifying a specific model  
llm = ChatNVIDIA(  
  base\_url="http://localhost:8000/v1",   
  model="meta/llama-3.1-70b-instruct"  
)

工具定义

在这个例子中,我们将配置一个具有四个可选参数的工具。基于这些参数,我们将构建一个相应的Cypher语句,用于从知识图谱中检索相关信息。具体来说,我们的工具将能够根据输入的药物、年龄和药企识别出最常见的副作用。

@tool  
def get\_side\_effects(    drug: Optional\[str\] = Field(        description="disease mentioned in the question. Return None if no mentioned."    ),  
    min\_age: Optional\[int\] = Field(        description="Minimum age of the patient. Return None if no mentioned."    ),  
    max\_age: Optional\[int\] = Field(        description="Maximum age of the patient. Return None if no mentioned."    ),  
    manufacturer: Optional\[str\] = Field(        description="manufacturer of the drug. Return None if no mentioned."    ),):  
    """Useful for when you need to find common side effects."""  
    params = {}  
    filters = \[\]  
    side\_effects\_base\_query = """  
    MATCH (c:Case)-\[:HAS\_REACTION\]->(r:Reaction), (c)-\[:IS\_PRIMARY\_SUSPECT\]->(d:Drug)  
    """  
    if drug and isinstance(drug, str):  
        candidate\_drugs = \[el\["candidate"\] for el in get\_candidates(drug, "drug")\]  
        if not candidate\_drugs:  
            return "The mentioned drug was not found"  
        filters.append("d.name IN $drugs")  
        params\["drugs"\] = candidate\_drugs  
  
    if min\_age and isinstance(min\_age, int):  
        filters.append("c.age > $min\_age ")  
        params\["min\_age"\] = min\_age  
    if max\_age and isinstance(max\_age, int):  
        filters.append("c.age < $max\_age ")  
        params\["max\_age"\] = max\_age  
    if manufacturer and isinstance(manufacturer, str):  
        candidate\_manufacturers = \[  
            el\["candidate"\] for el in get\_candidates(manufacturer, "manufacturer")  
        \]  
        if not candidate\_manufacturers:  
            return "The mentioned manufacturer was not found"  
        filters.append(  
            "EXISTS {(c)<-\[:REGISTERED\]-(:Manufacturer {manufacturerName: $manufacturer})}"  
        )  
        params\["manufacturer"\] = candidate\_manufacturers\[0\]  
  
    if filters:  
        side\_effects\_base\_query += " WHERE "  
        side\_effects\_base\_query += " AND ".join(filters)  
    side\_effects\_base\_query += """  
    RETURN d.name AS drug, r.description AS side\_effect, count(\*) AS count  
    ORDER BY count DESC  
    LIMIT 10  
    """  
    print(f"Using parameters: {params}")  
    data = graph.query(side\_effects\_base\_query, params=params)  
    return data  

get_side_effects函数旨在通过指定的搜索条件从知识图谱中检索药物的常见副作用。它接受可选参数,包括药物名称、患者年龄范围和药物制造商,以自定义搜索。每个参数都有一个描述,这些描述与函数描述一起传递给大型语言模型,使其理解如何使用这些参数。然后该函数根据提供的输入构建动态Cypher查询,针对知识图谱执行此查询,并返回结果副作用数据。

让我们测试该函数。

get\_side\_effects("lyrica")  
\# Using parameters: {'drugs': \['LYRICA', 'LYRICA CR'\]}  
\# \[{'drug': 'LYRICA', 'side\_effect': 'Pain', 'count': 32},  
\#  {'drug': 'LYRICA', 'side\_effect': 'Fall', 'count': 21},  
\# {'drug': 'LYRICA', 'side\_effect': 'Intentional product use issue', 'count': 20},  
\# {'drug': 'LYRICA', 'side\_effect': 'Insomnia', 'count': 19},  
\# ...

我们的工具首先将问题中提到的“lyrica”药物映射到知识图谱中的“[‘LYRICA’, ‘LYRICA CR’]”值,然后执行相应的Cypher语句以查找最常见的副作用。

基于图谱的大模型智能体

唯一需要做的就是配置一个可以使用定义工具回答有关药物副作用问题的LLM代理

智能体数据流 作者提供的图

图展示了用户与Llama-3.1智能体互动以询问药物副作用。智能体访问副作用工具,从知识图谱中检索信息,以向用户提供相关数据。

我们将首先定义提示模板。

prompt = ChatPromptTemplate.from\_messages(  
    \[  
        (  
            "system",  
            "You are a helpful assistant that finds information about common side effects. "  
            "If tools require follow up questions, "  
            "make sure to ask the user for clarification. Make sure to include any "  
            "available options that need to be clarified in the follow up questions "  
            "Do only the things the user specifically requested. ",  
        ),  
        MessagesPlaceholder(variable\_name="chat\_history"),  
        ("user", "{input}"),  
        MessagesPlaceholder(variable\_name="agent\_scratchpad"),  
    \]  
)

提示模板包括系统消息、可选的聊天记录和用户输入。agent_scratchpad 保留给 LLM,因为它有时需要执行多个步骤才能回答问题,比如执行和从工具中检索信息。LangChain 库通过使用 bind_tools 方法,使得为 LLM 添加工具变得简单。

tools = \[get\_side\_effects\]  
llm\_with\_tools = llm.bind\_tools(tools=tools)  
agent = (  
    {  
        "input": lambda x: x\["input"\],  
        "chat\_history": lambda x: \_format\_chat\_history(x\["chat\_history"\])  
        if x.get("chat\_history")  
        else \[\],  
        "agent\_scratchpad": lambda x: format\_to\_openai\_function\_messages(  
            x\["intermediate\_steps"\]  
        ),  
    }  
    | prompt  
    | llm\_with\_tools  
    | OpenAIFunctionsAgentOutputParser()  
)  
  
agent\_executor = AgentExecutor(agent=agent, tools=tools, verbose=True).with\_types(  
    input\_type=AgentInput, output\_type=Output  
)

代理通过一系列转换和处理程序处理输入,这些处理程序格式化聊天记录,应用带有绑定工具的LLM,并解析输出。最后,代理设置了一个执行器来管理执行流程,指定输入和输出类型,并包括详细日志记录的冗长设置。

现在让我们测试代理。

agent\_executor.invoke(  
    {  
        "input": "What are the most common side effects when using lyrica for people below 35 years old?"  
    }  
)

结果

Agent execution. Image by author.

LLM识别到需要使用get_side_effects函数并提供适当的参数。该函数动态生成Cypher语句,获取相关信息,并将其返回给LLM以生成最终答案。

Summary摘要

函数调用能力是对开源模型如Llama 3.1的强大补充,使与外部数据源和工具的互动更加结构化和可控。除了查询非结构化文档之外,基于图形的代理为与知识图谱和结构化数据的互动提供了令人兴奋的可能性。使用像NVIDIA NIM(https://build.nvidia.com/explore/discover)微服务这样的平台轻松托管这些模型,使它们变得越来越易于获取

如常,代码在GitHub上可用

https://github.com/tomasonjo/blogs/blob/master/llm/nvidia_neo4j_langchain.ipynb

如何学习大模型 AI ?

由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。

但是具体到个人,只能说是:

“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。

这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

在这里插入图片描述

第一阶段(10天):初阶应用

该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。

  • 大模型 AI 能干什么?
  • 大模型是怎样获得「智能」的?
  • 用好 AI 的核心心法
  • 大模型应用业务架构
  • 大模型应用技术架构
  • 代码示例:向 GPT-3.5 灌入新知识
  • 提示工程的意义和核心思想
  • Prompt 典型构成
  • 指令调优方法论
  • 思维链和思维树
  • Prompt 攻击和防范

第二阶段(30天):高阶应用

该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。

  • 为什么要做 RAG
  • 搭建一个简单的 ChatPDF
  • 检索的基础概念
  • 什么是向量表示(Embeddings)
  • 向量数据库与向量检索
  • 基于向量检索的 RAG
  • 搭建 RAG 系统的扩展知识
  • 混合检索与 RAG-Fusion 简介
  • 向量模型本地部署

第三阶段(30天):模型训练

恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。

到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?

  • 为什么要做 RAG
  • 什么是模型
  • 什么是模型训练
  • 求解器 & 损失函数简介
  • 小实验2:手写一个简单的神经网络并训练它
  • 什么是训练/预训练/微调/轻量化微调
  • Transformer结构简介
  • 轻量化微调
  • 实验数据集的构建

第四阶段(20天):商业闭环

对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。

  • 硬件选型
  • 带你了解全球大模型
  • 使用国产大模型服务
  • 搭建 OpenAI 代理
  • 热身:基于阿里云 PAI 部署 Stable Diffusion
  • 在本地计算机运行大模型
  • 大模型的私有化部署
  • 基于 vLLM 部署大模型
  • 案例:如何优雅地在阿里云私有部署开源大模型
  • 部署一套开源 LLM 项目
  • 内容安全
  • 互联网信息服务算法备案

学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。

如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

在这里插入图片描述

  • 18
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值