从基础到进阶:基于 LangChain、Streamlit 和 PubMed 构建 AI Agents (一)

499 阅读8分钟

最近休假有些空闲时间,于是我决定整理一下2024年做的AI Agents项目,并把前几天分享的经验,利用一个使用场景(PubMed筛查Agent)和代码分享给大家。在整理的过程中,我一边写,一边输出代码,结果不知不觉就形成了一个系列。今天来分享这个系列的第一篇:基础篇。从这一篇开始,我会带你一步步构建一个基于RAG技术的Streamlit应用——PubMed筛查器。这个工具结合了ChatGPT和PubMed数据库,帮助你从生物医学研究摘要中快速提取关键信息。

这个项目特别有意义——它能帮助生物科技领域的人员高效地找到所需的科研信息,解答他们的疑问。

系列中的会将代码设计灵活一些,提供一些标准接口,大家可以根据需要随时更换任何需要的大模型(LLM)或向量存储。

每篇文章我尽量会详细讲解,内容通俗易懂,就算你是新手,也能轻松跟上。

最后,我会把完整的源代码上传到Gitee,大家可以自由下载并根据自己的需求进行修改。

注意:需要体验效果的可以私信

前置条件

为了顺利跟随本系列进行实操,你需要具备以下条件:

  • 访问你选择的聊天模型,本教程中我们将使用OpenAI(gpt-4o-2024-11-20);
  • 本地开发环境,Python版本(3.11),以及你选择的IDE;
  • 我在Linux(Ubuntu)环境下工作。

应用场景——PubMed筛查器

假设你是一名医学研究人员,正在进行文献搜索,想找到与自己研究相关的线索。你可能会通过PubMed等公共科学数据库来查找相关论文。

如果有一个agents,能帮助你自动筛选出相关的论文,并提供一个互动界面,让你无需逐篇阅读摘要,就能快速提取关键信息,那该多高效!

我们将开发这样的agents,帮助研究人员用自然语言查询PubMed数据库,快速获取并分析数据,从而大大提高文献搜索的效率。

用例工作流程

  • 医学研究人员提出问题(用自然语言)。
  • 通过大型语言模型(LLM)将问题转换为PubMed的查询格式。
  • 使用PubMed API搜索并获取相关的研究摘要。
  • 从摘要中生成数据向量,并存储到向量数据库。
  • 使用RAG技术回答研究人员的问题,之后他们可以通过聊天界面继续提问。

什么是RAG,为什么它有用?

检索增强生成(RAG)是一种结合语言模型和检索系统的技术,它可以从外部来源获取真实的信息,并将这些信息融入到生成的文本中。这样,生成的内容就更加准确可靠。

开始构建应用

好了,接下来我们动手,开始为医学研究人员打造这个AI agents吧!

环境搭建

首先,我们需要为这个项目创建一个虚拟环境。你可以用任何你喜欢的Python环境管理工具来创建。对我来说,我使用的是_venv_。

  • 创建一个新的项目文件夹:
`mkdir langchain-rag-screener`
  • 创建虚拟环境:
`python -m venv langenv`
  • 激活虚拟环境:
`sourcelangenv/bin/activate`

设置项目结构

接下来,我们会按照以下的结构来搭建项目:

.
├── app
│ ├── app.py
│ ├── components
│ │ ├── chat_utils.py
│ │ ├── llm.py
│ │ └── prompts.py
│ └── tests
│ └── test_chat_utils.py
├── assets
│ └── m.png
│ └── favicon32-32.ico
└── environment
└── requirements.txt

environment/requirements.txt

这个文件会列出你项目需要的所有工具和库。对于我们这个项目,依赖项如下:

streamlit==1.41.1 
python-dotenv==1.0.1
langchain==0.3.14
langchain-community==0.3.14
langchain-core==0.3.30
langchain-openai==0.3.1
langchain-text-splitters==0.3.5
langsmith==0.2.11

创建一个requirements.txt文件后,可以使用下面的命令安装所有依赖:

pip install -r environment/requirements.txt
  • assets文件夹用来存放图片——比如我们的应用logo。这是可选的,你可以根据个人喜好添加自己喜欢的图片。
  • app文件夹里存放的是我们应用的所有代码。

接下来,我们看一看app文件夹中的每个 .py 文件,并详细讲解代码的内容。

components/llm.py

  • 这个文件中包含了一个 LangChain 聊天模型的实例,它负责连接我们的大型语言模型(LLM)。
  • 这里的对象就是 LangChain 的聊天模型。为了方便,本教程我用的是 AzureChatOpenAI,但你可以自由选择任何一种LangChain提供的聊天模型(你也可以参考 LangChain 的GitHub文档)。
  • 为了让 OpenAI 模型正常工作,你需要设置一些环境变量。只需要在项目的根目录下创建一个叫.env的文件。比如,我的 AzureOpenAI GPT-4 模型的

.env文件内容如下:

llm.py with ChatOpenAI model

from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import os

load_dotenv()

OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')

llm = ChatOpenAI(
    model="gpt-4o-2024-11-20",
    temperature=0,
    max_tokens=None,
    timeout=None,
    max_retries=2,
    api_key=OPENAI_API_KEY
)

components/prompts.py

  • 在这个 .py 文件中,我们将定义Agents的提示模板:
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

