LangChain 1.3 实战:从零构建能联网搜索与执行任务的智能 Agent
如果你正在尝试用大模型构建一个能联网搜索、查天气、发邮件甚至操作数据库的智能助手,却发现大模型本身只会“纸上谈兵”,无法真正执行这些外部操作,那么你遇到的正是当前AI应用开发的核心瓶颈:如何让大语言模型(LLM)与现实世界交互。
LangChain 正是为解决这个问题而生的框架,它通过“工具调用”(Tool Calling)机制,让 LLM 拥有了“手”和“脚”。然而,网上教程要么停留在概念,要么代码过时,面对 LangChain 1.3 的更新和复杂的 Agent 概念,很多开发者从“入门”直接走到了“放弃”。
本文将从零开始,手把手带你用 LangChain 1.3 构建一个能真正调用工具的智能 Agent。我们不只讲“是什么”,更会深入剖析:
- 工具调用与普通函数调用的本质区别:为什么它比简单的
if-else判断复杂得多? - LangChain 工具调用的核心流程与性能瓶颈:速度慢真的是 LangChain 的锅吗?
- 从单工具调用到多步推理 Agent 的完整演进路径:如何让模型学会“思考”后再行动?
- 一套可运行、可调试的本地化代码实战方案:完全基于开源模型,无需担心网络与费用。
读完本文,你将能独立开发一个能理解你的指令、自主选择工具、并执行复杂任务的 AI 助手。我们直接开始。
1. 核心问题:为什么需要 LangChain 和工具调用?
在深入代码之前,我们必须先理解要解决的根本问题。假设你直接问 ChatGPT:“上海今天的天气怎么样?” 它会根据训练数据中的知识(可能已过时)生成一段描述,但它无法实时访问天气 API 获取真实数据。LLM 是一个强大的“大脑”,但缺乏感知和操作世界的“感官”与“肢体”。
工具调用(Tool Calling)就是为 LLM 安装“肢体”的机制。其核心思想是:
- 定义工具:将外部能力(如搜索、计算、数据库查询)封装成函数,并附上清晰的文字描述。
- 模型决策:将用户问题、可用工具描述一起提交给 LLM。LLM 分析后,决定是否需要调用工具、调用哪个工具、以及传入什么参数。
- 执行与反馈:框架执行被选中的工具函数,获取结果(如真实的天气数据),再将结果返回给 LLM。
- 生成最终回答:LLM 结合工具返回的结果,组织成自然语言回复给用户。
这个过程看似简单,但隐藏着几个工程挑战:
- 格式标准化:如何让 LLM 的输出能被程序稳定地解析为工具调用指令?
- 复杂逻辑:用户问题可能需要连续调用多个工具(如先搜索“最佳咖啡店”,再查其“具体位置”)。
- 错误处理:工具执行失败时,如何让 LLM 理解并调整策略?
LangChain 的价值就在于它提供了一套标准化的组件(Tools, Agents, Chains)和设计模式,将这些复杂流程封装起来,让开发者能聚焦于业务逻辑。而Agent是 LangChain 中负责控制流的核心概念,一个能根据输入和中间结果动态决定下一步行动(调用工具或直接回答)的智能体。
很多人误以为 LangChain 只是“胶水代码”,但实际上,它定义了一套构建复杂 LLM 应用的“设计语言”。接下来,我们从环境搭建开始,亲手实践这套语言。
2. 环境准备与核心依赖安装
我们将创建一个纯净的 Python 项目,使用完全离线的本地大模型进行演示,确保过程可复现。这里我们选择Ollama来本地运行开源模型,它部署简单,且完美支持 LangChain。
2.1 基础环境与 Ollama 部署
首先,确保你的系统已安装 Python (建议 3.9 以上) 和 pip。然后,安装 Ollama。
对于 macOS/Linux:
curl -fsSL https://ollama.ai/install.sh | sh对于 Windows:访问 Ollama 官网 下载并安装可执行程序。
安装后,拉取一个适合工具调用的轻量级模型,例如qwen2.5:7b(中文理解好,工具调用格式遵循好):
ollama pull qwen2.5:7b拉取完成后,运行模型服务:
ollama serve默认服务会在http://localhost:11434启动。保持此终端运行。
2.2 创建项目并安装 Python 依赖
新建一个项目目录,并创建虚拟环境:
mkdir langchain-agent-tutorial && cd langchain-agent-tutorial python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate安装核心依赖。请特别注意 LangChain 版本,本文基于 1.3.x 版本,其 API 与早期版本(如 0.0.x)有较大差异。
pip install langchain==0.3.0 langchain-community==0.3.0 pip install langchainhub==0.1.20 pip install beautifulsoup4 requests # 用于后续的网页搜索工具示例这里我们安装langchain==0.3.0,这是 LangChain 1.3 生态的一部分(版本号规则已调整,langchain包版本与核心框架版本对应关系请以官方文档为准)。langchain-community包含了许多社区维护的工具和集成。
3. 核心概念拆解:Tool, LLM, Agent 如何协同工作
在写代码前,我们需要清晰理解三个核心组件的关系,这能帮你避免“复制代码却不知其所以然”的困境。
- LLM (大语言模型):项目的“大脑”。负责理解用户意图,并生成决策(调用工具或直接回答)。在 LangChain 中,它被封装为一个
ChatModel或LLM对象。 - Tool (工具):项目的“手”。任何一个能被 LLM 调用的函数。它必须包含:
name: 工具的唯一标识。description: 功能的自然语言描述,这是 LLM 决定是否调用该工具的唯一依据,描述质量直接决定工具调用的准确性。args_schema(可选): 参数的 Pydantic 模型,用于规范输入。func或run方法:具体的执行函数。
- Agent (智能体):项目的“指挥官”。它内部封装了一个
LLM和一组Tools,并遵循特定的AgentExecutor逻辑来循环执行“思考->行动->观察”的步骤,直到得出最终答案。
它们的工作流程如下图所示(概念性描述):
用户输入 -> Agent -> (LLM思考: 需要工具吗?) -> 是: 选择Tool并生成参数 -> 执行Tool -> 观察结果 -> 再次思考... -> 否: 生成最终回答 -> 输出给用户一个关键认知:LangChain 中的Agent本身不是一个魔法黑盒,它是一套预定义的提示词(Prompt)和执行循环(Loop)的模板。不同的AgentType(如ZERO_SHOT_REACT_DESCRIPTION,OPENAI_FUNCTIONS)对应不同的推理策略。接下来,我们从最简单的工具定义开始。
4. 实战第一步:手动封装与调用你的第一个工具
让我们暂时抛开 Agent 的复杂性,先感受一下最原始的工具调用流程。这能帮你透彻理解底层发生了什么。
4.1 定义一个计算器工具
创建一个文件first_tool.py:
# first_tool.py from langchain_core.tools import tool from typing import Optional # 使用 @tool 装饰器快速定义工具 @tool def multiply(a: float, b: float) -> float: """将两个数字相乘。当用户需要计算乘积时使用此工具。""" return a * b # 手动模拟工具调用流程 if __name__ == "__main__": # 1. 模拟LLM的“思考”输出:假设LLM决定调用 multiply 工具 # 在真实场景中,这部分由LLM根据工具描述生成 tool_name = "multiply" tool_args = {"a": 12.5, "b": 4} # 2. 根据工具名找到对应的工具对象 # 在实际框架中,Agent会维护一个工具列表 available_tools = [multiply] tool_to_call = None for t in available_tools: if t.name == tool_name: tool_to_call = t break # 3. 执行工具 if tool_to_call: result = tool_to_call.invoke(tool_args) print(f"工具执行结果: {result}") else: print("未找到指定工具")运行它:
python first_tool.py输出:工具执行结果: 50.0
这个例子揭示了核心:工具调用的本质是 LLM 输出一个结构化的指令(工具名和参数),然后由外部代码执行它。LangChain 自动化了“根据描述让 LLM 生成指令”和“根据指令查找并执行工具”的过程。
4.2 连接本地 LLM 与工具
现在,我们引入真正的 LLM,让它自己决定是否调用工具。创建llm_with_tool.py:
# llm_with_tool.py from langchain_community.llms import Ollama from langchain_core.tools import tool from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser # 1. 定义工具 @tool def get_word_length(word: str) -> int: """返回输入英文单词的长度。""" return len(word) # 2. 连接到本地 Ollama 服务的 LLM llm = Ollama(model="qwen2.5:7b", base_url="http://localhost:11434") # 3. 构建一个简单的提示词,将工具描述告知 LLM # 注意:这是一种简化的演示,并非标准的Agent调用方式。 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个有帮助的助手。你可以使用以下工具:\n工具名:{tool_name}\n工具描述:{tool_description}\n当用户的问题适合使用该工具时,请直接输出'TOOL_CALL:<参数>'的格式,例如'TOOL_CALL:hello'。否则,直接回答用户问题。"), ("human", "{question}") ]) # 4. 创建链 chain = prompt | llm | StrOutputParser() # 5. 测试 question = "单词 'langchain' 有几个字母?" response = chain.invoke({ "tool_name": get_word_length.name, "tool_description": get_word_length.description, "question": question }) print(f"LLM 原始输出: {response}") # 6. 手动解析并执行工具调用(模拟Agent的工作) if response.startswith("TOOL_CALL:"): word = response.split(":")[1].strip() tool_result = get_word_length.invoke({"word": word}) print(f"工具执行结果: {tool_result}") # 理论上,这里应该把结果再喂回给LLM生成最终回答 final_answer = f"单词 '{word}' 的长度是 {tool_result}。" print(f"最终回答: {final_answer}") else: print(f"LLM 直接回答: {response}")运行此脚本,观察 LLM 是否输出了TOOL_CALL:langchain这样的格式。这个例子虽然简陋,但它完整展示了工具调用的核心闭环:提示设计 -> LLM 决策 -> 解析执行 -> 结果整合。
然而,手动解析TOOL_CALL:这种格式非常脆弱。在实际开发中,我们需要依赖 LLM 和框架对结构化输出的支持。这就是 LangChain Agent 的价值所在。
5. 构建你的第一个真正 Agent:让 LLM 自动选择工具
LangChain 提供了多种 Agent 类型。对于支持工具调用(如 OpenAI 的function calling)或类似结构化输出能力的模型,我们可以使用create_react_agent等高级接口。由于我们使用的qwen2.5:7b支持工具调用格式,我们将使用 LangChain 最新的推荐方式。
5.1 定义多个工具并创建 Agent
创建first_agent.py:
# first_agent.py from langchain_community.llms import Ollama from langchain.agents import create_react_agent, AgentExecutor from langchain_core.tools import tool from langchain import hub # 用于拉取预定义的提示词 # 1. 定义多个工具 @tool def get_word_length(word: str) -> int: """返回输入英文单词的长度。""" return len(word) @tool def get_current_time() -> str: """返回当前的系统时间(UTC)。""" from datetime import datetime return datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC") # 2. 初始化 LLM llm = Ollama(model="qwen2.5:7b", base_url="http://localhost:11434") # 3. 获取一个标准的 ReAct 提示词模板 # ReAct (Reasoning + Acting) 是一种让 LLM 将思考过程与行动分离的经典范式。 prompt = hub.pull("hwchase17/react-chat") # 一个适合聊天交互的 ReAct 提示词 # 4. 创建工具列表 tools = [get_word_length, get_current_time] # 5. 创建 Agent 和 AgentExecutor # `create_react_agent` 会组装提示词,并配置LLM以支持工具调用。 agent = create_react_agent(llm, tools, prompt) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True) # `verbose=True` 会打印详细的执行步骤,便于调试。 # `handle_parsing_errors=True` 能优雅处理LLM输出格式错误。 # 6. 运行 Agent print("=== 测试 1: 需要调用工具的问题 ===") result1 = agent_executor.invoke({"input": "请告诉我单词 'supercalifragilisticexpialidocious' 的长度。"}) print(f"最终答案: {result1['output']}\n") print("=== 测试 2: 无需调用工具的问题 ===") result2 = agent_executor.invoke({"input": "你好,请做个自我介绍。"}) print(f"最终答案: {result2['output']}\n") print("=== 测试 3: 调用另一个工具 ===") result3 = agent_executor.invoke({"input": "现在UTC时间是什么?"}) print(f"最终答案: {result3['output']}")运行这个脚本 (python first_agent.py)。你会看到控制台输出类似以下内容:
=== 测试 1: 需要调用工具的问题 === > Entering new AgentExecutor chain... 我需要计算这个单词的长度。我可以使用 get_word_length 工具。 Action: get_word_length Action Input: {"word": "supercalifragilisticexpialidocious"} Observation: 34 Thought: 我已经得到了单词的长度。 Final Answer: 单词 “supercalifragilisticexpialidocious” 的长度是 34 个字母。 > Finished chain. 最终答案: 单词 “supercalifragilisticexpialidocious” 的长度是 34 个字母。通过verbose=True的输出,你可以清晰地看到 Agent 的“思考链”(Thought-Action-Observation)。这就是ReAct 框架的威力:它强制 LLM 输出结构化的推理步骤,使得整个决策过程变得可解释、可调试。
6. 开发实用级 Agent:集成真实世界工具(搜索与计算)
现在,我们构建一个更有用的 Agent,集成网络搜索和高级计算工具。我们将使用DuckDuckGo进行搜索(无需API Key),并使用langchain_experimental中的PythonREPLTool来执行任意 Python 代码进行复杂计算(注意安全风险)。
6.1 安装额外依赖并创建实用工具集
pip install duckduckgo-search langchain-experimental创建practical_agent.py:
# practical_agent.py from langchain_community.llms import Ollama from langchain.agents import create_react_agent, AgentExecutor from langchain_community.tools import DuckDuckGoSearchRun from langchain_experimental.tools import PythonREPLTool from langchain_core.tools import tool from langchain import hub import warnings warnings.filterwarnings('ignore') # 忽略一些不必要的警告 # 1. 初始化 LLM llm = Ollama(model="qwen2.5:7b", base_url="http://localhost:11434") # 2. 创建工具集 # 工具 A: 网络搜索 search = DuckDuckGoSearchRun() # 工具 B: Python REPL (谨慎使用!) python_repl = PythonREPLTool() python_repl.description = "一个可以执行Python代码并返回结果的工具。适用于数学计算、数据转换等。**警告:不要执行来自不可信来源的代码。**" # 工具 C: 自定义工具 - 单位换算 @tool def convert_currency(amount: float, from_currency: str, to_currency: str) -> str: """进行货币换算。这是一个模拟工具,实际汇率是固定的。 支持: USD, CNY, EUR, JPY。例如:100 USD 换算成 CNY。 """ # 模拟汇率表 rates = { "USD": {"CNY": 7.2, "EUR": 0.92, "JPY": 150, "USD": 1.0}, "CNY": {"USD": 0.14, "EUR": 0.13, "JPY": 21, "CNY": 1.0}, "EUR": {"USD": 1.09, "CNY": 7.8, "JPY": 163, "EUR": 1.0}, "JPY": {"USD": 0.0067, "CNY": 0.048, "EUR": 0.0061, "JPY": 1.0}, } try: rate = rates[from_currency.upper()][to_currency.upper()] result = amount * rate return f"{amount} {from_currency.upper()} = {result:.2f} {to_currency.upper()} (模拟汇率)" except KeyError: return f"不支持从 {from_currency} 到 {to_currency} 的换算。" tools = [search, python_repl, convert_currency] # 3. 创建 Agent prompt = hub.pull("hwchase17/react-chat") agent = create_react_agent(llm, tools, prompt) agent_executor = AgentExecutor( agent=agent, tools=tools, verbose=True, handle_parsing_errors=True, max_iterations=5, # 防止无限循环 early_stopping_method="generate" # 达到最大迭代次数时,强制生成最终答案 ) # 4. 测试复杂任务 questions = [ "用中文搜索一下 LangChain 的最新版本是什么,并总结其核心特性。", "计算 3 的 5 次方加上 10 的阶乘等于多少?", "把 5000 日元换算成人民币。", ] for q in questions: print(f"\n{'='*50}") print(f"用户问题: {q}") print(f"{'='*50}") try: result = agent_executor.invoke({"input": q}) print(f"\n最终答案: {result['output']}") except Exception as e: print(f"执行出错: {e}")运行此脚本。你将看到 Agent 如何针对不同问题选择不同的工具:
- 对于搜索问题,它会调用
DuckDuckGoSearchRun。 - 对于复杂计算,它会调用
PythonREPLTool并生成如math.factorial(10) + (3**5)的代码。 - 对于单位换算,它会调用我们自定义的
convert_currency工具。
重要安全提示:PythonREPLTool允许执行任意 Python 代码,在生产环境中使用必须进行严格的沙箱隔离或输入清洗,本文仅用于演示。对于计算类需求,更安全的做法是预定义专用的计算工具(如calculator)。
7. 深入原理:LangChain 工具调用的速度瓶颈与优化
很多开发者抱怨 LangChain 工具调用“慢”。我们需要客观分析,慢在哪里?
- LLM 推理速度:这是最主要的瓶颈。每次 Agent 的“思考”(生成 Thought/Action)都是一次完整的 LLM 调用。本地 7B 模型在普通 CPU 上推理一次可能需要数秒。
- 网络延迟:如果你使用云端 API(如 OpenAI),网络往返时间会叠加。
- 工具执行时间:如果工具本身是慢速操作(如爬取一个大型网页),那么整体时间就会很长。
- 框架开销:LangChain 的抽象层会带来一些额外开销,但在大多数场景下,这与前三点相比微乎其微。
优化建议:
- 模型层面:使用更快的推理引擎(如
vLLM,TGI)或量化模型。对于简单工具调用,小模型(如 3B, 7B)通常足够。 - 流程层面:
- 设置
max_iterations:防止 Agent 陷入无意义的循环。 - 使用更高效的 Agent 类型:如果模型原生支持
function calling(如 GPT-4, Claude, 或一些优化后的开源模型),使用create_openai_tools_agent会比create_react_agent更直接、更快。 - 精简提示词:过长的系统提示和上下文会拖慢推理。
- 设置
- 设计层面:
- 工具描述的精确性:清晰、无歧义的工具描述能减少 LLM 的理解偏差,避免错误调用和重试。
- 工具聚合:将经常连续调用的工具组合成一个,减少 LLM 调用次数。
下面,我们演示如何使用为工具调用优化的create_openai_tools_agent(即使使用本地模型,只要其遵循 OpenAI 的 function calling 格式即可)。修改practical_agent.py中的创建部分:
# 仅展示修改部分,在 practical_agent.py 中替换 agent 创建代码 from langchain.agents import create_openai_tools_agent # 确保你的 Ollama 模型支持 tool calling 格式。 # 创建 prompt prompt = hub.pull("hwchase17/openai-tools-agent") # 创建 agent agent = create_openai_tools_agent(llm, tools, prompt) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, ...)这种 Agent 期望 LLM 直接输出结构化的工具调用对象,而不是 ReAct 格式的文本,因此通常解析更快、更稳定。关键在于你的模型必须支持这种格式。
8. 常见问题与排查指南
在开发过程中,你几乎一定会遇到以下问题。这里提供系统的排查思路。
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
ModuleNotFoundError: No module named 'langchain.agents' | LangChain 版本不匹配。新版本中模块路径可能已变更。 | 运行pip show langchain和pip show langchain-community查看版本。 | 确保安装的是较新的 0.3.x 版本。对于旧版代码,可能需要将from langchain.agents import ...改为from langchain import agents或查阅对应版本的文档。 |
| Agent 陷入循环,不断调用同一个工具 | 1. 工具描述不清晰,LLM 无法正确理解结果。 2. max_iterations设置过高或未设置。3. LLM 本身推理能力不足。 | 1. 检查verbose日志,观察Observation(工具返回的结果)是否清晰。2. 查看是否达到 max_iterations后由early_stopping_method强制结束。 | 1. 优化工具描述和返回结果格式,使其对 LLM 更友好。 2. 合理设置 max_iterations(如 5-10)。3. 尝试能力更强的模型。 |
| LLM 不调用工具,总是直接回答 | 1. 工具描述不够有吸引力或与问题不匹配。 2. 系统提示词未明确要求使用工具。 3. 模型本身工具调用能力弱。 | 1. 检查工具description是否准确描述了功能和适用场景。2. 检查使用的 prompt模板是否是为工具调用设计的(如react-chat)。 | 1. 重写工具描述,以“当用户需要/想要...”开头,明确触发条件。 2. 尝试不同的提示词模板,或在提示词中明确强调“你必须使用可用工具”。 3. 换用工具调用能力更强的模型。 |
KeyError或参数解析错误 | 1. LLM 生成的参数格式与工具定义的args_schema不匹配。2. 工具函数参数名与 LLM 输出中的 key 不一致。 | 1. 查看verbose日志中的Action Input,是否是一个合法的 JSON 字符串。2. 对比工具函数的参数名。 | 1. 使用 Pydantic 模型明确定义args_schema,这能帮助 LLM 生成正确格式。2. 确保工具参数是简单的原生类型(str, int, float, bool)。 |
| 本地模型响应极慢 | 1. 模型太大,硬件资源不足。 2. 未使用 GPU 加速。 3. Ollama 服务配置问题。 | 1. 观察 CPU/GPU 和内存使用率。 2. 运行 ollama ps查看模型加载状态。 | 1. 换用更小的模型(如llama3.2:3b,qwen2.5:3b)。2. 确保 Ollama 支持 GPU(安装对应版本),并在 pull 模型时选择带量化后缀的(如 :7b-q4_K_M)。3. 检查 base_url是否正确。 |
DuckDuckGoSearchRun返回空或错误 | 1. 网络问题。 2. DuckDuckGo 临时屏蔽或更改了接口。 | 1. 尝试在浏览器中访问 DuckDuckGo。 2. 查看返回的错误信息。 | 1. 检查网络连接和代理设置。 2. 考虑使用其他搜索工具,如 TavilySearchResults(需 API Key)或SerpAPI(需 API Key)。 |
9. 生产环境最佳实践与安全考量
将基于 LangChain 的 Agent 部署到生产环境,需要超越“跑通Demo”的思维。
工具设计原则
- 最小权限:每个工具只拥有完成其任务所必需的最小权限。例如,一个文件读取工具不应有删除权限。
- 输入验证与清洗:在工具函数内部,对所有输入参数进行严格的类型、范围和内容检查,防止注入攻击(特别是对于
PythonREPLTool或执行 SQL 的工具)。 - 明确的错误处理:工具执行失败时,应返回结构化的错误信息,帮助 LLM 理解问题所在,而不是抛出未处理的异常导致整个 Agent 崩溃。
Agent 流程控制
- 设置超时与重试:为 AgentExecutor 设置
max_execution_time,并为网络请求类工具配置单独的超时和重试逻辑。 - 对话历史管理:对于多轮对话,需要妥善管理历史消息,避免上下文过长。考虑使用
ConversationBufferWindowMemory等组件。 - 可观测性与日志:关闭
verbose但将关键步骤(Thought, Action, Observation)记录到结构化日志系统(如 JSON 格式),便于监控和调试。
- 设置超时与重试:为 AgentExecutor 设置
模型与性能
- 模型选型:生产环境需权衡速度、成本与效果。可考虑使用专为工具调用优化的模型,或对通用模型进行微调。
- 缓存策略:对 LLM 的相同请求或工具调用的相同查询结果进行缓存,可以极大提升响应速度并降低成本。LangChain 提供了
LLMCache和ToolCache的接口。 - 异步处理:对于高并发场景,使用 LangChain 的异步接口(
ainvoke,ainvoke)以提高吞吐量。
安全与合规
- 用户输入过滤:对用户输入进行敏感词过滤和内容安全审核,防止 Agent 被用于生成有害内容。
- 工具访问控制:根据用户身份或会话上下文,动态决定哪些工具可用。例如,普通用户不能使用“删除数据库”工具。
- 审计追踪:记录每个用户会话中所有的工具调用、参数和结果,以满足审计要求。
遵循这些实践,你的 Agent 才能从玩具蜕变为可靠的生产力工具。从理解工具调用的本质,到手动模拟流程,再到使用 LangChain 构建功能完整的 Agent,最后考虑生产级部署,我们完成了一次完整的 LLM 应用开发之旅。核心在于理解 LLM 作为“决策大脑”与外部工具作为“执行手脚”的协作范式,而 LangChain 提供了实现这一范式的最佳实践工具箱。
