《AI Agent智能体开发实践+玩转FastGPT 像搭积木一样构建智能体 LLM大语言模型AI Agent开发 智能体性能优化调试部署实施方法书籍 AIAgent智能体开发实践 无规格》【摘要 书评 试读】- 京东图书
本章将基于LangChain框架和检索增强生成(RAG)技术实现一个问答智能体,这个智能体能够根据RAG知识准确回答用户的问题。该项目支持单轮问答和多轮对话两种模式,采用RAG技术,通过向量数据库实现对企业FAQ知识的高效检索,并结合大语言模型生成准确回答。
该项目基于LangChain框架和阿里云DashScope服务,构建了一个支持单轮问答与多轮对话的企业FAQ智能问答系统,整体框架可分为6个核心层级,形成“输入−处理−输出”的完整闭环,如图10.1所示。
图10.1 整体框架可分为6个核心层级
1. 数据源层
- 数据形式:采用结构化FAQ数据(问题−答案键值对),包含工作时间、年假申请等企业常见问题。
- 数据转换:将原始FAQ转换为LangChain的Document格式,包含page_content(问题+答案)和metadata(来源标识)。
- 设计特点:数据结构简单清晰,便于扩展至更大规模的企业知识库。
2. 数据存储层
- 核心组件:使用FAISS向量数据库存储文本嵌入向量。
- 工作原理:通过文本分割器(RecursiveCharacterTextSplitter)将文档分块后,利用嵌入模型转换为向量并存储。
- 检索配置:设置k=2表示每次检索返回最相关的2条文档。
- 设计优势:FAISS为轻量级本地向量库,适合小规模知识库场景,部署成本低。
3. 模型层
- 嵌入模型:自定义DashScopeEmbeddings,基于阿里云text-embedding-v2模型实现文本向量化。
- 提供embed_documents(批量文档嵌入)和embed_query(查询嵌入)方法。
- LLM模型:自定义DashScopeLLM,封装阿里云qwen-plus大语言模型。
- 支持温度参数(temperature=0.3)控制输出随机性。
- 限制最大生成tokens(max_tokens=512)控制回答长度。
- 设计亮点:通过统一接口封装第三方模型,便于替换为其他模型服务。
4. 链管理层
1)单轮问答链
- 由create_retrieval_chain构建,结合检索器和文档组合链。
- 提示模板限制仅使用上下文信息,避免编造内容。
2)多轮对话链
- 增加history_aware_retriever实现对话历史感知。
- 通过contextualize_q_prompt将当前问题结合历史对话重述为独立问题。
- 设计逻辑:基于LangChain的链模式,实现检索增强生成(RAG)的核心流程。
5. 业务逻辑层
1)单轮问答函数(single_question)
- 输入:用户问题。
- 输出:回答内容+参考来源。
- 功能:独立处理单个问题,返回引用的FAQ来源。
2)多轮对话函数(chat_conversation)
- 输入:当前问题+对话历史+记忆状态。
- 输出:更新后的对话历史+清空的输入框+记忆状态。
- 功能:维护对话上下文,支持连续交互。
- 设计特点:分离单轮与多轮处理逻辑,满足不同使用场景。
6. 用户交互层
- 界面框架:使用Gradio构建Web界面,包含两个标签页。
- 单轮问答:文本输入框+回答展示+来源展示。
- 多轮对话:聊天机器人组件+输入框+控制按钮。
- 交互设计:支持按钮提交和回车提交,提供对话清空功能。
- 设计优势:零代码部署Web界面,降低使用门槛,适合演示和快速迭代。
1. 基础配置与依赖处理
#从正确安装的pydantic中导入BaseModel
from pydantic import BaseModel
#全局配置允许任意类型
class CustomBaseModel(BaseModel):
class Config:
arbitrary_types_allowed = True
#替换LangChain冲突模块
import langchain.memory.summary
langchain.memory.summary.BaseModel = CustomBaseModel
#其他模块替换
上面代码主要解决LangChain与Pydantic之间的兼容性问题,通过自定义BaseModel并替换 LangChain相关模块中的基础模型类,避免因类型检查严格导致的冲突。
2. 阿里云DashScope API配置
DASHSCOPE_API_KEY = "your-dashscope-api-key-here" #替换为实际API密钥
QWEN_VL_ENDPOINT = "https://dashscope.aliyuncs.com/api/v1/services/aigc/ multimodal-generation/generation"
上面代码配置阿里云DashScope服务的API密钥和端点,这是调用阿里云大模型服务的基础。
3. 自定义嵌入模型
class DashScopeEmbeddings(Embeddings):
def__init__(self, model_name: str = "text-embedding-v2"):
self.model_name = model_name
def embed_documents(self, texts: List[str]) -> List[List[float]]:
#实现文档嵌入逻辑
...
def embed_query(self, text: str) -> List[float]:
#实现查询嵌入逻辑
...
上面代码实现了基于阿里云DashScope的文本嵌入模型,将文本转换为向量表示,用于后续的相似度检索。其包含以下两个核心方法。
- embed_documents:批量处理文档并生成嵌入向量。
- embed_query:处理用户查询并生成嵌入向量。
4. 自定义LLM模型
class DashScopeLLM(LLM, CustomBaseModel):
model_name: str = "qwen-plus"
temperature: float = 0.3
max_tokens: int = 512
@property
def_llm_type(self) -> str:
return "dashscope"
def_call(self, prompt: str, stop: Optional[List[str]] = None, **kwargs: Any) -> str:
#实现调用阿里云大模型的逻辑
...
上面代码封装了阿里云DashScope的大语言模型调用,实现了LangChain的LLM接口,使得阿里云的模型可以无缝集成到LangChain的工作流中。
5. 系统初始化组件
def initialize_system():
#环境配置、数据准备、组件初始化
...
这是系统的核心初始化函数,主要完成:
- 环境变量配置。
- FAQ数据准备与转换为Document格式。
- 文本分割处理。
- 嵌入模型和向量数据库(FAISS)初始化。
- 检索器(retriever)创建。
- LLM模型初始化。
- 单轮和多轮对话链的创建。
6. 交互功能实现
- 单轮问答函数single_question(question):处理独立的用户问题,返回回答和参考来源。
- 多轮对话函数chat_conversation(question, chat_history, memory):处理上下文相关的对话,维护对话历史,支持连续交互。
7. 前端界面
with gr.Blocks(title="RAG问答系统") as demo:
#界面组件定义与布局
...
使用Gradio创建了友好的Web交互界面,包含两个标签页:
- 单轮问答:适合独立问题查询。
- 多轮对话:支持上下文关联的连续对话。
本章到了这里,我们实现了基于LangChain开发问答智能体的完整功能。通过LangChain框架,我们可以快速构建基于特定知识库的问答系统,并根据需要进行优化和扩展。
【示例10.1】基于LangChain的完整示例代码main.py,实现一个RAG问答智能体,支持FAQ数据、向量化、检索、生成回答,并包含多轮对话记忆和基础部署建议(LangChain+Gradio+Qwen)。 # -*- coding: utf-8 -*- import os import dashscope import gradio as gr # 从正确安装的pydantic中导入BaseModel from pydantic import BaseModel # 全局配置允许任意类型 class CustomBaseModel(BaseModel): class Config: arbitrary_types_allowed = True # 替换LangChain冲突模块 import langchain.memory.summary langchain.memory.summary.BaseModel = CustomBaseModel import langchain.chains.combine_documents.base langchain.chains.combine_documents.base.BaseModel = CustomBaseModel import langchain_core.language_models.base langchain_core.language_models.base.BaseModel = CustomBaseModel import langchain.prompts.prompt langchain.prompts.prompt.BaseModel = CustomBaseModel # 导入LangChain组件(已包含社区版依赖) from langchain.vectorstores import FAISS from langchain.chains import create_retrieval_chain from langchain.chains.combine_documents import create_stuff_documents_chain from langchain.chains.history_aware_retriever import create_history_aware_retriever from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain.schema import Document, AIMessage, HumanMessage from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.memory import SimpleMemory from langchain.llms.base import LLM from typing import Optional, List, Mapping, Any from langchain.embeddings.base import Embeddings # ----------------------------- # 1. 阿里云 DashScope API 配置 # ----------------------------- DASHSCOPE_API_KEY = "your-dashscope-api-key-here" # 替换为实际API密钥 QWEN_VL_ENDPOINT = "https://dashscope.aliyuncs.com/api/v1/services/aigc/ multimodal-generation/generation" # -------------------------- # 2. 自定义阿里云DashScope嵌入模型 # -------------------------- class DashScopeEmbeddings(Embeddings): def __init__(self, model_name: str = "text-embedding-v2"): self.model_name = model_name def embed_documents(self, texts: List[str]) -> List[List[float]]: dashscope.api_key = DASHSCOPE_API_KEY response = dashscope.TextEmbedding.call( model=self.model_name, input=texts ) if hasattr(response, 'output') and 'embeddings' in response.output: return [item["embedding"] for item in response.output["embeddings"]] else: raise Exception(f"嵌入模型调用失败: {getattr(response, 'message', '未知错误')}") def embed_query(self, text: str) -> List[float]: return self.embed_documents([text])[0] # -------------------------- # 3. 自定义阿里云DashScope LLM # -------------------------- class DashScopeLLM(LLM, CustomBaseModel): model_name: str = "qwen-plus" temperature: float = 0.3 max_tokens: int = 512 @property def _llm_type(self) -> str: return "dashscope" def _call( self, prompt: str, stop: Optional[List[str]] = None,** kwargs: Any, ) -> str: if stop is not None: raise ValueError("不支持stop参数") dashscope.api_key = DASHSCOPE_API_KEY response = dashscope.Generation.call( model=self.model_name, prompt=prompt, temperature=self.temperature, max_tokens=self.max_tokens, endpoint=QWEN_VL_ENDPOINT, **kwargs ) if hasattr(response, 'output') and hasattr(response.output, 'text'): return response.output.text else: raise Exception(f"LLM调用失败: {getattr(response, 'message', '未知错误')}") @property def _identifying_params(self) -> Mapping[str, Any]: return { "model_name": self.model_name, "temperature": self.temperature, "max_tokens": self.max_tokens, "endpoint": QWEN_VL_ENDPOINT } # -------------------------- # 4. 初始化系统组件 # -------------------------- def initialize_system(): os.environ["DASHSCOPE_API_KEY"] = DASHSCOPE_API_KEY # 准备FAQ数据 faq_data = [ {"question": "公司的工作时间是什么?", "answer": "工作日为周一至周五,上午9:00到下午6:00。"}, {"question": "如何申请年假?", "answer": "通过HR系统提交休假申请,主管审批后生效。"}, {"question": "有没有远程办公政策?", "answer": "支持混合办公模式,每周可在家工作最多两天。"}, {"question": "加班有补贴吗?", "answer": "是的,超过晚上8点的加班可申请调休或加班费。"} ] # 转换为Document格式 documents = [] for item in faq_data: content = f"问题: {item['question']}\n答案: {item['answer']}" documents.append(Document(page_content=content, metadata={"source": "faq"})) # 文本分割 text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50) split_docs = text_splitter.split_documents(documents) # 初始化嵌入模型和向量库 embeddings = DashScopeEmbeddings(model_name="text-embedding-v2") db = FAISS.from_documents(split_docs, embeddings) retriever = db.as_retriever(search_kwargs={"k": 2}) # 初始化LLM llm = DashScopeLLM( model_name="qwen-plus", temperature=0.3, max_tokens=512 ) # 定义单轮问答提示模板 qa_prompt = ChatPromptTemplate.from_template("""严格使用以下上下文回答问题,不要编造信息。如果上下文没有相关内容,直接说“不知道”。 上下文: {context} 问题: {input} 回答:""") # 创建单轮问答链 combine_docs_chain = create_stuff_documents_chain(llm, qa_prompt) qa_chain = create_retrieval_chain(retriever, combine_docs_chain) # 定义多轮对话提示模板 contextualize_q_prompt = ChatPromptTemplate.from_messages([ MessagesPlaceholder(variable_name="chat_history"), ("human", "{input}"), ("human", "基于上述对话,将问题重述为一个独立的问题,不需要回答。") ]) # 创建历史感知的检索器 history_aware_retriever = create_history_aware_retriever( llm, retriever, contextualize_q_prompt ) # 创建多轮对话链 chat_chain = create_retrieval_chain(history_aware_retriever, combine_docs_chain) # 初始化多轮对话记忆 memory = SimpleMemory(memories={}) return qa_chain, chat_chain, memory # -------------------------- # 5. 定义交互函数 # -------------------------- def single_question(question): if DASHSCOPE_API_KEY == "your-dashscope-api-key-here": return "请先替换代码中的阿里云DashScope API密钥", "" try: qa_chain, _, _ = initialize_system() result = qa_chain.invoke({"input": question}) # 提取参考来源 sources = [] if result.get("context"): sources = [f"[{i+1}] {doc.page_content.split('答案: ')[-1]}" for i, doc in enumerate(result["context"])] return result["answer"], "\n".join(sources) except Exception as e: return f"错误: {str(e)}", "" def chat_conversation(question, chat_history, memory): if DASHSCOPE_API_KEY == "your-dashscope-api-key-here": chat_history.append((question, "请先替换代码中的阿里云DashScope API密钥")) return chat_history, "", memory try: _, chat_chain, memory = initialize_system() # 准备对话历史 messages = [] for human_msg, ai_msg in chat_history: messages.append(HumanMessage(content=human_msg)) messages.append(AIMessage(content=ai_msg)) # 调用对话链 result = chat_chain.invoke({ "input": question, "chat_history": messages }) # 更新对话历史 chat_history.append((question, result["answer"])) return chat_history, "", memory except Exception as e: chat_history.append((question, f"错误: {str(e)}")) return chat_history, "", memory # -------------------------- # 6. 创建Gradio界面 # -------------------------- with gr.Blocks(title="RAG问答系统") as demo: gr.Markdown("# 企业FAQ智能问答系统") gr.Markdown("基于LangChain和阿里云DashScope的RAG问答演示") memory_state = gr.State(None) # 用于存储对话记忆 with gr.Tabs(): with gr.Tab("单轮问答"): question_single = gr.Textbox(label="请输入问题") output_single = gr.Textbox(label="回答") sources_single = gr.Textbox(label="参考来源") btn_single = gr.Button("提交") btn_single.click( single_question, inputs=[question_single], outputs=[output_single, sources_single] ) with gr.Tab("多轮对话"): chatbot = gr.Chatbot(label="对话历史") question_chat = gr.Textbox(label="请输入问题") btn_chat = gr.Button("发送") btn_clear = gr.Button("清空对话") btn_chat.click( chat_conversation, inputs=[question_chat, chatbot, memory_state], outputs=[chatbot, question_chat, memory_state] ) btn_clear.click(lambda: (None, "", None), None, [chatbot, question_chat, memory_state]) question_chat.submit( chat_conversation, inputs=[question_chat, chatbot, memory_state], outputs=[chatbot, question_chat, memory_state] ) gr.Markdown(""" ### 使用说明 1. 请先在代码中替换您的阿里云DashScope API密钥 2. 单轮问答:适合独立的问题查询 3. 多轮对话:支持上下文关联的连续对话 """) if __name__ == "__main__": demo.launch()运行代码,控制台窗口中输出如下(请读者自行测试):
Running on local URL: http://127.0.0.1:7862