从零构建AI Agent:基于LangChain的智能体开发实战指南
1. 项目概述:为什么是LangChain和AI Agent?
如果你最近在关注AI应用开发,那么“AI Agent”这个词一定高频出现在你的视野里。它不再是实验室里的概念,而是正在成为构建下一代智能应用的核心范式。简单来说,一个AI Agent(智能体)就是一个能感知环境、自主决策并执行任务来达成目标的程序。它不只是被动地回答你的问题,而是能像一个虚拟员工一样,主动去调用工具、处理信息、完成一系列复杂操作。
那么,为什么我们要用LangChain来打造它?直接调用大模型API不行吗?这里的关键在于“工程化”。当你只是让模型写首诗或总结文章时,直接调用API确实够了。但当你需要模型记住对话历史、去搜索引擎查资料、再根据结果写一份报告,最后把报告发邮件给你时,事情就变得复杂了。你需要管理对话状态(Memory)、串联不同的工具(Tools)、控制执行流程(Orchestration),并处理可能出现的错误。这些正是LangChain所擅长的——它提供了一套标准化的“积木”(组件)和“图纸”(链、代理),让你能像搭乐高一样,高效、可靠地组装出功能强大的AI应用。
所以,这篇指南的目标很明确:我们不空谈概念,而是手把手带你从零开始,用LangChain构建一个能真正干活的AI Agent。你会看到完整的代码、理解每一步的设计逻辑,并最终获得一个可以扩展和部署的原型。无论你是想为自己的产品添加智能,还是想深入理解AI Agent的开发,这篇实践指南都将为你铺平道路。
2. 核心概念与工具选型:LangChain生态一览
在动手写代码之前,我们需要先理清工具箱。LangChain生态目前有几个核心部分,了解它们能帮你做出更合适的技术选型。
2.1 LangChain Core vs. LangChain vs. LangGraph
这是最容易混淆的地方,也是新手入门的第一道坎。
- LangChain Core :这是基石。它定义了最核心的抽象接口,比如
Runnable(可运行对象)、Messages(消息)、Tools(工具)等。它非常轻量,不依赖具体的大模型服务。当你需要极高的定制化,或者想基于底层接口构建自己的框架时,可以直接使用它。 - LangChain (LangChain Package) :我们通常所说的LangChain,指的是这个主包。它在Core之上,集成了对上百种大模型(OpenAI, Anthropic, 本地模型等)、向量数据库、文档加载器、工具等的现成支持。它提供了高级的链(Chains)和代理(Agents)实现,是快速开发应用的首选。 我们本指南将主要使用它。
- LangGraph :这是为构建复杂、有状态的AI Agent工作流而生的。如果说LangChain的Agent是“单步决策-执行”,那么LangGraph允许你定义包含循环、分支、并行节点的 图 。它非常适合需要多轮规划、回溯、或涉及多个异构子任务的应用。对于入门第一个Agent,我们暂不需要它,但了解其定位很重要。
简单类比:LangChain Core是汽车发动机和底盘的设计图;LangChain Package是一辆已经组装好、加满油、能直接上路的家用轿车;而LangGraph则是用于构建复杂赛车或特种车辆(如多步骤审批流、游戏NPC)的专用改装车间。
2.2 AI Agent的核心组件
一个典型的LangChain Agent由以下几个关键部分组成,理解它们就理解了Agent的骨架:
- LLM(大语言模型) :Agent的“大脑”。负责理解指令、进行推理和决策。我们将使用OpenAI的GPT模型作为示例。
- Tools(工具) :Agent的“手和脚”。LLM本身无法直接操作外部世界,Tools赋予了它这种能力。一个Tool本质上是一个函数,LLM可以决定在何时调用它。常见的工具有:网页搜索、计算器、数据库查询、API调用等。
- Agent(代理逻辑) :连接大脑和手脚的“神经系统”。它定义了LLM如何根据当前对话和可用工具,决定下一步是“思考”还是“使用某个工具”。LangChain内置了多种Agent类型,如
ReAct、OpenAI Tools等。 - Memory(记忆) :Agent的“短期记忆”。用于存储和回顾之前的对话历史,让Agent具备上下文感知能力。可以是简单的对话缓冲区,也可以是更复杂的向量存储记忆。
- AgentExecutor :Agent的“执行引擎”。它负责运行整个循环:将用户输入、记忆和工具描述喂给Agent(LLM),解析LLM的输出(是最终答案还是工具调用),执行工具,将结果返回给LLM进行下一轮思考,直到Agent给出最终答案。
我们的实践将围绕这五个部分展开。
3. 环境搭建与基础配置
工欲善其事,必先利其器。我们先准备好开发环境。
3.1 创建虚拟环境与安装依赖
强烈建议使用虚拟环境来管理项目依赖,避免包冲突。
# 1. 创建并进入项目目录
mkdir my-first-ai-agent && cd my-first-ai-agent
# 2. 创建Python虚拟环境(以venv为例)
python -m venv venv
# 3. 激活虚拟环境
# 在 macOS/Linux 上:
source venv/bin/activate
# 在 Windows 上:
# venv\Scripts\activate
# 4. 安装核心依赖
pip install langchain langchain-openai
这里我们安装的是 langchain 主包和 langchain-openai 集成包。后者是LangChain社区维护的OpenAI专用包,比通用的 langchain[openai] 更新更及时。
3.2 获取并配置API密钥
你需要一个OpenAI的API密钥。如果你没有,可以去OpenAI官网注册获取。
重要安全提示:永远不要将API密钥直接硬编码在代码中或提交到版本控制系统(如Git)。
最佳实践是使用环境变量:
# 在终端中设置环境变量(临时)
# macOS/Linux:
export OPENAI_API_KEY='你的-api-key-here'
# Windows (PowerShell):
# $env:OPENAI_API_KEY='你的-api-key-here'
或者在项目根目录创建一个 .env 文件(确保该文件在 .gitignore 中):
OPENAI_API_KEY=你的-api-key-here
然后在Python代码中使用 python-dotenv 加载:
pip install python-dotenv
from dotenv import load_dotenv
load_dotenv() # 这会从 .env 文件加载环境变量
# 现在 os.getenv('OPENAI_API_KEY') 就能获取到密钥了
3.3 初始化LLM客户端
我们将使用最新的 ChatOpenAI 类来初始化模型客户端。 gpt-3.5-turbo 性价比高,适合实验。
# 文件:agent_core.py
import os
from langchain_openai import ChatOpenAI
# 从环境变量读取API密钥
# 确保已按照上述方法之一设置了 OPENAI_API_KEY
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# temperature 控制创造性,0表示更确定性和事实性,适合工具调用场景。
print(f"LLM 初始化成功: {llm.model_name}")
运行一下这个脚本,如果没有报错,说明你的环境和API配置成功了。
4. 打造你的第一个工具(Tools)
没有工具的Agent就像没有手臂的智者,空有想法无法落地。我们来为Agent打造两把最常用的“瑞士军刀”:一个用于计算,一个用于获取实时信息。
4.1 构建一个计算器工具
虽然LLM本身能做简单计算,但对于复杂或精确的数学运算,专用工具更可靠。我们将使用Python的 numexpr 库来安全地评估数学表达式。
pip install numexpr
# 文件:tools.py
from langchain.tools import tool
import numexpr
@tool
def calculator(expression: str) -> str:
"""
计算一个数学表达式的值。
支持加减乘除(+-*/)、乘方(**)、括号等。
例如:“(3 + 5) * 2 / 4”
Args:
expression (str): 数学表达式字符串。
Returns:
str: 计算结果字符串,或错误信息。
"""
try:
# 使用 numexpr 安全地计算,避免 eval 的安全风险
result = numexpr.evaluate(expression).item()
return f"计算结果: {result}"
except Exception as e:
return f"计算错误: {e}"
# 测试工具
if __name__ == "__main__":
print(calculator.invoke("3 * 7 + 10")) # 输出:计算结果: 31
print(calculator.invoke("10 / (1 + 1)")) # 输出:计算结果: 5.0
关键点解析:
-
@tool装饰器 :这是LangChain提供的便捷方式,能将一个普通Python函数自动包装成LangChain可识别的Tool对象。它会利用函数的文档字符串(docstring)作为工具的“说明书”提供给LLM。 - 参数与文档 :
expression: str定义了工具输入。文档字符串至关重要,LLM完全依赖它来理解这个工具是干什么的、该怎么用。描述要清晰、准确,最好包含示例。 - 安全计算 : 绝对不要使用Python内置的
eval()函数来处理用户输入 ,这是严重的安全漏洞。numexpr库限制了解析能力,更为安全。
4.2 集成一个搜索工具(Tavily Search)
为了让Agent能获取最新信息,我们集成一个搜索工具。这里使用Tavily Search API,它专为AI Agent设计,返回结构化的搜索结果摘要。
- 先去 Tavily官网 注册获取免费API密钥。
- 安装集成包并配置密钥。
pip install langchain-tavily
# 设置环境变量 TAVILY_API_KEY
# 文件:tools.py (续)
from langchain_community.tools.tavily_search import TavilySearchResults
# 初始化搜索工具,限制返回结果数为3以提高效率
search_tool = TavilySearchResults(max_results=3)
# 可以测试一下(会消耗API调用次数,慎用)
# if __name__ == "__main__":
# print(search_tool.invoke("今天北京天气怎么样?"))
现在,你的工具箱里就有了一把“计算尺”和一个“信息雷达”。
5. 组装智能体:从零构建AgentExecutor
有了大脑(LLM)和工具,现在需要把它们组装起来,并装上控制系统(AgentExecutor)。
5.1 创建工具列表并绑定LLM
首先,将我们创建的工具放入一个列表,然后使用 create_openai_tools_agent 来创建一个符合OpenAI Function Calling格式的Agent。这是目前最稳定、高效的Agent类型之一。
# 文件:build_agent.py
from langchain.agents import create_openai_tools_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from tools import calculator, search_tool # 导入我们定义的工具
from agent_core import llm # 导入初始化好的LLM
# 1. 定义工具列表
tools = [calculator, search_tool]
# 2. 构建提示词模板(Prompt Template)
# 这是指导Agent行为的“宪法”,非常重要!
prompt = ChatPromptTemplate.from_messages([
("system", """你是一个乐于助人的AI助手。你可以使用工具来获取信息或进行计算。
请严格按照以下规则行事:
1. 如果用户的问题涉及实时信息、最新事件或你不知道的具体数据,请务必使用搜索工具。
2. 如果用户的问题涉及数学计算,请使用计算器工具。
3. 你的回答应基于工具返回的事实,不要捏造信息。
4. 保持回答简洁、准确。
"""),
MessagesPlaceholder(variable_name="chat_history"), # 为对话历史占位
("human", "{input}"), # 用户当前输入
MessagesPlaceholder(variable_name="agent_scratchpad"), # Agent思考过程占位
])
# 3. 创建Agent
agent = create_openai_tools_agent(llm=llm, tools=tools, prompt=prompt)
提示词设计心得: 系统提示词(System Prompt)是Agent的“人格”和“行为准则”。上面的示例设定了基本规则:
- 明确工具使用条件 :告诉LLM什么时候该用什么工具。
- 强调事实依据 :要求基于工具结果回答,减少“幻觉”。
- 预留插槽 :
MessagesPlaceholder是关键,它允许我们在运行时动态插入“对话历史”和“Agent的思考过程”,这是实现多轮对话和复杂推理的基础。
5.2 创建AgentExecutor并添加记忆
Agent本身只负责“决策”, AgentExecutor 则负责驱动整个“决策-执行”循环。
# 文件:build_agent.py (续)
from langchain.memory import ConversationBufferMemory
# 4. 创建记忆(Memory)
# ConversationBufferMemory 会保存最近的对话历史
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
# memory_key="chat_history" 指定了记忆在提示词中对应的变量名
# return_messages=True 确保返回的是LangChain的Message对象列表,而不是字符串
# 5. 创建Agent执行器(AgentExecutor)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory,
verbose=True, # 设为True可以看到Agent的思考过程,调试时非常有用!
handle_parsing_errors=True, # 优雅地处理LLM输出解析错误
max_iterations=5, # 限制最大循环次数,防止“死循环”
)
print("AI Agent 组装完成!")
参数详解与避坑指南:
verbose=True: 开发调试必备 。打开后,控制台会打印出Agent每一步的思考、工具调用和结果,让你清晰看到内部运作,快速定位问题。handle_parsing_errors=True:当LLM的输出不符合工具调用格式时,这个选项能捕获错误并返回一个友好信息,而不是让整个程序崩溃。max_iterations=5: 安全阀 。有些问题可能导致Agent陷入不停调用工具的循环。设置一个上限(比如5-10次),超过后强制停止,并返回当前最佳结果。
6. 运行与测试:让你的Agent活起来
现在,激动人心的时刻到了,让我们来测试这个亲手打造的Agent。
6.1 基础功能测试
创建一个简单的交互循环:
# 文件:run_agent.py
from build_agent import agent_executor
def chat_with_agent():
print("你好!我是你的AI助手。输入 '退出' 或 'quit' 来结束对话。")
while True:
try:
user_input = input("\n你: ")
if user_input.lower() in ['退出', 'quit', 'exit']:
print("再见!")
break
if not user_input.strip():
continue
# 调用Agent执行器
response = agent_executor.invoke({"input": user_input})
print(f"\n助手: {response['output']}")
except KeyboardInterrupt:
print("\n\n对话被中断。")
break
except Exception as e:
print(f"\n抱歉,出错了: {e}")
if __name__ == "__main__":
chat_with_agent()
运行 python run_agent.py ,然后尝试以下问题:
-
计算测试 :“123乘以456等于多少?”
- 预期 :Agent会识别出这是计算问题,调用
calculator工具,并返回正确结果。 - 控制台观察 :当
verbose=True时,你会看到类似Action: calculator, Action Input: 123*456的日志。
- 预期 :Agent会识别出这是计算问题,调用
-
搜索测试 :“2024年巴黎奥运会什么时候开幕?”
- 预期 :Agent识别出这是需要最新信息的问题,调用
search_tool,并基于搜索结果回答。 - 注意 :搜索工具会消耗Tavily API额度,免费额度有限。
- 预期 :Agent识别出这是需要最新信息的问题,调用
-
混合任务测试 :“北京和上海今天的温差大概是多少?先用搜索工具查一下它们的气温。”
- 预期 :这是一个需要多步推理的任务。Agent需要先搜索两地气温,理解结果,然后可能再调用计算器求差值。这考验Agent的规划和工具组合能力。
6.2 理解执行流程与调试
当 verbose=True 时,控制台输出是学习Agent内部机制的最佳教材。以计算问题为例,你可能会看到:
> 进入新的AgentExecutor链...
思考:用户问了一个乘法计算,我需要使用计算器工具。
Action: calculator
Action Input: 123*456
Observation: 计算结果: 56088
思考:我得到了计算结果,可以回答用户了。
Final Answer: 123乘以456等于56088。
> 链结束。
这个流程清晰展示了 ReAct(Reasoning + Acting)模式 :思考(Reasoning) -> 行动(Action,调用工具)-> 观察(Observation,工具结果)-> 再思考 -> ... -> 最终回答。
调试常见问题:
- Agent不调用工具 :检查提示词是否明确要求使用工具;检查工具的描述文档是否清晰;尝试降低LLM的
temperature使其更“听话”。 - 工具调用参数错误 :LLM可能错误格式化了输入。确保工具函数的参数类型和文档描述匹配。有时需要在提示词中给出更具体的调用示例。
- 无限循环 :检查
max_iterations设置。观察verbose日志,看Agent是否在几个工具间来回调用而无进展。可能需要优化提示词或增加更严格的停止条件。
7. 进阶优化与功能扩展
一个基础的Agent已经跑通了,但要让它在实际应用中更可靠、更强大,还需要以下优化。
7.1 优化提示词工程
提示词是Agent的“指挥棒”。一个糟糕的提示词会让强大的模型表现失常。我们可以优化之前的系统提示词:
advanced_system_prompt = """
你是一个专业、精准的AI助手。你的核心任务是利用工具高效、准确地解决用户问题。
## 核心原则
1. **工具优先**:对于计算、实时信息查询、数据获取等任务,必须优先使用对应工具,不得仅凭内部知识猜测。
2. **结果验证**:对工具返回的结果进行简要合理性检查。如果结果明显异常(如计算错误、搜索无结果),可以尝试换一种方式或告知用户。
3. **分步解决**:对于复杂问题,将其分解为多个子步骤,一步一步地使用工具解决,并在最终回答中清晰说明过程。
4. **诚实透明**:如果工具无法提供信息,或问题超出能力范围,直接、礼貌地说明。绝不捏造答案。
## 工具使用规范
- **计算器 (`calculator`)**:用于所有数学表达式计算,包括算术、百分比、比较等。
- **网络搜索 (`tavily_search`)**:用于查询当前事件、新闻、概念解释、实时数据、产品信息等任何需要最新或外部知识的问题。
现在,请开始帮助用户。记住:思考过程要简洁,最终答案要完整。
"""
# 用这个新的提示词模板替换之前build_agent.py中的简单提示词
优化点包括:更清晰的原则、分步指导、结果验证要求和工具使用规范。这能显著提升Agent的决策质量。
7.2 实现结构化输出与流式响应
有时我们希望Agent的输出是规整的JSON数据,方便后续程序处理。可以利用LangChain的 PydanticOutputParser 或LLM自身的结构化输出功能(如OpenAI的JSON Mode)。
对于聊天应用,流式响应能极大提升用户体验,让答案逐字显示。 AgentExecutor 支持通过 astream 方法实现流式输出,不过它流式返回的是整个执行过程的中间步骤。对于最终答案的流式输出,通常需要更底层的控制,一个常见的折中方案是让Agent快速执行完,然后流式输出最终生成的文本。
7.3 错误处理与超时控制
在生产环境中,健壮性至关重要。
from langchain.agents import AgentExecutor
import asyncio
robust_agent_executor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory,
verbose=False,
handle_parsing_errors="请重试你的问题,或者换一种方式提问。",
max_iterations=7,
max_execution_time=30, # 整体最大执行时间30秒
early_stopping_method="generate", # 达到最大迭代次数时,强制LLM生成一个最终答案
)
# 在调用时也可以使用超时控制
try:
# 使用asyncio为同步调用设置超时(示例)
response = asyncio.run(asyncio.wait_for(
robust_agent_executor.ainvoke({"input": user_input}),
timeout=45.0
))
except asyncio.TimeoutError:
response = {"output": "处理超时,请稍后再试或简化您的问题。"}
7.4 添加自定义工具:获取股票价格
让我们实践添加一个更复杂的自定义工具,例如通过一个公开API获取股票价格。
# 文件:custom_tools.py
import requests
from langchain.tools import tool
from datetime import datetime
@tool
def get_stock_price(symbol: str) -> str:
"""
获取指定股票代码的最新股价信息。
注意:此示例使用一个模拟API,真实场景需替换为可靠的金融数据API(如Alpha Vantage, Yahoo Finance等)。
Args:
symbol (str): 股票代码,例如 'AAPL' 代表苹果公司。
Returns:
str: 包含股价、变化等信息的字符串。
"""
# 示例:使用一个模拟API。实际使用时请替换URL和解析逻辑。
# 假设我们有一个返回模拟数据的端点
api_url = f"https://api.example-mock-finance.com/quote/{symbol.upper()}"
try:
response = requests.get(api_url, timeout=10)
response.raise_for_status()
data = response.json()
# 模拟解析
price = data.get('current_price', 'N/A')
change = data.get('change', 'N/A')
return f"{symbol.upper()} 最新股价: ${price},变化: {change} (数据更新时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')})"
except requests.exceptions.RequestException as e:
return f"获取 {symbol} 股价失败: {e}"
except KeyError:
return f"解析 {symbol} 股价数据时出错。"
# 将这个新工具添加到 tools 列表中
# tools.append(get_stock_price)
开发自定义工具的关键:
- 清晰的文档 :LLM完全依赖它。
- 健壮的异常处理 :网络请求可能失败,API格式可能变化。
- 输入验证 :确保
symbol是有效的格式。 - 安全考虑 :避免在工具内部执行不可信的代码或暴露敏感信息。
8. 常见问题排查与性能调优
在实际开发中,你肯定会遇到各种问题。这里汇总了一些典型场景和解决方案。
8.1 问题排查速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| Agent完全不调用工具,直接回答。 | 1. 提示词未强调工具使用。 2. 工具描述不清。 3. LLM temperature 过高,太“随意”。 4. 问题太简单,LLM觉得无需工具。 |
1. 强化系统提示词中的工具使用规则。 2. 检查并优化工具函数的文档字符串,加入明确用例。 3. 将 temperature 设为0或更低值。 4. 正常,对于常识问题,直接回答更高效。 |
工具调用格式错误,如 Action Input 不是字符串。 |
LLM未能按照指定格式生成输出。 | 1. 使用 handle_parsing_errors=True 防止崩溃。 2. 确保使用 create_openai_tools_agent ,它专为OpenAI的规范设计。 3. 在提示词中提供更具体的工具调用示例。 |
| Agent陷入调用循环,反复使用同一工具。 | 1. 工具返回的结果未能让LLM满意或理解。 2. 问题本身模糊或无法解决。 3. max_iterations 设置过高。 |
1. 查看 verbose 日志,分析工具返回的 Observation 是否有效。 2. 优化工具,使其返回更清晰、结构化的结果。 3. 适当降低 max_iterations (如设为5)。 4. 在提示词中增加“如果工具无法解决问题,请直接告知用户”的指令。 |
| 多轮对话中,Agent忘记之前聊过的内容。 | Memory未正确配置或未传入。 | 1. 检查 AgentExecutor 初始化时是否传入了 memory 参数。 2. 检查提示词模板中是否有 MessagesPlaceholder(variable_name="chat_history") ,且 variable_name 与 memory.memory_key 一致。 3. 确保每次调用 invoke 时, agent_executor 是同一个实例,这样才能保持记忆。 |
| 响应速度慢。 | 1. LLM API调用延迟。 2. 工具(如搜索)响应慢。 3. Agent迭代次数过多。 |
1. 考虑使用更快的模型(如 gpt-3.5-turbo )或配置API超时。 2. 为网络工具设置合理的超时时间,并考虑缓存。 3. 优化问题表述,减少Agent的困惑,从而减少迭代。使用 max_iterations 限制。 |
8.2 性能与成本优化建议
- 模型选择 :实验阶段用
gpt-3.5-turbo,成本低、速度快。对推理能力要求高的生产场景再考虑gpt-4。 - 提示词精简 :在系统提示词和工具描述中,使用准确、简洁的语言,减少不必要的token消耗。
- 缓存 :对于重复性查询(如某些计算、静态信息查询),可以考虑集成
LangChain的缓存功能(如InMemoryCache,RedisCache),避免重复调用LLM和工具。 - 异步调用 :如果你的应用是异步框架(如FastAPI),使用
ainvoke/astream异步方法,避免阻塞。 - 监控与日志 :记录每次调用的token使用量、工具调用次数、耗时等,便于分析和优化。
8.3 从原型到生产:下一步方向
当你完成了第一个可运行的Agent原型后,可以考虑以下方向深化:
- 引入LangGraph :当你的任务流程需要复杂的循环、分支或并行处理时(例如一个需要先规划步骤、再执行、最后验证的复杂任务),就是引入LangGraph的时候了。它用“图”来定义状态机,控制力更强。
- 实现RAG(检索增强生成) :为Agent接入私有知识库。结合向量数据库,让Agent能根据你提供的专属文档(公司手册、产品文档、个人笔记)来回答问题。这是当前企业级AI应用的热点。
- 前端集成 :为你的Agent构建一个Web界面(如用Gradio、Streamlit快速搭建)或集成到聊天软件(如Slack、钉钉)中。
- 部署与运维 :使用Docker容器化你的Agent应用,通过云服务(如AWS, GCP, Azure)或服务器进行部署。考虑设置API网关、速率限制和监控告警。
构建第一个AI Agent就像学骑自行车,最开始可能会摇晃,但一旦掌握了平衡(理解了LLM、工具、提示词、执行器之间的协作),你就能自由探索更广阔的天地。这个从零到一的过程,最大的收获不是一段可运行的代码,而是对智能体如何“思考”和“行动”建立起直观的认知。接下来,试着为你自己的场景定制工具和提示词,看看这个虚拟助手能帮你做到多少以前不敢想的事情。
更多推荐

所有评论(0)