企业级大模型应用实战:基于LangChain构建RAG与Agent系统
最近在帮团队落地大模型应用时深刻体会到从零到一构建一个稳定、可用的企业级智能系统有多“酸爽”。网上资料要么是零散的API调用要么是过于学术化的概念讲解真正能把LangChain、RAG、Agent这几个核心模块串起来讲清楚从环境搭建、数据处理到生产部署全流程的实战教程少之又少。很多开发者卡在工具调用、知识库构建和智能体逻辑编排这些关键环节导致项目迟迟无法上线。本文旨在解决这个痛点。我将结合多个真实项目经验为你拆解一套完整的、可落地的企业级大模型应用开发方案。从LangChain基础环境配置开始手把手带你构建一个功能完备的RAG知识库问答系统并在此基础上进阶开发一个能自主调用工具、完成复杂任务的智能体Agent。全程附带可运行的代码、避坑指南和性能优化建议确保你学完就能动手掌握企业级项目开发的核心技能。1. 背景与核心概念为什么需要LangChain、RAG与Agent在深入代码之前我们必须先理清这几个核心概念以及它们要解决的问题。这能帮助你在后续开发中做出正确的技术选型和架构设计。1.1 大模型应用的挑战与LangChain的定位大语言模型LLM能力强大但直接将其集成到业务系统中会面临诸多挑战上下文长度限制模型无法记住和处理超长的对话或文档。知识滞后性模型的训练数据有截止日期无法获取最新、最专业的领域知识。“幻觉”问题模型可能会生成看似合理但完全错误的信息。缺乏行动能力模型本身无法执行查询数据库、调用API等具体操作。LangChain应运而生它不是一个新模型而是一个用于开发由大语言模型驱动的应用程序的框架。它的核心价值在于提供了模块化的组件和链Chain让你能像搭积木一样将LLM与外部数据源、工具、记忆系统等连接起来构建出功能复杂的应用。简单说LangChain是大模型应用的“粘合剂”和“脚手架”。1.2 RAG为模型注入专业知识和最新信息RAGRetrieval-Augmented Generation检索增强生成是解决模型知识滞后和幻觉问题的关键技术。其核心思想不是让模型“记住”所有知识而是让模型学会“查阅资料”。RAG系统的工作流程通常分为三步索引Index将你的私有文档如产品手册、公司制度、技术文档进行切分、向量化并存入向量数据库。检索Retrieve当用户提问时系统将问题也转化为向量并在向量数据库中搜索与之最相关的文档片段。生成Generate将检索到的相关片段作为上下文和用户问题一起提交给LLM让LLM基于这些可靠的上下文生成答案。这样答案的准确性不再完全依赖于模型的原始知识而是依赖于你提供的可靠文档极大地提升了专业领域问答的可靠性。1.3 Agent让模型拥有思考和行动的能力如果说RAG扩展了模型的“知识”那么Agent智能体则扩展了模型的“能力”。一个基础的LLM只能进行文本对话。而一个Agent是一个具备自主性的系统它以大语言模型为“大脑”可以根据目标、结合上下文记忆、自主地规划、调用工具、执行动作并观察结果最终完成复杂任务。例如一个数据分析Agent的任务可能是“分析上周的销售情况并生成报告”。它会自主规划先调用工具A查询数据库获取销售数据再调用工具B进行数据可视化最后用LLM总结成报告。LangChain提供了Agent、Tool、AgentExecutor等核心组件来构建这样的智能体。三者关系总结LangChain是构建应用的框架RAG是基于LangChain的一种用于增强知识的具体模式Agent是基于LangChain的一种用于赋予行动能力的具体模式。在实际项目中RAG和Agent常常结合使用例如一个客服Agent可以先通过RAG检索知识库再调用工单系统创建任务。2. 环境准备与版本说明本教程将构建一个完整的项目建议使用Python 3.9及以上版本。我们将使用LangChain的主流社区版本并搭配Chroma作为向量数据库轻量易用以及OpenAI的GPT模型作为LLM核心也可替换为本地模型如Ollama。2.1 创建项目与虚拟环境首先创建一个干净的项目目录并设置独立的Python环境这是管理依赖的最佳实践。# 创建项目目录 mkdir enterprise-llm-app cd enterprise-llm-app # 创建并激活虚拟环境 (Linux/macOS) python -m venv venv source venv/bin/activate # 创建并激活虚拟环境 (Windows) python -m venv venv venv\Scripts\activate2.2 安装核心依赖创建requirements.txt文件并安装以下依赖。请注意LangChain社区活跃版本更新较快以下版本为当前稳定版本未来如有变化请以官方文档为准。# requirements.txt langchain0.1.0 langchain-community0.0.10 # 包含社区维护的集成工具 langchain-openai0.0.5 # OpenAI集成 langchain-chroma0.1.0 # Chroma向量数据库集成 chromadb0.4.22 # Chroma客户端 openai1.6.1 # OpenAI官方SDK tiktoken0.5.2 # 用于Token计数 python-dotenv1.0.0 # 管理环境变量 pypdf3.17.0 # 用于读取PDF文档 unstructured0.12.2 # 用于解析多种格式文档使用pip安装pip install -r requirements.txt2.3 配置API密钥与环境变量为了安全地管理像OpenAI API Key这样的敏感信息我们使用.env文件。在项目根目录创建.env文件。从OpenAI官网获取你的API密钥填入该文件。# .env OPENAI_API_KEY你的-openai-api-key-here # 后续如需使用其他服务如SerpAPI谷歌搜索也可在此添加 # SERPAPI_API_KEYyour-serpapi-key重要务必在.gitignore文件中添加.env避免将密钥提交到代码仓库。3. 核心模块拆解LangChain基础组件在开始实战前快速理解几个最核心的LangChain组件它们是你构建应用的基石。3.1 模型I/O (LLMs Chat Models)这是与LLM交互的入口。LLM组件用于文本补全式调用而ChatModel用于更结构化的对话式调用推荐。# 文件demo_basic_llm.py from langchain_openai import ChatOpenAI from langchain.schema import HumanMessage, SystemMessage from dotenv import load_dotenv load_dotenv() # 加载.env文件中的环境变量 # 初始化Chat模型指定模型名称和温度创造性 llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) # 简单调用 response llm.invoke(什么是LangChain) print(response.content) # 结构化消息调用更接近真实对话场景 messages [ SystemMessage(content你是一个专业的Python工程师。), HumanMessage(content请用Python写一个快速排序函数。) ] response llm.invoke(messages) print(response.content)3.2 提示模板 (Prompt Templates)为了避免在代码中硬编码提示词使用PromptTemplate来抽象和复用提示逻辑。# 文件demo_prompt_template.py from langchain.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI prompt_template ChatPromptTemplate.from_messages([ (system, 你是一位{style}风格的技术作家。), (user, 请为{technology}写一段简介。) ]) # 填充模板生成最终的提示消息列表 formatted_messages prompt_template.format_messages( style幽默风趣, technologyRAG ) print(formatted_messages) llm ChatOpenAI(modelgpt-3.5-turbo) response llm.invoke(formatted_messages) print(response.content)3.3 链 (Chains)Chain是将多个组件模型、提示、工具等按顺序组合起来的工作流。LCEL(LangChain Expression Language) 是推荐的新语法用管道符|连接组件非常直观。# 文件demo_chain_lcel.py from langchain.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain.schema.output_parser import StrOutputParser # 定义提示模板 prompt ChatPromptTemplate.from_template(用不超过一句话解释{concept}) # 定义模型 llm ChatOpenAI(modelgpt-3.5-turbo) # 定义输出解析器将模型输出解析为字符串 output_parser StrOutputParser() # 使用 LCEL 创建链 prompt - llm - output_parser chain prompt | llm | output_parser # 运行链 result chain.invoke({concept: 向量数据库}) print(result) # 输出向量数据库是一种专门用于高效存储和检索高维向量数据如图像、文本嵌入的数据库系统。3.4 检索器 (Retrievers) 与向量存储 (Vectorstores)这是RAG的核心。Vectorstore负责存储和检索向量化的文档片段。# 这是一个概念示例具体创建过程在下一章实战中体现 from langchain_chroma import Chroma from langchain_openai import OpenAIEmbeddings from langchain.schema import Document # 1. 准备文档 docs [ Document(page_contentLangChain是一个用于开发大语言模型应用的框架。), Document(page_contentRAG通过检索外部知识来增强大模型生成答案的准确性。), ] # 2. 创建嵌入模型用于将文本转为向量 embeddings OpenAIEmbeddings(modeltext-embedding-3-small) # 3. 将文档存入向量数据库Chroma并自动创建检索器 vectorstore Chroma.from_documents(documentsdocs, embeddingembeddings, persist_directory./chroma_db) # 4. 获取检索器 retriever vectorstore.as_retriever(search_kwargs{k: 1}) # 检索最相关的1个片段 # 5. 进行检索 relevant_docs retriever.invoke(什么是LangChain) print(relevant_docs[0].page_content)4. 完整实战一构建企业级RAG知识库问答系统现在我们将综合运用上述组件构建一个完整的RAG系统。假设我们有一些公司内部的产品PDF文档需要构建一个能回答相关问题的智能助手。4.1 项目结构准备创建如下项目结构enterprise-llm-app/ ├── .env ├── requirements.txt ├── app.py # 主应用入口 ├── core/ │ ├── __init__.py │ ├── knowledge_base.py # 知识库构建与加载模块 │ └── qa_chain.py # 问答链构建模块 ├── data/ # 存放原始文档如PDF │ └── product_manual.pdf └── chroma_db/ # 向量数据库持久化目录自动生成4.2 知识库构建模块core/knowledge_base.py负责文档加载、切分、向量化并持久化存储。# 文件core/knowledge_base.py import os from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_chroma import Chroma from dotenv import load_dotenv load_dotenv() class KnowledgeBase: def __init__(self, persist_directory: str ./chroma_db): 初始化知识库设置向量数据库路径和嵌入模型。 self.persist_directory persist_directory self.embeddings OpenAIEmbeddings(modeltext-embedding-3-small) # 如果已有持久化数据则直接加载否则创建一个空的 if os.path.exists(self.persist_directory): self.vectorstore Chroma( persist_directoryself.persist_directory, embedding_functionself.embeddings ) print(f已加载现有知识库来自 {self.persist_directory}) else: self.vectorstore None print(未找到现有知识库请先执行 build 方法构建。) def build(self, data_dir: str): 从指定目录构建知识库。 参数: data_dir: 存放文档如PDF的目录路径。 documents [] # 1. 加载文档遍历目录加载所有PDF文件 for filename in os.listdir(data_dir): if filename.endswith(.pdf): file_path os.path.join(data_dir, filename) print(f正在加载文档: {file_path}) loader PyPDFLoader(file_path) docs loader.load() # 加载出的是一组Document对象 documents.extend(docs) if not documents: print(未在指定目录中找到任何PDF文档。) return # 2. 分割文本将长文档切分成适合检索的小片段 text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个片段大约500字符 chunk_overlap50, # 片段间重叠50字符保持上下文连贯 separators[\n\n, \n, 。, , , , , ] ) splits text_splitter.split_documents(documents) print(f文档已分割为 {len(splits)} 个文本片段。) # 3. 向量化并存储将文本片段转换为向量存入ChromaDB self.vectorstore Chroma.from_documents( documentssplits, embeddingself.embeddings, persist_directoryself.persist_directory ) print(f知识库构建完成已保存至 {self.persist_directory}) def get_retriever(self, search_typesimilarity, k4): 获取检索器。 参数: search_type: 检索类型如 similarity相似度, mmr最大边际相关性去重 k: 返回的最相关片段数量。 返回: 一个检索器对象。 if self.vectorstore is None: raise ValueError(知识库未初始化或未构建请先调用 build 方法。) return self.vectorstore.as_retriever(search_typesearch_type, search_kwargs{k: k}) if __name__ __main__: # 本地测试构建知识库 kb KnowledgeBase() kb.build(./data) # 假设你的PDF文档放在 ./data 目录下4.3 问答链构建模块core/qa_chain.py负责组合检索器和LLM形成完整的“检索-生成”流水线。# 文件core/qa_chain.py from langchain.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain.schema.runnable import RunnablePassthrough from langchain.schema.output_parser import StrOutputParser from dotenv import load_dotenv load_dotenv() def create_rag_chain(retriever): 创建一个RAG问答链。 参数: retriever: 知识库检索器。 返回: 一个可调用的RAG链。 # 定义提示模板其中 {context} 和 {question} 是占位符 template 你是一个专业的客服助手请严格根据以下上下文信息来回答问题。 如果上下文信息中没有相关答案请直接说“根据现有资料我无法回答这个问题”不要编造信息。 上下文信息 {context} 问题 {question} 请根据上下文提供准确、有用的答案 prompt ChatPromptTemplate.from_template(template) # 初始化LLM llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) # 使用LCEL定义RAG链 # 1. 将用户问题传入检索器获取上下文 # 2. 将上下文和原始问题格式化到提示词中 # 3. 发送给LLM # 4. 解析输出 rag_chain ( {context: retriever, question: RunnablePassthrough()} | prompt | llm | StrOutputParser() ) return rag_chain if __name__ __main__: # 本地测试需要先有构建好的知识库 from core.knowledge_base import KnowledgeBase kb KnowledgeBase() retriever kb.get_retriever(k2) chain create_rag_chain(retriever) # 提问测试 question 你们产品的主要优势是什么 answer chain.invoke(question) print(f问题{question}) print(f答案{answer})4.4 主应用入口app.py提供一个简单的命令行交互界面。# 文件app.py import sys sys.path.append(.) # 确保可以导入core模块 from core.knowledge_base import KnowledgeBase from core.qa_chain import create_rag_chain def main(): print( 企业级RAG知识库问答系统 ) print(1. 初始化/加载知识库...) kb KnowledgeBase() # 如果知识库不存在则构建首次运行 if kb.vectorstore is None: print(首次运行需要构建知识库。) data_path input(请输入存放文档的目录路径例如 ./data: ).strip() kb.build(data_path) else: print(知识库加载成功。) print(\n2. 准备问答链...) retriever kb.get_retriever(k4) # 检索4个最相关片段 qa_chain create_rag_chain(retriever) print(\n3. 系统就绪请输入您的问题输入 quit 或 exit 退出) print(- * 50) while True: question input(\n您的问题: ).strip() if question.lower() in [quit, exit]: print(再见) break if not question: continue try: answer qa_chain.invoke(question) print(f\n助手: {answer}) except Exception as e: print(f\n出错: {e}) if __name__ __main__: main()4.5 运行与验证将你的产品手册等PDF文档放入./data目录。在终端运行python app.py。首次运行会提示构建知识库输入./data。构建完成后即可开始提问。预期效果系统会从你提供的PDF中检索相关信息并生成基于上下文的准确回答对于文档未涉及的问题会明确表示无法回答有效缓解“幻觉”问题。5. 完整实战二开发能调用工具的智能体Agent在RAG的基础上我们进一步提升系统的能力创建一个能自主使用工具的智能体。例如一个可以查询天气、计算数学、搜索网络的助手。5.1 定义工具Tools工具是Agent能力的延伸。我们先定义几个简单的工具。# 文件core/agent_tools.py from langchain.tools import tool import requests import json from datetime import datetime tool def get_current_time(): 获取当前的日期和时间。 now datetime.now() return now.strftime(%Y-%m-%d %H:%M:%S) tool def calculator(expression: str) - str: 计算一个数学表达式的值。支持加减乘除-*/和括号。 参数: expression: 数学表达式例如 ‘(35)*2‘。 # 警告在生产环境中直接使用eval有安全风险此处仅作演示。 # 实际应用应使用安全的数学表达式解析库如 asteval。 try: result eval(expression) return f{expression} {result} except Exception as e: return f计算错误: {e} # 注意以下是一个需要API密钥的示例工具你需要先注册相关服务。 # tool # def search_web(query: str) - str: # 使用搜索引擎搜索网络信息。 # 参数: # query: 搜索关键词。 # # import os # from serpapi import GoogleSearch # params { # engine: google, # q: query, # api_key: os.getenv(SERPAPI_API_KEY) # 需要在.env中配置 # } # search GoogleSearch(params) # results search.get_dict() # # 简化返回第一个结果 # if organic_results in results and len(results[organic_results]) 0: # first_result results[organic_results][0] # return f{first_result.get(title)}: {first_result.get(link)}\n{first_result.get(snippet)} # return 未找到相关结果。5.2 创建智能体Agent我们将使用LangChain的ReAct框架来创建Agent它擅长推理和调用工具。# 文件core/agent.py from langchain_openai import ChatOpenAI from langchain.agents import create_react_agent, AgentExecutor from langchain.tools import Tool from langchain import hub # 用于拉取预定义的提示词 from core.agent_tools import get_current_time, calculator from core.qa_chain import create_rag_chain from core.knowledge_base import KnowledgeBase from dotenv import load_dotenv load_dotenv() def create_agent(): 创建并返回一个配置好的智能体执行器。 # 1. 初始化LLM llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) # 2. 准备工具列表 # 首先加载我们之前构建的RAG系统作为一个“知识库查询工具” kb KnowledgeBase() retriever kb.get_retriever(k2) rag_chain create_rag_chain(retriever) # 将RAG链包装成一个Tool对象 rag_tool Tool( nameKnowledge_Base, funcrag_chain.invoke, description当需要回答关于公司产品、政策或内部知识的问题时使用此工具。输入是一个具体的问题。 ) tools [ rag_tool, get_current_time, calculator, # 可以在此处添加更多工具如 search_web ] # 3. 拉取一个为ReAct Agent设计好的提示词模板 prompt hub.pull(hwchase17/react-chat) # 4. 创建ReAct Agent agent create_react_agent(llm, tools, prompt) # 5. 创建Agent执行器它负责运行Agent并处理工具调用循环 agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, # 设置为True可以看到Agent的思考过程便于调试 handle_parsing_errorsTrue, # 优雅地处理解析错误 max_iterations5, # 限制最大迭代次数防止死循环 early_stopping_methodgenerate # 当Agent认为任务完成时停止 ) return agent_executor if __name__ __main__: # 本地测试智能体 agent_executor create_agent() result agent_executor.invoke({ input: 先查一下现在几点然后计算一下(15-3)除以4等于多少最后问问知识库我们产品的主要优势是什么 }) print(\n最终结果:, result[output])5.3 运行智能体运行python core/agent.py你会看到类似以下的输出verbose模式开启 Entering new AgentExecutor chain... 我需要按顺序完成三个任务查时间、计算、询问知识库。 首先我需要获取当前时间。有一个工具叫get_current_time。 Action: get_current_time Action Input: Observation: 2024-05-27 10:30:15 Thought: 时间获取到了。接下来计算 (15-3)除以4。使用calculator工具。 Action: calculator Action Input: (15-3)/4 Observation: (15-3)/4 3.0 Thought: 计算完成。最后询问产品优势。使用Knowledge_Base工具。 Action: Knowledge_Base Action Input: 我们产品的主要优势是什么 Observation: 根据上下文信息我们产品的主要优势在于其高度的可集成性、卓越的性能和7x24小时的技术支持。 Thought: 我已经完成了所有三个任务。 Final Answer: 当前时间是2024-05-27 10:30:15。(15-3)除以4等于3.0。关于我们产品的主要优势根据知识库信息其优势在于高度的可集成性、卓越的性能和7x24小时的技术支持。 Finished chain. 最终结果: 当前时间是2024-05-27 10:30:15。(15-3)除以4等于3.0。关于我们产品的主要优势根据知识库信息其优势在于高度的可集成性、卓越的性能和7x24小时的技术支持。可以看到Agent成功规划了步骤并依次调用了三个不同的工具最终整合出了一个连贯的回答。6. 常见问题与排查思路在开发过程中你可能会遇到以下典型问题。问题现象可能原因排查思路与解决方案ModuleNotFoundError: No module named ‘langchain_xxx‘依赖未安装或版本不匹配。1. 检查requirements.txt中的包名是否正确。2. 运行pip list | grep langchain查看已安装版本。3. LangChain v0.1.x后很多集成移到了langchain-community确保已安装。OpenAI API调用报错认证失败、额度不足API密钥错误、未设置环境变量、账户余额不足。1. 检查.env文件中的OPENAI_API_KEY是否正确且已通过load_dotenv()加载。2. 登录OpenAI平台检查API密钥状态和余额。3. 尝试在代码中直接print(os.getenv(‘OPENAI_API_KEY‘))确认密钥已加载。向量数据库检索结果不相关文本分割策略不当、嵌入模型不匹配、检索参数k不合适。1. 调整RecursiveCharacterTextSplitter的chunk_size和chunk_overlap通常500-1000重叠50-100。2. 确保构建和检索时使用相同的嵌入模型。3. 尝试不同的检索类型如search_type“mmr“可以在相关性和多样性间取得平衡。4. 检查原始文档质量。Agent陷入循环或调用错误工具工具描述不清晰、LLM温度过高、提示词引导不足。1. 为每个工具编写清晰、具体的description说明其用途和输入格式。2. 将LLM的temperature设为0减少随机性。3. 在Agent的提示词中明确约束例如“你必须根据工具描述选择最合适的一个”。4. 设置max_iterations如5防止无限循环。处理长文档时程序内存占用高或速度慢一次性加载所有文档、嵌入计算耗时。1. 对于超大文档考虑流式加载或分批处理。2. 对于生产环境考虑使用更高效的嵌入模型如text-embedding-3-small或本地嵌入模型如sentence-transformers。3. 将向量数据库部署为独立服务如Chroma Server。‘Document‘ object has no attribute ‘page_content‘LangChain版本升级Document对象属性名变化。较新版本中Document的核心属性是page_content和metadata。如果出错检查你使用的Document类来源应来自from langchain.schema import Document。7. 最佳实践与工程建议将原型推进到生产环境需要关注以下方面7.1 性能优化嵌入模型选择OpenAI的嵌入模型效果好但需网络调用且有成本。对于内部应用可评估本地模型如all-MiniLM-L6-v2通过sentence-transformers库在精度和速度间取得平衡。检索优化混合搜索结合向量相似度搜索和关键词如BM25搜索提升召回率。重排序先用向量检索出大量候选如k20再用更精细的交叉编码器模型进行重排序返回Top-k显著提升精度。元数据过滤在存储文档时添加元数据如文档类型、部门、日期检索时进行过滤缩小搜索范围。缓存对频繁相同的查询结果进行缓存例如使用langchain.cache配合SQLiteCache或RedisCache大幅降低LLM调用成本和延迟。7.2 可观测性与评估记录日志详细记录用户的提问、检索到的文档片段、LLM的回复、工具调用记录。这对调试和后续分析至关重要。构建评估集准备一组标准问题及其预期答案定期运行测试监控RAG系统答案质量的变化如使用RAGAS等评估框架。监控关键指标检索延迟、LLM调用耗时与费用、用户反馈如点赞/点踩。7.3 安全与合规输入输出检查对用户输入进行清洗防止提示词注入攻击。对模型输出进行内容安全过滤。权限控制知识库文档应有访问权限控制。在检索前根据用户身份过滤其可访问的文档元数据。数据隐私如果使用云端LLM API确认你的业务数据特别是通过提示词发送的是否符合公司的数据出境和安全政策。必要时使用本地化模型。7.4 架构演进从单体到服务化当系统变复杂可将RAG检索服务、Agent推理服务、工具服务等拆分为独立的微服务。引入编排引擎对于非常复杂的Agent工作流涉及多步骤、条件分支、循环可以考虑使用LangGraphLangChain的另一库来以图的形式定义和控制工作流这比基础的AgentExecutor更强大、更可控。结合业务系统将Agent的工具与内部业务系统CRM、ERP、工单系统的API对接使其能真正驱动业务流程。通过本教程你不仅学会了如何使用LangChain搭建RAG和Agent更重要的是掌握了构建企业级大模型应用的核心方法论和工程化思维。从环境搭建、核心概念理解、模块化开发到生产级优化这套流程可以应用到大多数类似的智能系统开发中。真正的掌握源于实践。建议你以本项目为蓝本尝试接入真实的公司文档定义几个有业务价值的工具如查询数据库、发送邮件构建一个能解决实际问题的智能助手。在这个过程中你会更深入地理解各个组件的细节和调优方法。