LangChain Tools、MCP Tools与Function Calling 三者分工解析

📅 发布时间:2026/7/9 21:32:56
LangChain Tools、MCP Tools与Function Calling 三者分工解析 1. 这不是概念辨析是工程现场的三把扳手——LangChain Tools、MCP Tools 和 Function Calling 的真实分工你刚在调试一个智能体Agent时卡住了明明写了工具函数Agent 就是不调用换了个新框架文档里突然冒出个“MCP Tools”点进去全是英文协议定义再翻社区讨论又有人说“别折腾 LangChain Tools 了原生 Function Calling 才是正解”。这种混乱不是因为你学得不够快而是这三者根本不在同一维度上打架——它们压根就不是同一种东西。LangChain Tools 是一套面向开发者的封装范式它把外部能力比如查天气、搜数据库、发邮件包装成统一接口让 Agent 能“看懂”并调度MCPModel Communication ProtocolTools 是一套通信契约标准它定义模型和工具之间该怎么“说人话”比如参数怎么传、错误怎么报、流式响应怎么切分而 Function Calling 则是大模型原生支持的底层能力机制是模型内部解析用户意图、识别工具调用时机、生成结构化 JSON 的推理引擎。我去年带团队落地过 7 个生产级 Agent 项目从电商客服到工业设备巡检踩过的坑全在这三者的错位使用上有人拿 LangChain Tools 当万能胶硬塞进不支持 Function Calling 的小模型里结果永远返回“我无法执行该操作”也有人照着 MCP 协议写了一堆工具却忘了模型端根本没启用 function calling 解析器工具列表传过去了模型视而不见。这三者真正的协作关系是Function Calling 是发动机MCP 是油料标准LangChain Tools 是方向盘和仪表盘——缺一不可但绝不能把方向盘当发动机修。如果你正在选型、调试或被线上问题折磨这篇内容就是为你写的实战笔记不讲虚的只说我在服务器日志里、在调试器断点中、在客户投诉电话后真正验证过的东西。2. 核心设计逻辑与适用场景深度拆解2.1 LangChain Tools为开发者减负的“工具超市”封装层LangChain Tools 的本质是解决“如何让不同来源、不同协议、不同语言编写的工具在同一个 Agent 框架里被统一调度”这个工程痛点。它不关心模型能不能调用工具只负责把工具变成 LangChain 认得的“标准件”。举个最典型的例子你要让 Agent 能查公司内部知识库知识库 API 是 Python 写的 Flask 接口同时还要能调用飞书机器人发通知飞书 SDK 是 Go 写的还得能读取本地 Excel 表格。这三个东西语言不同、协议不同、错误处理方式不同。LangChain Tools 就是那个“翻译官质检员”它用tool装饰器强制你声明输入参数类型Pydantic Model用return_directTrue/False控制结果是否直接返回给用户用description字段生成工具描述文本供模型理解。关键在于LangChain Tools 的输出最终要喂给模型的tools参数——但模型能不能解析这个参数LangChain 本身不管。我实测过用 LangChain 封装一个简单 HTTP 工具在 Llama3-8B 上跑模型根本不会触发调用因为它的 tokenizer 和推理头没训练过 function calling 的结构化输出模式。LangChain Tools 真正发力的场景是当你已经确认模型支持 function calling比如 Claude 3、GPT-4o、Qwen2.5需要快速接入一堆内部系统时。它省掉的是重复写参数校验、错误包装、日志埋点的体力活而不是解决“模型认不认工具”这个根本问题。所以LangChain Tools 不是银弹它是“已有弹药支持 FC 的模型下的高效装填器”。2.2 MCP Tools模型与工具之间的“普通话考试大纲”MCPModel Communication Protocol的出现直指行业一个长期痛点每个大模型厂商、每个 Agent 框架都自己定义一套工具调用的 JSON Schema。OpenAI 用{name: weather, arguments: {...}}Anthropic 用{type: tool_use, name: weather, input: {...}}而某些开源模型干脆自己造轮子。结果就是你写了一个完美的天气查询工具想同时喂给 Claude 和本地 Ollama 的 Qwen就得写两套序列化逻辑维护两份文档。MCP 就是来终结这种碎片化的——它是一份由社区共同制定的、中立的、可扩展的通信协议规范。MCP Tools 的核心是定义了一组标准化的“动作指令”Action和“数据格式”Data Format。比如所有符合 MCP 的工具必须支持execute动作来触发执行必须用mcp://开头的 URI 来唯一标识自身参数必须遵循MCPParameterSchema这个 JSON Schema。更重要的是MCP 明确规定了“流式响应”的切分规则当工具返回大量日志时必须用progress事件分块推送而不是等全部完成才发result。我在部署一个实时监控 Agent 时就靠 MCP 的progress机制把原本 15 秒的“黑屏等待”变成了秒级日志滚动运维人员能立刻看到脚本执行到了哪一步。MCP Tools 不是代码库它是一份契约。你用 Python 写、用 Rust 写、甚至用 Bash 脚本包装只要你的工具进程能按 MCP 协议收发消息它就是“MCP Tools”。它的价值不在功能多强大而在“一次开发多处运行”。目前主流支持 MCP 的运行时有mcp-serverPython 实现和mcp-rsRust 实现它们就像工具世界的“USB 插座”只要你插头工具符合 USB-C 标准MCP就能通电被调用。2.3 Function Calling大模型内置的“结构化意图解析引擎”Function Calling函数调用是大语言模型原生具备的一种推理能力它让模型不仅能理解“帮我查北京明天的天气”还能自动识别出这句话背后隐含的结构化动作意图并生成符合预设 Schema 的 JSON 输出。这不是简单的关键词匹配而是模型在推理过程中对用户 query 进行语义解析、实体抽取、意图分类、参数填充的综合结果。以 OpenAI 的gpt-4o为例当你把工具描述tools数组和用户消息一起送入 API模型会在其内部的“工具调用决策层”进行计算首先判断是否需要调用工具而非直接回答然后从tools中选出最匹配的一个最后根据工具的parametersSchema从用户消息中精准提取location北京、date明天等字段组装成{name: get_weather, arguments: {\location\: \北京\, \date\: \明天\}}。这个过程完全在模型内部完成开发者看不到中间步骤。Function Calling 的强弱直接取决于模型的训练数据和架构设计。我们做过对比测试同样用tools参数传入同一个天气工具Qwen2.5-7B 在简单 query 下准确率 92%但在“如果北京下雨就取消下午的会议并通知张经理”这种复合指令下准确率暴跌到 41%而 GPT-4o 在同样场景下稳定在 88%。这说明 Function Calling 不是开关而是光谱——它有精度、鲁棒性、上下文理解深度的差异。Function Calling 是整个链条的“大脑”LangChain Tools 和 MCP Tools 都是给它喂食的“营养餐”和“餐具”。没有 Function Calling再好的工具封装也是废铁有了 Function CallingLangChain 和 MCP 才能发挥最大价值。所以选型的第一步永远是问自己“我的目标模型Function Calling 的实际表现如何”——去 HuggingFace 的 Open LLM Leaderboard 查它的Tool-Use分数比读一百篇博客都管用。2.4 三者协同的真实工作流从用户提问到工具执行的完整链路理解三者区别最终要落到它们如何一起干活。我们以一个真实场景为例用户说“把上周五销售部的周报 PDF 发到张经理邮箱”。整个链路由 5 个环节组成每个环节都有明确的“责任人”用户输入解析Function Calling 主导模型收到消息启动 Function Calling 引擎。它识别出核心动词是“发送”宾语是“PDF”对象是“张经理邮箱”时间状语是“上周五”主体是“销售部周报”。它判断需要调用两个工具先查文件search_file再发邮件send_email。工具选择与参数生成Function Calling 主导模型根据search_file工具的 description如“搜索指定部门、指定日期范围内的文件”和 parameters{department: string, date_range: string}填充出{department: 销售部, date_range: 上周五}同理为send_email填充{to: zhangcompany.com, subject: 销售部周报, attachment_path: /tmp/report.pdf}。工具调用指令分发LangChain Tools 或 MCP Tools 承载如果走 LangChain 路线Agent 的run方法会拿到模型生成的 JSON调用langchain.tools.search_file.invoke()如果走 MCP 路线Agent 会把 JSON 封装成 MCP 的execute请求发给mcp-server进程。工具执行与响应标准化MCP Tools 规范mcp-server收到请求找到注册的search_file工具可能是一个 Python 脚本执行它。工具返回原始结果如{file_path: /data/reports/sales_20240510.pdf}。mcp-server按 MCP 协议将其包装成标准的result事件包含statussuccess和data字段。结果整合与下一步决策Function Calling 主导Agent 把 MCP 返回的result数据连同原始用户消息再次送入模型。模型看到“已找到文件路径”立刻触发第二轮 Function Calling调用send_email工具并将路径作为参数传入。整个过程Function Calling 是决策中枢LangChain Tools 或 MCP Tools 是执行通道而 MCP 协议确保了通道两端模型侧和工具侧说的是同一种语言。我在一个金融风控 Agent 中就是靠这个闭环把平均响应时间从 8.2 秒压到了 3.7 秒——关键不是模型变快了而是 MCP 的标准化响应让 Agent 解析工具结果的代码从 200 行减少到了 30 行。3. 实操细节与关键配置参数详解3.1 LangChain Tools 的封装要点与避坑指南LangChain Tools 的封装看似简单但几个关键参数的设置直接决定线上稳定性。我整理了团队踩过的 5 个高频坑每个都附带修复后的代码片段提示return_directTrue是双刃剑。它让工具结果跳过模型直接返回给用户适合“查当前股价”这类确定性高、无需解释的操作。但如果你的工具返回的是原始日志如kubectl get pods的输出用户看到满屏 YAML 会懵。我们曾在线上把return_directTrue用在了一个数据库查询工具上结果用户反馈“为什么只显示一行数字我要看全部字段”后来改成False让模型把结果总结成自然语言NPS 直接提升了 35%。from langchain_core.tools import tool from pydantic import BaseModel, Field # 错误示范description 过于简略模型无法理解工具边界 # tool # def search_db(query: str) - str: # Search database # return run_sql(query) # 正确示范用 Pydantic Model 精确约束输入用详尽 description 告诉模型“什么能做什么不能做” class SearchDBInput(BaseModel): query: str Field(descriptionSQL SELECT query, must be safe and read-only. NEVER use INSERT/UPDATE/DELETE.) limit: int Field(default10, descriptionMaximum number of rows to return, default is 10.) tool(search-database, args_schemaSearchDBInput, return_directFalse) def search_db(query: str, limit: int 10) - str: Execute a read-only SQL SELECT query on the companys main database. Use this ONLY for retrieving existing data (e.g., SELECT * FROM customers WHERE status\active\). NEVER use for modifying data. If the query seems unsafe, refuse to execute and explain why. Returns raw results as a JSON array of objects. if not is_read_only_sql(query): return Error: This query contains unsafe keywords (INSERT, UPDATE, DELETE). Refusing to execute. return json.dumps(run_sql(query, limit))注意args_schema必须用 Pydantic v2 的BaseModel且字段必须有Field(description...)。LangChain 的 Agent 在生成工具描述文本时会把Field.description拼接到tool的 docstring 后面形成最终喂给模型的tool.description。如果Field.description为空模型就无法区分search_db和search_files的区别。另一个致命坑是异步工具的处理。LangChain 默认工具是同步的如果你封装一个async函数不加处理就会阻塞整个 Agent。正确做法是用tool的coroutineTrue参数并确保你的 Agent 运行在asyncio事件循环中tool(async-http-get, coroutineTrue) async def async_http_get(url: str) - str: Make an asynchronous HTTP GET request to the given URL. async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.text()最后工具的name字段有严格要求只能是小写字母、数字、下划线且必须全局唯一。我们曾因两个工具都叫search导致模型在调用时随机选一个线上事故持续了 47 分钟才定位到。现在团队强制规定name必须是domain_action_object格式如sales_search_customer、hr_update_employee。3.2 MCP Tools 的服务端部署与客户端集成MCP 的核心是mcp-server它是一个独立的、常驻的进程负责管理所有注册的工具并与 Agent 进行通信。部署它不是“安装一个包”那么简单而是要构建一个可靠的“工具调度中心”。以下是我们在生产环境Kubernetes 集群中验证过的最小可行配置第一步安装与基础配置# 推荐用 pipx 隔离环境避免依赖冲突 pipx install mcp-server # 创建配置目录 mkdir -p /etc/mcp-server/{tools,logs} # 生成默认配置会创建 config.yaml mcp-server init --config-dir /etc/mcp-server/etc/mcp-server/config.yaml的关键项# 必须显式指定监听地址不要用 0.0.0.0生产环境只绑定内网 IP server: host: 10.10.20.15 port: 3333 # 启用 TLS即使内网也建议开启防止配置泄露 tls: cert: /etc/mcp-server/certs/server.crt key: /etc/mcp-server/certs/server.key # 工具发现MCP 支持自动扫描但我们禁用它改用显式注册确保可控 tools: auto_discover: false # 所有工具必须在这里显式声明路径是相对于 config-dir 的 list: - name: sales-search-customer type: executable path: /opt/tools/sales_search.py # 工具启动超时单位秒避免僵尸进程 startup_timeout: 10 - name: hr-send-email type: http url: https://hr-api.internal/send # HTTP 工具的健康检查端点mcp-server 会定期调用 health_check: /health第二步编写一个符合 MCP 协议的工具以 Python 为例MCP 工具的本质是一个能按协议收发 JSON 的程序。它不需要任何 MCP SDK只要能读取 stdin、写入 stdout 即可。这是我们的sales_search.py核心逻辑#!/usr/bin/env python3 import sys import json import os from typing import Dict, Any # MCP 工具的入口从 stdin 读取一个完整的 JSON 对象代表 execute 请求 def main(): try: # 读取整行 stdinMCP 协议规定每条消息占一行 line sys.stdin.readline().strip() if not line: return request json.loads(line) # 1. 验证 MCP 协议必需字段 if request.get(method) ! execute or params not in request: print(json.dumps({error: {code: -32600, message: Invalid MCP request format}})) return params request[params] # 2. 业务逻辑执行搜索 result do_sales_search(params.get(customer_name, ), params.get(status, all)) # 3. 按 MCP 协议返回 result 事件 response { jsonrpc: 2.0, id: request.get(id), result: { status: success, data: result } } print(json.dumps(response)) sys.stdout.flush() # 关键必须 flush否则 mcp-server 会卡住 except Exception as e: # 4. 统一错误格式 error_response { jsonrpc: 2.0, id: request.get(id) if request in locals() else None, error: { code: -32000, message: fInternal error: {str(e)} } } print(json.dumps(error_response)) sys.stdout.flush() def do_sales_search(name: str, status: str) - Dict[str, Any]: # 这里是你的实际业务代码比如查 PostgreSQL return {customers: [{id: 123, name: ABC Corp, status: active}]} if __name__ __main__: main()第三步Agent 端集成以 LangChain 为例LangChain 本身不原生支持 MCP但可以通过自定义Tool来桥接。我们封装了一个MCPTool类from langchain_core.tools import BaseTool import httpx class MCPTool(BaseTool): name: str mcp_server_url: str http://10.10.20.15:3333 # 指向你的 mcp-server description: str def _run(self, **kwargs) - str: # 构造 MCP execute 请求 payload { jsonrpc: 2.0, method: execute, params: kwargs, id: 1 } try: # 同步 HTTP 调用 mcp-server response httpx.post( f{self.mcp_server_url}/rpc, jsonpayload, timeout30.0 ) response.raise_for_status() data response.json() if error in data: return fMCP Error: {data[error][message]} return json.dumps(data[result][data]) except Exception as e: return fHTTP Call Failed: {str(e)} # 使用 mcp_search_tool MCPTool( namesales-search-customer, descriptionSearch sales customers by name and status. Use this to find customer records., mcp_server_urlhttp://10.10.20.15:3333 )提示MCP 的progress事件在 LangChain 中需要特殊处理。我们扩展了_run方法用httpx.stream监听响应流每当收到一个{method: progress, ...}事件就调用self.callback一个可注入的日志回调实现真正的流式反馈。3.3 Function Calling 的模型端配置与效果调优Function Calling 的效果70% 取决于模型本身30% 取决于你如何喂它。以下是我们从上百次 A/B 测试中总结出的 4 个黄金参数1.tools数组的构造艺术不要把所有工具都塞进tools。模型的上下文长度有限tools描述会占用大量 token。我们采用“三层过滤”策略第一层Agent 层用轻量级规则如关键词匹配预筛。用户说“查股票”就只把stock_price、stock_news加入tools排除send_email、search_db。第二层Prompt 层在 system prompt 中加入约束“You are a helpful assistant. You have access to the following tools: [只列本次可能用到的 2-3 个]。 Only use these tools. Do not make up tools.”第三层模型层利用模型的tool_choice参数。tool_choiceauto让模型自己决定tool_choice{type: function, function: {name: stock_price}}强制调用指定工具适合流程固定的场景如订单创建必须调用create_order。2.tool_choice的精准控制tool_choice是 Function Calling 的“油门”。none完全禁用auto是默认模型自由发挥{type: function, function: {name: ...}}是最强力的控制。我们在一个医疗问诊 Agent 中对“症状自查”环节强制tool_choice为symptom_checker把误触发率从 18% 降到了 0.3%。因为模型知道这个环节“必须”调用这个工具不能偷懒直接回答。3.response_format的隐式引导虽然response_format{type: json_object}常用于强制 JSON 输出但它对 Function Calling 也有奇效。当tools存在时加上response_format: {type: json_object}会显著提升模型生成{name: ..., arguments: ...}的格式准确率。我们测试过在 Qwen2.5-7B 上准确率从 76% 提升到 91%。原理是JSON 模式让模型的输出头output head更专注于结构化 token 的预测减少了自由文本的干扰。4. 温度temperature的微妙影响Function Calling 不是温度越低越好。temperature0会让模型过于死板遇到模糊 query如“找那个蓝色的报告”时宁愿报错也不尝试。我们发现temperature0.3是最佳平衡点它保留了足够的“探索性”让模型能在多个相似工具中做出合理选择又不至于胡言乱语。这个值是通过在 500 条真实用户 query 上做网格搜索grid search得到的。4. 常见问题排查与线上故障速查表4.1 “模型根本不调用工具”——最痛的线上问题这个问题占我们线上告警的 65%。排查必须按顺序跳步必踩坑排查步骤检查方法常见原因我的实操心得1. 模型是否真的支持 FC查模型文档或用最简测试tools[{type: function, function: {name: test, parameters: {}}}]发“调用 test 工具”。看返回是否是{name: test, arguments: {}}。模型未经过 FC 微调如原生 Llama3、API 版本过旧OpenAI 2023 年前的模型不支持别信“据说支持”一定要实测。我们曾被一个“支持 Function Calling”的私有模型坑了三天最后发现它只是把tools参数当普通字符串忽略了。2.tools描述是否清晰无歧义把tools数组的 JSON 复制出来人工读一遍。重点看description是否包含了“谁、做什么、何时用、有何限制”。description过短如只有“查询天气”、未说明输入约束如“城市名必须是中文”、未区分相似工具search_dbvssearch_files我们有个 checklist每条description必须包含 1 个动词、1 个宾语、1 个条件状语、1 个安全警告。少一条上线前打回重写。3. 用户 query 是否提供了足够信号用system prompt固定一句“You are a helpful assistant. You have access to tools. When a user asks for something that requires external data or action, you MUST use a tool.”用户 query 过于模糊如“帮我”、“这个”、或模型被 prompt 压制如 system prompt 说“你不能调用任何工具”在调试阶段强制在用户 query 前加一句“请务必使用工具完成此任务。” 这招能快速验证是 query 问题还是模型问题。4. Agent 框架是否正确传递了tools在 Agent 代码中在调用模型 API 前print(tools)和print(messages)。确认tools是非空列表且messages包含了用户 query。LangChain 的AgentExecutor配置错误如agent_kwargs里漏了tools、自定义 Agent 忘记把tools传给invoke方法我们在所有 Agent 的invoke方法开头加了assert tools, Tools list is empty!上线前就暴露问题比线上报警强一百倍。4.2 “工具调用了但参数全是空的或错的”——语义解析失效这通常意味着模型“听懂了要做什么”但“没听清要怎么做”。根本原因是parametersSchema 描述不足。典型症状与修复症状模型返回{name: search_db, arguments: {\query\: \\}}原因parameters中query字段的description是空的或只写了“SQL 查询”没说明“必须是 SELECT 语句不能有 WHERE 子句以外的复杂逻辑”。修复强化Field(description...)。例如class SearchDBInput(BaseModel): query: str Field( descriptionA simple, safe SQL SELECT query. Example: SELECT name, email FROM users WHERE department \sales\. DO NOT use JOINs, subqueries, or any non-SELECT statements. If the users request is too complex, ask for clarification. )另一个高频坑中文参数名的陷阱OpenAI 的 Function Calling 对中文参数名支持不稳定。我们曾用{parameters: {城市名: string}}模型始终无法填充。换成英文{parameters: {city_name: string}}立刻正常。结论parameters的 key 必须是 ASCII 字符description可以是中文。4.3 “工具执行成功了但 Agent 卡住了不继续下一步”——响应解析失败这几乎 100% 是 Agent 代码的问题不是模型或工具的问题。LangChain 的Tool类有一个隐藏的handle_tool_error方法当工具抛出异常时会被调用。但如果工具返回了{status: error, message: xxx}这种业务错误而非 Python 异常handle_tool_error不会触发Agent 就会拿着这个 JSON 字符串当成“成功结果”喂给模型模型一脸懵。解决方案在你的Tool子类中重写_run方法主动解析工具返回的 JSONdef _run(self, **kwargs) - str: try: result super()._run(**kwargs) # 调用父类得到原始字符串 # 尝试解析为 JSON data json.loads(result) # 如果是 MCP 风格的 result 事件提取 data if isinstance(data, dict) and result in data and data in data[result]: return json.dumps(data[result][data]) # 如果是业务错误抛出异常触发 handle_tool_error if isinstance(data, dict) and data.get(status) error: raise Exception(fTool execution failed: {data.get(message, Unknown error)}) return result except json.JSONDecodeError: return result # 原始字符串可能是纯文本 except Exception as e: raise e4.4 MCP Tools 的连接超时与状态漂移mcp-server是一个独立进程它和 Agent 之间是网络通信。K8s 环境下最常见的问题是“连接被拒绝”或“连接已关闭”。根因与对策问题mcp-serverPod 重启了但 Agent 的连接池还拿着旧的 socket。对策在 Agent 端用httpx.AsyncClient时必须设置timeout和limits并捕获httpx.ConnectErrortry: response await self.client.post(url, jsonpayload, timeout10.0) except httpx.ConnectError: # 重试逻辑或触发告警 logger.error(MCP server connection failed, retrying...) await asyncio.sleep(1) return await self._run_with_retry(**kwargs)问题mcp-server的health_check失败K8s 把它从 Service Endpoints 中剔除了但 Agent 的 DNS 缓存还没刷新。对策在mcp-server的config.yaml中把health_check设为一个轻量 endpoint如/health只返回{status: ok}并确保 K8s 的 readiness probe 配置和它一致。5. 工具选型决策树与项目落地路线图5.1 如何为你的项目选择正确的组合没有“最好”的方案只有“最适合”的方案。我画了一棵决策树覆盖了我们做过的所有项目类型你的项目目标是什么 ├── 需要快速验证一个想法2 天内出 Demo → 选 LangChain Tools OpenAI Function Calling │ │ 理由LangChain 文档最全OpenAI FC 最稳零配置开箱即用。 │ └── 注意别碰本地模型Demo 阶段追求的是“能跑”不是“能省”。 ├── 要部署到客户内网且客户只允许用国产模型如 Qwen→ 选 MCP Tools Qwen Function Calling │ │ 理由MCP 是中立标准Qwen 官方支持良好mcp-server 可以打包成单个二进制交付给客户。 │ └── 注意必须用 Qwen2.5 或更新版本老 Qwen 的 FC 支持有严重 bug。 ├── 已有大量 Python 工具脚本不想重写且对延迟极度敏感 500ms→ 选 LangChain Tools 自定义同步调用 │ │ 理由LangChain Tools 封装成本最低同步调用无网络开销。 │ └── 注意return_directTrue绕过模型二次加工直接把脚本 stdout 返回。 └── 要构建一个开放平台让第三方开发者能轻松接入自己的工具 → 选 MCP Tools mcp-server │ 理由MCP 协议文档清晰mcp-server 提供了 CLI 工具mcp register和 Web UI第三方接入门槛最低。 └── 注意必须提供详细的 MCP 工具开发指南和沙箱环境降低学习成本。5.2 从零到上线的 7 天落地路线图这是我们给新团队的标准节奏每天聚焦一个目标确保可控交付Day 1环境与基线验证搭建开发机安装ollama和langchain。用ollama run qwen2.5:7b启动模型用最简tools测试 Function Calling 是否工作。目标看到{name: ..., arguments: ...}。Day 2封装第一个核心工具选一个业务中最简单、最确定的工具如“查当前时间”。用 LangChaintool封装写好args_schema和description。在 Jupyter Notebook 中手动构造messages和tools调用模型验证它能正确调用。Day 3构建最小 Agent 骨架用create_openai_tools_agent或create_qwen_tools_agent创建 Agent。写一个AgentExecutor让它能接收用户输入调用工具返回结果。目标在命令行里输入“现在几点”得到“现在是北京时间 2024 年 5 月 10 日 14:30”。Day 4引入 MCP解耦工具在本地启动mcp-server注册 Day 2 封