LangChain 1.1 的 Agent 跑 Claude Skills 动态加载,Base URL 填 TaoToken 一、当 50 个工具塞进上下文Agent 开始选错工具如果你正在用 LangChain 1.1 的create_agent搭一个多工具 Agent大概率遇到过这种场景工具列表越加越多从最初的 5 个涨到 30 个、50 个然后模型开始犯迷糊——明明该调用search_docs它偏偏去调run_sql明明只需要一个轻量查询它却把整条工具链都试一遍。更直接的问题是 token每个工具的描述动辄两三百 token50 个工具光描述就上万 token还没开始干活上下文预算已经烧掉一大半。Claude Skills 给出的解法是渐进式披露Progressive Disclosure不把所有工具一次性摊给模型而是根据当前任务状态只暴露此刻相关的那几个。LangChain 1.1 新增的 Middleware API 正好提供了落地这个思路的钩子——通过继承AgentMiddleware、重写wrap_model_call在每次模型调用前用request.override(toolsfiltered_tools)动态替换工具列表再用state_schema追踪skills_loaded状态。但原文只讲到了导入和核心概念真正让create_agent发出请求这一步是空的模型通道怎么接、Base URL 填哪里、Key 放哪长会话里怎么保证每次调用都走通。这篇就把这块补上——用 TaoTokenhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 提供 Key 和 Base URL动态过滤逻辑仍然写在你的wrap_model_call里两者互不干扰。二、TaoToken 前置只负责模型通道不碰你的过滤逻辑先把职责边界说清楚避免混淆TaoToken 负责的提供一个兼容 OpenAI/Anthropic 风格的 API 入口你在这里注册账号、创建 Key把create_agent所用模型的 Base URL 指向https://taotoken.net/apiKey 写进环境变量。它解决的是模型请求往哪发、用什么凭证发的问题。你自己负责的AgentMiddleware子类里的wrap_model_call、state_schema里的skills_loaded追踪、request.override(toolsfiltered_tools)的过滤条件。这些逻辑跟模型通道无关换任何 Base URL 都不用改。这样拆分的好处是动态工具过滤是纯 Agent 层逻辑模型通道是纯接入层配置两边解耦。你调试过滤逻辑时不用管 Key排查请求失败时也不用翻 Middleware 代码。操作路径打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册账号进入控制台创建 API Keyhttps://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 把 Key 写进环境变量Base URL 统一用https://taotoken.net/api。三、可复制配置Base URL、环境变量与 create_agent 接线3.1 环境变量在项目根目录的.env里写TAOTOKEN_API_KEYYOUR_API_KEY TAOTOKEN_BASE_URLhttps://taotoken.net/api注意 Base URL 不要带 UTM 参数API 地址就是https://taotoken.net/api。3.2 初始化模型并接入 create_agentLangChain 1.1 里create_agent需要一个 chat model 实例。以 OpenAI 兼容接口为例import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain.agents import create_agent load_dotenv(overrideTrue) model ChatOpenAI( modelclaude-sonnet-4-5, # 按你实际可用的模型 ID 填 api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], temperature0, ) agent create_agent( modelmodel, toolsall_tools, # 全量工具过滤交给 Middleware middleware[SkillsFilterMiddleware()], state_schemaAgentState, )关键点toolsall_tools仍然传全量过滤发生在 Middleware 里而不是在create_agent这一层。这样create_agent的接线保持干净模型通道只认base_url和api_key。3.3 Middleware 与 state_schema 骨架from typing import List from typing_extensions import TypedDict from langchain.agents.middleware import AgentMiddleware, ModelRequest from langchain_core.tools import BaseTool class AgentState(TypedDict, totalFalse): skills_loaded: List[str] class SkillsFilterMiddleware(AgentMiddleware): def wrap_model_call(self, request: ModelRequest, handler): loaded request.state.get(skills_loaded, []) filtered_tools [ t for t in request.tools if self._tool_belongs_to_loaded_skill(t, loaded) ] return handler(request.override(toolsfiltered_tools)) def _tool_belongs_to_loaded_skill(self, tool: BaseTool, loaded: List[str]) - bool: skill_tag tool.metadata.get(skill) if tool.metadata else None return skill_tag in loadedskills_loaded由你的业务逻辑在对话过程中更新比如识别到用户意图后追加 skill 名Middleware 每次调用前读一次决定这次暴露哪些工具。四、验证请求观察每次调用实际暴露的工具数量配通之后别急着跑复杂任务先用一个最小多轮场景验证渐进式披露是否真的生效。4.1 加一行日志在wrap_model_call里临时打印print(f[skills_loaded]{loaded} - tools{len(filtered_tools)}/{len(request.tools)})4.2 跑一个两轮任务第一轮用户说帮我查一下文档里的配置项。此时skills_loaded为空或只含doc_search日志应显示暴露工具数远小于全量。第二轮用户说顺便把结果写进数据库。业务逻辑把db_write追加进skills_loaded日志里暴露工具数应增加但依然不是全量。如果两轮日志里的工具数量符合预期变化说明request.override(toolsfiltered_tools)生效了模型每次只看到相关工具。4.3 确认请求真的发出去了回到 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的控制台查看调用记录如果能看到对应时间点的请求且没有报 401/404说明 Base URL 和 Key 都接对了。这一步是很多人漏掉的——过滤逻辑对了但请求根本没发出去日志里只有本地打印。五、本篇常见错排查错误 1request.override(tools...)不生效模型仍看到全量工具。检查create_agent里是否把middleware传进去了以及wrap_model_call是否真的调用了handler(request.override(...))而不是直接handler(request)。另外确认request.tools是你期望的全量列表。错误 2request.state.get(skills_loaded)返回 None。state_schema里skills_loaded要用totalFalse的TypedDict并且确保业务逻辑在调用前已经写入。如果第一轮就是空用request.state.get(skills_loaded, [])兜底。错误 3请求 401 或 403。Key 没写进环境变量或者.env没被load_dotenv(overrideTrue)加载。确认TAOTOKEN_API_KEY的值是控制台里创建的那串不要带引号或空格。错误 4请求 404提示模型不存在。model字段填的模型 ID 跟账号可用列表不匹配。去模型对话页https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 确认可用模型 ID再回填。错误 5Base URL 写成了带 UTM 的地址。API 地址就是https://taotoken.net/api不要拼 UTM 参数否则可能路由异常。错误 6长会话里工具数量不变化。skills_loaded的更新逻辑没接上。Middleware 只负责读状态并过滤状态的写入要靠你的 Agent 业务代码。检查每轮对话后是否真的往skills_loaded里追加了 skill 名。六、语义一致 CTA动态工具过滤逻辑跑通后接下来要做的通常是两件事一是把 Key 管理规范化二是把长会话编码场景接上。需要创建或轮换 Key、查看接入文档进 API Keys 页https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 和接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。想先验证模型通道是否正常用模型对话页https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 发一条测试请求。长期跑编码类 Agent、需要稳定长会话通道看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。如果你用 Claude Code 作为 Agent 的运行环境模型通道配置写在settings.json的ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY里指向https://taotoken.net/api和你的 Key如果走 CLI可以用npm i -g taotoken/taotoken安装后用taotoken cc -k YOUR_API_KEY -u https://taotoken.net/api -m MODEL_ID启动。动态工具过滤那部分代码不用动它跟模型通道是两层。