LangChain Agent进阶:Function Calling与自动Tool注册实战指南

📅 2026/8/14 3:10:07
LangChain Agent进阶:Function Calling与自动Tool注册实战指南
1. 项目概述从“会思考”到“会动手”的Agent进化如果你已经跟着LangChain 30天教程走到了第23天那么恭喜你你已经跨过了Agent的基础门槛。之前的课程可能教会了你如何让Agent“思考”——通过ReActReasoning Acting框架让大模型能够规划、推理并调用工具。但今天我们要解决一个更实际、更核心的问题如何让Agent“动手”动得更优雅、更高效、更自动化这就是Day 23的主题Function Calling 自动Tool注册。想象一下你构建了一个客服Agent它需要查询天气、搜索知识库、调用计算器。按照传统方式你需要手动为每一个功能编写一个Tool类定义好名称、描述、参数schema然后一个个注册到Agent的Toolkit里。这在小规模时还行但当你的系统有几十上百个API或函数时维护成本会急剧上升每次新增功能都是一次代码修改和部署。而“Function Calling”结合“自动Tool注册”的理念就是要将这个过程标准化、声明化让Agent能像发现新插件一样自动识别和集成新的能力从而实现真正的“会动”。这不仅仅是代码层面的优化更是架构思想的升级。它意味着你的Agent系统从“硬编码的封闭工具箱”转向了“开放的可扩展能力平台”。无论是处理复杂的办公自动化流程还是集成瞬息万变的第三方服务API这套组合拳都能让你游刃有余。接下来我们就深入拆解如何实现这一目标。2. 核心思路拆解声明式函数与自动化发现要实现“Function Calling 自动Tool注册”我们需要先理解这两个概念是如何协同工作的以及为什么它们比传统方式更优越。2.1 Function Calling让LLM理解并结构化调用首先Function Calling并不是指编程语言中的函数调用。在LLM大语言模型的语境下它指的是一种标准化的协议或格式用于描述一个可供模型调用的外部函数或工具。其核心是一个结构化的JSON Schema明确告诉模型“这里有一个叫做get_weather的函数它需要一个location参数类型是字符串描述是‘城市名’。你可以调用它来获取天气信息。”传统方式中我们通过自然语言描述来“教”模型使用工具比如在Tool的描述里写“这个工具用来查询天气你需要给我一个地点”。这种方式依赖模型的自然语言理解能力不稳定且容易产生歧义。而Function Calling通过严格的JSON Schema将工具的“接口契约”清晰地定义出来大大提高了模型调用工具的准确性和可靠性。主流的大模型API如OpenAI GPT, Anthropic Claude都原生支持这种格式的请求和响应。2.2 自动Tool注册从手动配置到动态加载自动Tool注册是建立在Function Calling之上的工程化实践。其目标是系统在启动或运行时能够自动扫描、识别符合特定规范即Function Calling Schema的函数或类并将它们注册为Agent可用的Tool而无需开发者手动编写agent_toolkits [tool1, tool2, ...]这样的代码。这通常通过以下几种机制实现装饰器Decorator在函数定义上方使用tool装饰器自动将该函数及其参数信息转化为一个标准的Tool对象。目录扫描与动态导入指定一个目录如tools/系统自动扫描该目录下所有Python模块寻找继承了BaseTool类或标记了特定装饰器的类/函数并批量注册。配置文件驱动使用YAML或JSON配置文件集中声明所有可用的工具及其元数据名称、描述、参数schema、对应的执行函数路径系统根据配置动态加载。这样做的好处显而易见解耦和可维护性。工具的开发与Agent核心逻辑分离新增一个工具只需在指定位置添加一个符合规范的新文件或函数Agent系统重启后便能自动识别实现了“开箱即用”的插件化架构。2.3 技术选型为什么是LangChain Pydantic在这个实战中我们选择LangChain作为框架并深度结合Pydantic。原因如下LangChain的成熟生态LangChain对Tool和Agent的支持最为成熟提供了BaseTool基类、多种Agent执行器如create_react_agent以及丰富的内置工具。其tool装饰器是实现自动注册的绝佳起点。Pydantic的数据验证与Schema生成Pydantic库能利用Python类型注解type hints自动生成高质量的JSON Schema。这正好满足了Function Calling对参数格式的严格要求。用Pydantic的BaseModel来定义工具的输入参数既能获得强大的数据验证能力又能轻松导出标准的JSON Schema供LLM理解。无缝集成LangChain的Tool类可以很好地包装一个Pydantic模型定义的函数两者结合能构建出类型安全、描述清晰、易于注册的工具单元。这个组合为我们打造了一个坚实的技术底座接下来我们就进入实战环节看看如何一步步构建这个系统。3. 实战环境搭建与核心工具定义在开始写代码之前确保你的环境已经就绪。我们将创建一个干净的项目目录。3.1 环境准备与依赖安装首先创建一个新的项目目录并初始化虚拟环境推荐使用conda或venv。mkdir langchain-agent-advanced cd langchain-agent-advanced python -m venv venv # Windows: venv\Scripts\activate # Mac/Linux: source venv/bin/activate安装核心依赖。这里我们选择OpenAI的模型作为LLM驱动当然你也可以替换为其他兼容的模型如通过Ollama本地部署的模型。pip install langchain langchain-openai pydantic python-dotenv创建.env文件来管理你的敏感配置如API密钥。OPENAI_API_KEYyour_openai_api_key_here注意永远不要将API密钥硬编码在代码中或提交到版本控制系统如Git。使用.env文件并通过python-dotenv加载是行业最佳实践。3.2 使用Pydantic定义第一个“可调用函数”让我们从一个简单的工具开始一个计算阶乘的函数。我们将用Pydantic来定义它的输入参数。创建一个文件tools/math_tools.pyfrom pydantic import BaseModel, Field from langchain.tools import tool import math # 1. 使用Pydantic定义工具的参数模型 class FactorialInput(BaseModel): n: int Field(..., description一个非负整数用于计算其阶乘, ge0) # 2. 使用LangChain的tool装饰器并传入args_schema tool(args_schemaFactorialInput) def calculate_factorial(n: int) - str: 计算一个非负整数n的阶乘。 阶乘定义为 n! n * (n-1) * ... * 1其中0! 1。 这是一个计算密集型操作对于大的n值如1000可能会较慢。 try: result math.factorial(n) return f{n}! {result} except ValueError as e: return f输入错误{e}。请确保n是一个非负整数。 # 这个函数现在已经被装饰器包装成了一个LangChain的Tool对象。 # 它的名字、描述、参数schema都自动从函数和Pydantic模型中提取。关键点解析FactorialInput类继承自BaseModel字段n使用Field进行增强描述和约束ge0表示大于等于0。tool装饰器是关键。它接收args_schema参数这里我们传入了FactorialInput类。LangChain会利用这个Pydantic模型自动生成符合Function Calling规范的JSON Schema。函数的文档字符串...非常重要它会被自动用作Tool的描述直接影响LLM是否以及如何选择这个工具。描述应清晰、准确说明功能、输入和可能的限制。3.3 实现自动注册机制手动导入每个工具文件很麻烦。我们要实现一个工具加载器自动发现tools目录下的所有工具。创建core/tool_registry.pyimport importlib import pkgutil from pathlib import Path from typing import List, Dict, Any from langchain.tools import BaseTool import logging logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) class ToolRegistry: 自动工具注册表 def __init__(self, tools_package: str tools): 初始化注册表。 :param tools_package: 存放工具模块的Python包名目录需有__init__.py。 self.tools_package tools_package self._tools: Dict[str, BaseTool] {} def discover_and_register_tools(self) - List[BaseTool]: 自动发现指定包下的所有模块并注册其中被tool装饰的函数。 返回注册成功的工具列表。 try: # 动态导入工具包 tools_module importlib.import_module(self.tools_package) package_path Path(tools_module.__file__).parent # 遍历包下的所有模块 for _, module_name, is_pkg in pkgutil.iter_modules([str(package_path)]): if is_pkg: continue # 跳过子包如需递归可修改 full_module_name f{self.tools_package}.{module_name} try: module importlib.import_module(full_module_name) # 遍历模块中的所有属性 for attr_name in dir(module): attr getattr(module, attr_name) # 检查是否为LangChain的Tool实例由tool装饰器创建 if isinstance(attr, BaseTool) and attr_name ! BaseTool: tool_name attr.name if tool_name in self._tools: logger.warning(f工具名 {tool_name} 冲突位于模块 {full_module_name}。) else: self._tools[tool_name] attr logger.info(f已注册工具: {tool_name} - {attr.description[:50]}...) except Exception as e: logger.error(f导入模块 {full_module_name} 失败: {e}) continue except ImportError as e: logger.error(f无法导入工具包 {self.tools_package}: {e}) return [] return list(self._tools.values()) def get_tool_by_name(self, name: str) - BaseTool: 根据名称获取工具 return self._tools.get(name) def list_tools(self) - List[str]: 列出所有已注册的工具名 return list(self._tools.keys()) # 单例模式方便全局访问 _registry_instance None def get_tool_registry(tools_package: str tools) - ToolRegistry: 获取工具注册表的全局实例 global _registry_instance if _registry_instance is None: _registry_instance ToolRegistry(tools_package) _registry_instance.discover_and_register_tools() return _registry_instance代码解读与注意事项动态导入使用importlib和pkgutil来动态扫描tools目录下的所有.py文件无需硬编码导入语句。识别工具核心逻辑是判断模块中的属性是否为BaseTool的实例。被tool装饰器修饰后的函数其本质就是一个Tool对象继承自BaseTool。单例模式我们通过一个全局函数get_tool_registry来获取注册表实例确保工具只被加载和注册一次。冲突处理代码中简单处理了工具名冲突的警告。在实际项目中你可能需要更严格的命名规范或命名空间管理。__init__.py文件确保你的tools目录下有一个__init__.py文件可以是空的这样Python才会将其视为一个包package从而能够被import_module正确导入。实操心得自动注册在开发时非常方便但在生产环境部署时需要考虑冷启动时间。如果工具非常多扫描和导入可能耗时。一种优化策略是使用缓存将注册结果工具名和对应的类路径序列化到文件下次启动时直接加载仅当文件变更时才重新扫描。4. 构建支持Function Calling的智能体Agent工具准备好了现在我们需要一个能使用这些工具的智能体。我们将使用LangChain的create_react_agent因为它实现了ReAct框架能很好地与结构化工具调用配合。4.1 初始化LLM与Agent执行器创建main.py作为应用的入口点import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain.agents import create_react_agent, AgentExecutor from langchain.prompts import PromptTemplate # 导入我们的自动注册器 from core.tool_registry import get_tool_registry # 加载环境变量 load_dotenv() def initialize_agent(): 初始化LLM、工具集并创建React Agent。 # 1. 初始化LLM关键是指定model_kwargs来启用Function Calling llm ChatOpenAI( modelgpt-3.5-turbo, # 或 gpt-4 temperature0, # 对于工具调用低温度确定性高通常更好 api_keyos.getenv(OPENAI_API_KEY), model_kwargs{functions: None} # 注意对于新版API工具定义方式可能不同见下文说明。 ) # 2. 自动获取所有工具 registry get_tool_registry() tools registry.discover_and_register_tools() print(f✅ 已加载 {len(tools)} 个工具: {[t.name for t in tools]}) # 3. 定义ReAct Agent的提示词模板 # LangChain通常有内置模板这里我们自定义一个更清晰的版本 prompt_template PromptTemplate.from_template( 你是一个强大的AI助手可以调用工具来帮助用户解决问题。 你可以使用的工具如下 {tools} 使用以下格式来思考和行动 问题用户提出的问题 思考你需要分析问题并决定是否需要使用工具以及使用哪个工具 行动需要调用的工具名称必须是以下工具之一[{tool_names}] 行动输入调用该工具所需的输入必须是一个格式正确的JSON字符串 观察工具返回的结果 ... (这个“思考/行动/行动输入/观察”的循环可以重复多次) 最终答案当你有了最终答案时用它来回答用户的问题 开始 问题{input} 思考{agent_scratchpad} ) # 4. 创建ReAct Agent agent create_react_agent( llmllm, toolstools, promptprompt_template ) # 5. 创建Agent执行器控制交互流程 agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, # 开启详细日志方便调试 handle_parsing_errorsTrue, # 优雅处理模型输出解析错误 max_iterations5, # 防止无限循环设置最大迭代次数 early_stopping_methodgenerate # 当模型决定不再使用工具时停止 ) return agent_executor if __name__ __main__: agent_executor initialize_agent() # 测试交互 while True: try: user_input input(\n 请输入您的问题 (输入 quit 退出): ) if user_input.lower() quit: break if not user_input.strip(): continue result agent_executor.invoke({input: user_input}) print(f\n 最终答案: {result[output]}) except KeyboardInterrupt: print(\n\n程序退出。) break except Exception as e: print(f\n❌ 执行出错: {e})重要更新与说明 上述代码中model_kwargs{functions: None}是一种简写。实际上OpenAI的ChatCompletion API在2023年后推出了新的“工具调用”Tool Calling接口与旧的“函数调用”Function Calling略有不同但理念相通。更推荐的做法是使用LangChain的bind_tools方法# 更推荐的方式使用 bind_tools from langchain_core.utils.function_calling import convert_to_openai_tool # 将LangChain Tool转换为OpenAI Tool格式 openai_tools [convert_to_openai_tool(tool) for tool in tools] # 将工具绑定到LLM llm_with_tools llm.bind_tools(openai_tools) # 然后在创建agent时使用 llm_with_tools 而不是原始的 llm agent create_react_agent(llmllm_with_tools, toolstools, promptprompt_template)bind_tools方法会正确处理新的工具调用格式让模型更好地理解可用的工具列表。这是当前LangChain与OpenAI模型集成的最佳实践。4.2 添加更多工具以测试复杂性为了充分测试我们的自动注册系统和Agent的决策能力让我们在tools/目录下添加更多工具。创建tools/web_tools.pyfrom pydantic import BaseModel, Field, HttpUrl from langchain.tools import tool import requests from typing import Optional class SearchWebInput(BaseModel): query: str Field(..., description需要搜索的关键词) max_results: Optional[int] Field(5, description返回的最大结果数量, ge1, le10) tool(args_schemaSearchWebInput) def search_web(query: str, max_results: int 5) - str: 使用一个公共的搜索API例如DuckDuckGo Instant Answer来搜索网络信息。 注意这是一个模拟工具实际使用时需要替换为真实的API并处理速率限制和错误。 # 警告这是一个示例。实际使用需要注册API密钥并遵守服务条款。 try: # 示例使用DuckDuckGo Instant Answer API (无需密钥但有速率限制) url fhttps://api.duckduckgo.com/ params { q: query, format: json, no_html: 1, skip_disambig: 1 } response requests.get(url, paramsparams, timeout10) response.raise_for_status() data response.json() abstract data.get(AbstractText, ) answer data.get(Answer, ) related_topics data.get(RelatedTopics, [])[:max_results] result_parts [] if answer: result_parts.append(f直接答案: {answer}) if abstract: result_parts.append(f摘要: {abstract}) if related_topics: topics_text \n.join([f- {t.get(Text, t.get(Name, N/A))} for t in related_topics if isinstance(t, dict)]) if topics_text: result_parts.append(f相关主题:\n{topics_text}) return \n.join(result_parts) if result_parts else f未找到关于 {query} 的明确信息。 except requests.exceptions.RequestException as e: return f网络请求失败: {e} except Exception as e: return f搜索过程中发生错误: {e} class FetchURLInput(BaseModel): url: HttpUrl Field(..., description需要获取内容的完整URL地址) tool(args_schemaFetchURLInput) def fetch_url_content(url: str) - str: 获取指定URL的网页内容文本部分。会遵循robots.txt并设置合理的请求头。 注意仅用于获取公开、合法的网页内容。 headers { User-Agent: Mozilla/5.0 (compatible; LangChain-Tool-Bot/1.0; http://example.com/bot) } try: response requests.get(url, headersheaders, timeout15) response.raise_for_status() # 简单提取文本实际项目应使用如BeautifulSoup等库进行解析 # 这里仅返回前2000个字符作为演示 return response.text[:2000] (... if len(response.text) 2000 else ) except requests.exceptions.RequestException as e: return f获取URL内容失败: {e}创建tools/date_tools.pyfrom pydantic import BaseModel, Field from langchain.tools import tool from datetime import datetime, timedelta import calendar class CalculateDateInput(BaseModel): days_offset: int Field(..., description从今天起偏移的天数正数为未来负数为过去) tool(args_schemaCalculateDateInput) def calculate_date(days_offset: int) - str: 计算从今天起偏移指定天数后的日期。 target_date datetime.now() timedelta(daysdays_offset) return target_date.strftime(%Y年%m月%d日 %A) class GetWeekInfoInput(BaseModel): date_str: str Field(None, description可选日期字符串格式为YYYY-MM-DD。默认为今天。) tool(args_schemaGetWeekInfoInput) def get_week_info(date_str: str None) - str: 获取指定日期是星期几以及它所在月的日历概览。 if date_str: try: target_date datetime.strptime(date_str, %Y-%m-%d) except ValueError: return 日期格式错误请使用 YYYY-MM-DD 格式例如 2023-10-27。 else: target_date datetime.now() weekday target_date.strftime(%A) year, month target_date.year, target_date.month # 获取当月日历 cal calendar.month(year, month) return f日期 {target_date.strftime(%Y-%m-%d)} 是 {weekday}。\n\n{year}年{month}月日历\n{cal}现在你的tools目录下应该有math_tools.pyweb_tools.pydate_tools.py以及一个__init__.py文件。重新运行main.py你会看到控制台输出类似✅ 已加载 4 个工具: [calculate_factorial, search_web, fetch_url_content, calculate_date, get_week_info]这表明自动注册机制成功发现了所有工具。5. 运行测试与Agent行为分析启动main.py让我们进行一系列测试观察Agent如何利用Function Calling和自动注册的工具进行推理和行动。测试1简单计算 请输入您的问题 (输入 quit 退出): 请问5的阶乘是多少 进入新的Agent执行链... 思考用户需要计算5的阶乘。我有一个工具叫calculate_factorial专门用于计算阶乘。我应该使用它。 行动calculate_factorial 行动输入{n: 5} 观察5! 120 思考我已经得到了计算结果可以直接回答用户。 最终答案5的阶乘是120。 最终答案: 5的阶乘是120。分析Agent正确识别了意图选择了匹配的工具并构造了符合JSON Schema{n: 5}的输入。整个过程完全结构化。测试2需要多步推理的复杂问题 请输入您的问题 (输入 quit 退出): 三天后是星期几顺便告诉我那天的日期。 进入新的Agent执行链... 思考用户问了两个问题1) 三天后是星期几2) 那天的日期。我有一个工具calculate_date可以计算未来日期另一个工具get_week_info可以获取日期的星期信息和日历。我需要先计算出三天后的具体日期然后用那个日期去查询星期信息。 行动calculate_date 行动输入{days_offset: 3} 观察2023年10月30日 Monday 思考我得到了日期“2023年10月30日 Monday”。从观察中我已经知道那天是星期一。但用户也问了“那天的日期”虽然观察里已经有了我可以直接回答。不过为了更完整我也可以用get_week_info工具再确认一下并获取日历信息。 行动get_week_info 行动输入{date_str: 2023-10-30} 观察日期 2023-10-30 是 Monday。... 最终答案: 三天后是2023年10月30日星期一。分析这是一个经典的ReAct推理过程。Agent首先规划需要两个工具并按顺序调用。它甚至展示了“思考”步骤意识到第一个工具的结果已经包含了部分答案但为了完整性还是调用了第二个工具。这体现了其推理能力。测试3工具选择与参数处理 请输入您的问题 (输入 quit 退出): 帮我搜索一下LangChain的最新版本信息。 进入新的Agent执行链... 思考用户想搜索网络信息。我有一个search_web工具。我需要构造一个搜索查询。 行动search_web 行动输入{query: LangChain latest version, max_results: 3} 观察摘要LangChain是一个用于开发由语言模型驱动的应用程序的框架... 最新稳定版本是0.0.xxx ... 最终答案根据搜索LangChain的最新稳定版本是0.0.xxx ...此处为模拟结果。 最终答案: 根据搜索LangChain的最新稳定版本是0.0.xxx ...分析Agent成功将自然语言问题转化为结构化的工具调用并填充了query参数。它还使用了可选的max_results参数的默认值或根据描述推断。6. 高级技巧与生产级考量基础功能跑通后我们需要考虑如何将这个系统变得健壮、高效适用于生产环境。6.1 工具描述优化与提示工程工具的name和description来自函数docstring是LLM选择工具的唯一依据。优化它们能极大提升Agent的准确性。命名清晰使用动词开头如calculate_xxx,fetch_xxx,search_xxx明确表达动作。描述具体在docstring中第一句话概括功能后续详细说明输入输出、使用场景和限制。tool def search_company_stock(symbol: str) - str: 根据股票代码查询上市公司的实时股价和基本信息。 参数: symbol (str): 股票代码例如 AAPL 代表苹果公司00700 代表腾讯控股。 返回: str: 包含公司名称、当前股价、涨跌幅、市值等信息的格式化字符串。 注意: - 这是一个模拟工具实际需要接入金融数据API。 - 数据可能有15分钟延迟。 # ... 实现结构化提示词在给Agent的提示词模板中清晰列出工具列表和格式要求如我们之前在main.py中所做的那样。6.2 错误处理与韧性提升一个健壮的Agent必须能处理各种异常。工具执行错误在工具函数内部使用try...except捕获异常并返回友好的错误信息而不是抛出异常导致整个Agent崩溃。模型输出解析错误在AgentExecutor中设置handle_parsing_errorsTrue。你还可以自定义一个错误处理函数。agent_executor AgentExecutor( ..., handle_parsing_errorslambda e: f“抱歉我无法理解你的指令。请重新表述你的问题。错误详情{e}”, )网络与超时对于web_tools这类依赖外部服务的工具务必设置timeout参数并使用重试机制如tenacity库。输入验证Pydantic模型已经提供了第一层验证。你还可以在工具函数内部进行业务逻辑验证。6.3 性能优化与扩展工具懒加载不是所有工具都需要在启动时全部实例化。可以修改注册器只记录工具的定义如函数路径和schema在首次被调用时才动态导入和实例化减少内存占用和启动时间。工具路由Routing当工具数量庞大如超过50个时让LLM从所有工具中选择效率会降低。可以引入“路由层”先根据用户问题用一个小模型或规则判断工具类别如“计算”、“搜索”、“日期”再在该类别下让主模型精细选择。记忆与上下文当前的Agent是“无状态”的。对于多轮对话需要引入ConversationBufferMemory等记忆组件让Agent记住之前的交互历史。异步支持如果工具调用涉及大量I/O如网络请求考虑使用异步函数async def和LangChain的异步Agent执行器AgentExecutor.ainvoke来提升并发性能。6.4 安全与权限控制在自动注册的开放架构下安全尤为重要。工具访问控制不是所有用户都能使用所有工具。可以在注册时或调用前为工具添加权限标签如required_role: [admin, user]并在调用链中根据用户上下文进行鉴权。输入净化与沙箱对于执行系统命令、访问数据库或处理用户上传文件的工具必须进行严格的输入验证和沙箱隔离防止注入攻击。审计日志记录每一次工具调用的详细信息用户、时间、工具名、输入参数、输出结果脱敏后。这对于调试、分析和安全审计至关重要。7. 常见问题排查与调试实录在实际开发中你肯定会遇到各种问题。这里记录一些典型场景和解决方法。问题1Agent总是说“我没有这个工具”或选择了错误的工具。原因A工具描述不清晰。检查函数的docstring是否准确描述了功能和输入。用更具体、差异化的词汇。原因B提示词Prompt中工具列表格式不佳。确保{tools}和{tool_names}在提示词中被正确格式化列表清晰可读。解决方法开启Agent的verboseTrue模式观察它的“思考”过程看它是如何理解问题和匹配工具的。根据观察调整描述或提示词。问题2模型输出的“行动输入”不是有效的JSON。原因虽然Function Calling要求JSON但模型有时会输出不规范或带注释的JSON。解决方法在AgentExecutor中设置handle_parsing_errorsTrue。使用更强大的模型如GPT-4在复杂任务上表现更稳定。在自定义提示词中强化输出格式的要求例如明确写出“必须是一个格式正确的JSON字符串”。问题3自动注册没有找到我新添加的工具。原因A新工具文件没有放在tools_package参数指定的目录下或目录不是Python包缺少__init__.py。原因B工具没有被tool装饰器正确装饰或者装饰时没有正确传入args_schema。原因C注册器单例缓存。如果你在运行中修改了代码需要重启Python进程或重新初始化注册表。解决方法在注册器代码中添加更详细的日志打印扫描的模块和找到的属性。确保你的工具函数在模块顶层定义并且装饰器应用正确。问题4工具执行速度慢拖累整个Agent响应。原因工具本身执行慢如调用慢速API、复杂计算。解决方法超时设置在工具函数内部或AgentExecutor级别设置超时。异步化将工具改为异步函数并使用异步执行器。缓存对幂等相同输入总是相同输出且结果不常变的工具如某些查询添加缓存层如functools.lru_cache。并行调用如果Agent需要调用多个不依赖彼此结果的工具可以探索支持并行工具调用的Agent类型如Plan-and-Execute架构。通过Day 23的实战我们成功地将LangChain Agent从“手动装配”升级到了“自动装配”时代。Function Calling提供了精准的结构化交互接口而自动Tool注册则实现了系统的可扩展性和可维护性。这套组合拳是构建复杂、实用AI智能体的基石。记住核心在于清晰的定义Pydantic Schema、自动化的发现注册器和智能的调度ReAct Agent。接下来你可以尝试集成更强大的工具如数据库操作、邮件发送、文件处理或者结合LangGraph来编排更复杂的工作流让你的Agent真正“动”得起来解决实际问题。