0

0

构建Langserve动态RAG应用:实现运行时问题与语言输入

霞舞

霞舞

发布时间:2025-10-24 12:21:27

|

691人浏览过

|

来源于php中文网

原创

构建Langserve动态RAG应用:实现运行时问题与语言输入

本文详细介绍了如何在langserve中构建一个支持动态输入的问题回答(rag)应用。通过集成langchain的`runnable`接口,我们展示了如何将用户在运行时提供的查询问题和目标语言参数,动态地传递给检索器和语言模型,从而实现高度交互性和灵活性的rag服务部署。

引言

随着大型语言模型(LLM)应用的普及,检索增强生成(RAG)已成为一种强大的范式,它通过结合外部知识库来提升LLM的准确性和相关性。Langchain作为构建LLM应用的框架,提供了丰富的工具和抽象。而Langserve则进一步简化了Langchain应用的部署,使其能够轻松地通过HTTP API对外提供服务。

然而,在开发RAG应用时,一个常见的需求是允许用户在运行时动态地提供输入,例如查询问题和输出语言。原始的示例代码中,检索器的查询和提示词中的语言参数是硬编码的,这大大限制了应用的灵活性和交互性。本教程旨在解决这一问题,指导您如何在Langserve中构建一个能够接收动态输入的RAG应用。

核心挑战:静态输入与动态需求

在传统的Langchain链中,如果我们将查询问题直接写入retriever.get_relevant_documents("What does finance accounts contain?"),或者将语言参数固定在prompt = ChatPromptTemplate.from_template("...strictly in {lang}")中,那么每次需要改变这些参数时,都必须修改代码并重新部署。这对于一个需要与用户交互的应用来说是不可接受的。

我们的目标是:

  1. 允许用户通过Langserve API动态输入查询问题。
  2. 允许用户通过Langserve API动态指定生成答案的语言。
  3. 将这些动态输入无缝地集成到RAG链中,驱动检索器和LLM。

Langserve动态输入机制

Langserve应用基于FastAPI,其核心思想是将Langchain的Runnable对象封装成HTTP端点。Runnable对象可以定义其期望的输入类型。当Langserve接收到HTTP请求时,它会解析请求体中的数据,并将其作为输入传递给相应的Runnable链。

要实现动态输入,关键在于:

  1. 定义链的输入结构:明确链需要哪些参数(例如question和lang)。
  2. 在链中处理输入:使用Langchain的RunnablePassthrough、RunnableLambda等工具,从输入的字典中提取所需参数,并将其传递给链中的各个组件。
  3. 在add_routes中声明输入类型:通过input_type参数告知Langserve(和FastAPI)预期的输入数据模型。

构建动态RAG链

我们将逐步构建一个支持动态问题和语言输入的RAG链。

1. 准备必要的组件

首先,我们需要一个检索器(Retriever)和一个语言模型(LLM)。为了演示,我们将使用一些模拟数据和OpenAI模型。

import os
from fastapi import FastAPI
from langserve import add_routes
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from langchain_core.output_parsers import StrOutputParser
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import OpenAIEmbeddings
from langchain_core.documents import Document
from langchain_openai import ChatOpenAI
import uvicorn

# 设置OpenAI API密钥 (请替换为您的实际密钥或从环境变量加载)
# os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"

# 1. 模拟检索器设置
# 在实际应用中,您会从数据库或文件加载文档,并使用更复杂的嵌入模型和向量存储
docs_content = [
    "财务账户通常包含财务交易记录,包括资产、负债、权益、收入和支出。它们用于编制资产负债表和损益表等财务报表。",
    "资产负债表在特定时间点提供公司财务状况的快照,列出资产、负债和所有者权益。",
    "损益表,也称为利润和亏损表,总结了公司在一段时间内的收入、支出以及利润或亏损。",
    "财务报表对于投资者、债权人和管理层评估公司的业绩和财务状况至关重要。"
]
documents = [Document(page_content=d) for d in docs_content]
embeddings = OpenAIEmbeddings() # 使用OpenAI嵌入模型
vectorstore = FAISS.from_documents(documents, embeddings)
retriever = vectorstore.as_retriever()

# 2. LLM设置
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7) # 使用GPT-3.5 Turbo模型

2. 文档格式化函数

检索器返回的是Document对象列表,我们需要将其转换为字符串,以便注入到提示词中。

def format_docs(docs):
    """将检索到的文档列表格式化为单个字符串。"""
    return "\n\n".join(doc.page_content for doc in docs)

3. 构建动态RAG链

这是核心部分。我们将使用RunnablePassthrough和RunnableLambda来处理动态输入。链的输入将被假定为一个字典,包含"question"和"lang"键。

Mulan AI
Mulan AI

画布式AI视频创作平台,轻松制作爆款视频

下载
# 3. 构建RAG链,支持动态输入
# 链的输入预期是一个字典,例如:{"question": "...", "lang": "..."}
rag_chain = (
    # 步骤1: 从输入中提取问题,并并行地执行检索
    # RunnablePassthrough.assign 允许我们添加新的键到输入字典中,而不改变原始输入
    # 'context' 键的值将是检索器根据 'question' 检索到的文档
    RunnablePassthrough.assign(
        context=lambda x: retriever.invoke(x["question"]) | RunnableLambda(format_docs)
    )
    # 步骤2: 创建聊天提示模板
    # 模板将使用动态的 'context'、'question' 和 'lang'
    | ChatPromptTemplate.from_template(
        "根据以下上下文信息:\n{context}\n\n请回答问题: {question},并严格使用{lang}语言。"
    )
    # 步骤3: 调用语言模型生成答案
    | llm
    # 步骤4: 解析LLM的输出为字符串
    | StrOutputParser()
)