chat_prompt_template = ChatPromptTemplate.from_messages(
    [        ("system", "你是一个专门为生物医学领域设计的聊天机器人,利用 ChatGPT 技术与 PubMed 数据库结合,帮助用户高效检索研究摘要。"),        MessagesPlaceholder(variable_name="history"),        ("human", "{question}"),    ]
)
  • 提示模板决定了每次调用时,LLM 将接收到的格式。
  • 这里,我们有一个系统指令("你是一个专门为生物医学领域设计的聊天机器人,利用 ChatGPT 技术与 PubMed 数据库结合,帮助用户高效检索研究摘要。"),一个MessagesPlaceholder对象,它表示聊天历史记录,将会包含在提示中,以及引用用户提问("{question}")。
  • 注意:这只是本系列中最初的简单示例,接下来我们会创建一个更复杂的提示模板,包含从 RAG 检索到的片段。

components/chat_utils.py

  • 这个文件实现了Agents的核心功能。
  • ChatAgent类包含了设置链(聊天引擎)、管理聊天历史和在界面上显示消息所需的方法。
import streamlit as st
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.runnables.base import Runnable
from langchain_community.chat_message_histories import StreamlitChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate
import time


class ChatAgent:
    def __init__(self, prompt: ChatPromptTemplate, llm: Runnable):
        """
        初始化 ChatAgent。

        参数:
        - prompt (ChatPromptTemplate): 聊天提示模板。
        - llm (Runnable): 语言模型可运行对象。
        """
        self.history = StreamlitChatMessageHistory(key="chat_history")
        self.llm = llm
        self.prompt = prompt
        self.chain = self.setup_chain()

    def setup_chain(self) -> RunnableWithMessageHistory:
        """
        为 ChatAgent 设置链。

        返回:
        - RunnableWithMessageHistory: 配置有消息历史的链。
        """
        chain = self.prompt | self.llm
        return RunnableWithMessageHistory(
            chain,
            lambda session_id: self.history,
            input_messages_key="question",
            history_messages_key="history",
        )

    def display_messages(self):
        """
        在聊天界面显示消息。
        如果没有消息,添加一个默认的 AI 消息。
        """
        if len(self.history.messages) == 0:
            self.history.add_ai_message("我能帮您做些什么?")
        for msg in self.history.messages:
            st.chat_message(msg.type).write(msg.content)

    def start_conversation(self):
        """
        在聊天界面开始对话。
        显示消息,提示用户输入,并处理 AI 响应。
        """
        self.display_messages()
        user_question = st.chat_input(placeholder="有什么问题都可以问我哦!")
        if user_question:
            st.chat_message("human").write(user_question)
            config = {"configurable": {"session_id": "any"}}
            response_placeholder = st.empty()  # 创建一个空的 Streamlit 容器
            response = self.chain.invoke({"question": user_question}, config)

            # 一个字符一个字符地显示响应
            current_text = ""
            for char in response.content:
                current_text += char
                response_placeholder.text(current_text)
                time.sleep(0.05)  # 暂停0.05秒

app.py

  • 这是 Streamlit 应用的入口,也是唯一的页面。
  • 这段代码包含了 Streamlit 应用页面的布局,它实例化了 ChatAgent 类,并调用了该类的 start_conversation 方法:
import streamlit as st
from components.chat_utils import ChatAgent
from components.prompts import chat_prompt_template
from components.llm import llm


def main():
    # 设置页面配置
    st.set_page_config(
        page_title="PubMed 筛查器",  # 页面主题
        page_icon='../assets/favicon32-32.ico',  # 主题logo
        layout='wide'
    )

    # 定义两列 - 布局水平分割
    col1, col2 = st.columns([1, 3])

    # 在第一列放置logo
    with col1:
        st.image('../assets/m.png')

    # 在第二列,放置供用户可能会问的一些示例科学问题。
    with col2:
        st.title("PubMed 筛查器")
        st.markdown("""
                    PubMed 筛查器是一个结合 ChatGPT 和 PubMed 数据库的洞察工具,帮助你从生物医学的研究摘要中获取信息。
                    #### 你可以问这样的问题
                    - 如何使用先进的成像技术和检测标志物来早期诊断和监控脑部疾病的发展?
                    - 干细胞技术在治疗脑部疾病方面有哪些可能性,这些治疗方法面临哪些挑战?
                    - 肠道中的微生物如何影响糖尿病的发展,我们能通过调整这些微生物来帮助治疗糖尿病吗?
                    - 癌症治疗中常用的靶向药物为什么会失效,我们怎样才能克服这种抗药性?
                  """)

    # 聊天机器人组件
    chat_agent = ChatAgent(prompt=chat_prompt_template, llm=llm)
    chat_agent.start_conversation()


# 主程序
if __name__ == "__main__":

    main()
  • 这是应用的入口点,现在我们可以直接运行它,在浏览器里查看我们想要的效果!

运行应用

  • 打开**/app**文件夹,然后输入以下命令来启动应用:
streamlit run app.py

运行结果

接着,在浏览器中访问以下网址:http://localhost:8501/

你可以看到类似这样的界面,说明我们已经运行成功了

  • 正如你看到的,我们的聊天机器人应用现在已经能够正常运行了,但它目前只能根据LLM在训练时掌握的知识给出一些高层次、通用的回答。这对于医学研究来说并不理想,因为LLM可能会错过最新的研究成果,而且我们也无法确认回答中信息的来源,这增加了“幻觉”信息的风险。
  • 在本系列的后续部分,我们将通过从PubMed数据库中检索科学摘要,来为LLM的回答提供更可靠的依据,并教LLM在回答中提供相关来源的引用。

敬请关注后续内容!