链的解释:

  • RunnablePassthrough.assign(...): 这是一个强大的工具,它允许我们向当前的输入字典中添加新的键值对,同时保留原始输入。
    • context=lambda x: retriever.invoke(x["question"]) | RunnableLambda(format_docs): 这部分是核心。
      • lambda x: ...: 定义了一个匿名函数,它接收整个输入字典x。
      • retriever.invoke(x["question"]): 从输入字典x中提取"question"键的值,并将其作为参数调用检索器。
      • | RunnableLambda(format_docs): 检索器返回Document对象列表后,通过管道将其传递给format_docs函数进行格式化。
      • 最终,格式化后的文档字符串将作为"context"键的值添加到链的输入中。
  • ChatPromptTemplate.from_template(...): 接收包含context、question和lang的字典,并构建最终的提示消息。
  • | llm: 将构建好的提示消息传递给LLM进行处理。
  • | StrOutputParser(): 将LLM的输出(通常是AIMessage对象)解析为纯文本字符串。

部署与测试

现在,我们将使用Langserve来部署这个动态RAG链。

# 4. Langserve应用设置
app = FastAPI(
    title="动态RAG应用",
    version="1.0",
    description="一个Langserve应用,支持动态问题和语言输入的RAG功能。"
)

# 5. 添加路由
# 注意:input_type 参数明确定义了链的预期输入结构
add_routes(
    app,
    rag_chain,
    path="/dynamic_rag",
    input_type={"question": str, "lang": str}, # 明确定义输入Schema
    output_type=str # 定义输出Schema
)

# 6. 运行Langserve应用
if __name__ == "__main__":
    uvicorn.run(app, host="localhost", port=8000)

运行应用:

  1. 确保您已安装所有必要的库:
    pip install langchain langchain-openai langserve uvicorn "fastapi[all]" python-dotenv faiss-cpu
  2. 将上述代码保存为 app.py。
  3. 运行:
    python app.py

测试应用:

打开浏览器访问 http://localhost:8000/dynamic_rag/playground/。您将看到Langserve Playground界面。

在左侧的“Input”区域,您会看到两个输入框:question (string) 和 lang (string)。

  • 在 question 框中输入:“财务账户包含哪些内容?”
  • 在 lang 框中输入:“中文”
  • 点击“Invoke”按钮。

您将看到LLM根据检索到的上下文,用中文回答了您的问题。您可以尝试不同的问题和语言,验证动态输入的效果。

注意事项与进阶

  1. 输入验证:在生产环境中,强烈建议对用户输入进行更严格的验证。您可以使用Pydantic模型定义更复杂的输入Schema,并在Langserve的input_type中使用它。
  2. 错误处理:在链中加入适当的错误处理逻辑,例如当检索器未能找到相关文档时,可以返回一个友好的提示。
  3. 可配置的替代方案 (configurable_alternatives):原始问题中提到了configurable_alternatives。这个功能主要用于在运行时根据配置选择不同的链组件,而不是简单地传递参数。例如,您可以配置一个链,使其在某些条件下使用OpenAIEmbeddings,而在另一些条件下使用HuggingFaceEmbeddings。对于本教程中仅仅是传递动态参数的需求,直接使用RunnablePassthrough等方法更为简洁和直接。如果您的应用需要根据用户输入或外部配置来切换不同的检索器、LLM或整个子链,那么configurable_alternatives将是更合适的选择。
  4. 安全性:如果您的应用涉及敏感数据或需要访问外部API(如OpenAI API),请务必妥善管理API密钥,避免硬编码,并考虑使用环境变量或密钥管理服务。
  5. 性能优化:对于高并发场景,可以考虑使用异步操作,并优化检索器和LLM的调用。

总结

通过本教程,我们成功地将一个静态的RAG应用改造为一个支持动态输入的Langserve服务。我们学习了如何利用Langchain的Runnable接口和Langserve的部署能力,使RAG应用能够接收用户在运行时提供的查询问题和目标语言。这种动态化的方法极大地增强了应用的灵活性、交互性和实用性,为构建更智能、更用户友好的LLM应用奠定了基础。

相关专题

更多
Python FastAPI异步API开发_Python怎么用FastAPI构建异步API
Python FastAPI异步API开发_Python怎么用FastAPI构建异步API

Python FastAPI 异步开发利用 async/await 关键字,通过定义异步视图函数、使用异步数据库库 (如 databases)、异步 HTTP 客户端 (如 httpx),并结合后台任务队列(如 Celery)和异步依赖项,实现高效的 I/O 密集型 API,显著提升吞吐量和响应速度,尤其适用于处理数据库查询、网络请求等耗时操作,无需阻塞主线程。

27

2025.12.22

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

381

2023.08.02

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

278

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1494

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

622

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

572

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

586

2024.04.29

c++ 根号
c++ 根号

本专题整合了c++根号相关教程,阅读专题下面的文章了解更多详细内容。

45

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 20.2万人学习

Django 教程
Django 教程

共28课时 | 3.5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号