工具对于Agent的作用不言而喻。虽然不同的Agent框架针对工具具有不同的定义和注册方式,但是工具的大部分描述信息是为LLM提供的,因为最终决定调用哪个工具决定于LLM针对推理任务的理解和当前可用工具的描述,所以DSH针对工具的定义也不会有什么太特别的地方。如果硬要说特别的化,那就是源于DSH建立在Cordis之上,需要以插件的形式调用相应的服务来注册工具。这篇文章我们将通过一系列的实例来体现工具定义和注册的编程模式。
1. 在自定义插件中定义和注册工具
为了便于Debug,我们依然采用源码的方式启动DSH。我们在根目录上创建一个名为./explor的目录,并在其中定义插件和对应的补丁文件,具体的目录结构如下。我们在DeepSeek Harness深度拆解-06:在自定义插件中体验LLM的动态路由就是采用这种方式来体现LLM的动态路由机制的。
./explor ├── src | └── tool_explor.ts ├── cordis.patch.yml ├── package.json └── tsconfig.json工具的定义和注册都体现在./explor/src/tool_explor.ts这个.ts文件中。如代码所示,我们具体注册的是一个通过ToolDefinition接口表示的工具定义,该接口基于与工具相关的大部分类型都由@deepseek-ai/dsh-tools这个包提供。我们按照此接口定义了一个用来根据指定城市提供天气信息的工具,并利用字段name、description和parameters指定了工具的名称、描述和输入参数的JSON Schema。output字段利用schema提供工具输出对象的结构,render提供了一个函数将工具函数返回的对象转换成LLM能够识别的内容。
import{Context}from'@deepseek-ai/cordis'import{ToolDefinition}from'@deepseek-ai/dsh-tools'exportconstname="tool_explor"exportasyncfunctionapply(ctx:Context){consttool:ToolDefinition={name:'get_weather',description:"Get the specified city's weather information.",parameters:{type:'object',properties:{city:{type:'string',description:'The city to look up weather for.'},},required:['city'],},output:{schema:{type:'string'},render:(_args,value:string)=>[{type:'text',text:value}],},asyncexecute(args:any,_){return`It is 20 °C raining in${args.city}right now.`},}ctx.tools.register(tool)}至于工具在本地的执行逻辑,则直接定义在execute字段对应的函数中。作为模拟,我们返回一个固定的天气信息。我们定义的这个ToolDefinition对象最终需要利用ctx.tools返回的ToolRuntime对象的register方法进行注册。顾名思义,作为工具执行的运行时,ToolRuntime是与工具相关的最核心的类型,我们将在后续的文章对它进行系统介绍。针对上面定义的插件,我们修改了对应的补丁文件./explor/cordis.patch.yml。
-insert:-id:tool_explorname:'E:\dsh\deepseek-harness\explor\src\tool_explor.ts'inject:['tools']接下来我们以源码的形式(将源文件apps/cli/src/bin.ts作为启动程序)以headlessProfile启动DSH。我们附件了上面这个补丁文件,并提出我们的问题(根据苏州目前的天气,给出适合的着装建议)。输出不仅给出了最终的答案,还给出了它的推理过程。不论是最终答案,还是推理流程,都体现了针对get_weather工具的调用。
E:\dsh\deepseek-harness>npx tsx apps/cli/src/bin.ts --profile headless --patch ./explor/cordis.patch.yml 根据苏州目前的天气,给出适合的着装建议 npm notice run @deepseek-ai/dsh-root@0.1.5-alpha.2 npx npm notice run tsx apps/cli/src/bin.ts --profile headless --patch ./explor/cordis.patch.yml 根据苏州目前的天气,给出适合的着装建议 dsh: reasoning: The user is asking about current weather in Suzhou and clothing recommendations. I have a `get_weather` tool available. Let me use it to get Suzhou's weather. This is a simple task. Let me call get_weather for Suzhou. dsh: reasoning: The weather is 20°C and raining in Suzhou. Let me give clothing recommendations. 根据苏州当前天气(**20°C,正在下雨**),给你以下着装建议: ## 🌧️ 苏州当前着装建议(20°C 雨天) **核心原则:防雨 + 舒适保暖,叠穿更灵活** ### 上装 - 一件**长袖薄针织衫 / 卫衣 / 薄款毛衣**即可,20°C 体感偏舒适但雨天会有凉意 - 怕冷的话可加一件**薄外套(风衣、牛仔外套、针织开衫)**,方便随时穿脱 ### 外层(重点) - 务必带**防水的风衣 / 冲锋衣**,或**雨衣 / 雨伞**,下雨天这是刚需 ### 下装 - **长裤**(牛仔裤、休闲裤、西装裤都合适) - 不建议短裤,雨天偏凉且容易淋湿 ### 鞋子 - 选择**防水或易干的鞋**(皮鞋、皮面运动鞋),避免帆布鞋、网面鞋容易渗水 - 条件允许可备一双换穿 ### 配饰 - 一把**折叠伞**随身携带 - 可选一顶帽子或轻便围巾,防风防潮 --- **小结**:20°C 的雨天属于"不冷不热但潮湿"的天气,**叠穿 + 防水外层 + 防滑/防水鞋**是最稳妥的组合。出门记得带伞!☔ 如果需要更详细的信息(比如未来几小时降雨趋势),可以告诉我。2. 调用defineTool函数定义工具
虽然工具定义以ToolDefinition接口的方式定义,但是按照这个结构自行构建工具还是比较繁琐,所以@deepseek-ai/dsh-tools还提供了一个专门用来定义工具的defineTool函数,使用它可用是工具定义稍微简单一点。比如上面这个get_weather工具就可用改用如下的方式利用defineTool函数来定义。
import{Context}from'@deepseek-ai/cordis'import{defineTool}from'@deepseek-ai/dsh-tools'exportconstname="tool_explor"exportasyncfunctionapply(ctx:Context){consttool=defineTool({name:'get_weather',description:"Get the specified city's weather information.",parameters:{city:{type:'string',description:'The city to look up weather for.'},},output:{schema:{type:'string'},render:(_args,value:string)=>[{type:'text',text:value}],},asyncexecute(args:any,_){return`It is 20 °C raining in${args.city}right now.`}})ctx.tools.register(tool)}3. 查看发送给LLM的工具信息
之所以需要利用ToolDefinition接口来规范工具定义,源于LLM对工具描述的要求。我们可用按照如下的方式通过监听llm/stream事件,看看我们注册的工具最终以何种形态发送给LLM。
import{Context}from'@deepseek-ai/cordis'import{defineTool}from'@deepseek-ai/dsh-tools'import{GenerateOptions}from'@deepseek-ai/dsh-llm'exportconstname="tool_explor"exportasyncfunctionapply(ctx:Context){...ctx.tools.register(tool)ctx.on("llm/stream",(options:GenerateOptions,next)=>{consttool=options.tools?.find(t=>t.name=="get_weather")if(tool){console.log(`'get_weather' tool in LLM (${options.provider}/${options.model}) request`)console.log(JSON.stringify(tool,null,2))}returnnext()})}按照与上面一致的方式启动DSH,会得到如下的输出结果。可用看出整个执行流程两次调用LLM,并且都携带了针对注册工具的信息,并且只包含Schema相关的信息,即名称、描述和参数的数据结构。需要说明的是,不同的LLM对注册工具的描述并不一致,这样也是LLM适配器的一项核心工作。输入也表明了采用的模型是deepseek-official/deepseek-v4-pro。
E:\dsh\deepseek-harness>npx tsx apps/cli/src/bin.ts --profile headless --patch ./explor/cordis.patch.yml 根据苏州目前的天气,给出适合的着装建议 npm notice run @deepseek-ai/dsh-root@0.1.5-alpha.2 npx npm notice run tsx apps/cli/src/bin.ts --profile headless --patch ./explor/cordis.patch.yml 根据苏州目前的天气,给出适合的着装建议 'get_weather' tool in LLM request { "name": "get_weather", "description": "Get the specified city's weather information.", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "The city to look up weather for." } } } } dsh: reasoning: The user is asking me to give clothing advice based on the current weather in Suzhou. I have a `get_weather` tool. Let me get the weather for Suzhou. 'get_weather' tool in LLM request { "name": "get_weather", "description": "Get the specified city's weather information.", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "The city to look up weather for." } } } } dsh: reasoning: The weather is 20°C and raining in Suzhou. Let me give appropriate clothing advice. 苏州现在气温 **20°C**,正在下雨。根据这个天气情况,建议如下着装: ## 👕 穿衣建议(20°C 雨天) **上衣** - 一件长袖衬衫、薄款针织衫或卫衣即可,20°C 不会太冷,单穿偏薄长袖正合适。 - 也可选择「短袖内搭 + 薄外套」的组合,方便早晚或雨停后调节。 **外套(关键)** - 一定要带**防水/防风外套**,比如冲锋衣、轻便雨衣或带帽子的防水夹克。 - 普通棉质外套不防水,淋湿后会又冷又重,不建议作为外层。 **下装** - 长裤(牛仔裤、休闲裤、运动裤)即可,不需要加秋裤。 **鞋袜** - 建议穿**防滑、防水**的鞋,比如防水运动鞋、雨鞋或皮面鞋,避免网面运动鞋。 - 袜子选吸湿透气的,可多备一双以防鞋袜被打湿。 ## ☂️ 出行提醒 - 带好雨伞,雨天地面湿滑,步行注意安全。 - 如果骑电动车/步行时间较长,可考虑戴帽子或穿连帽外套。 - 20°C 加雨水,体感会偏凉一点,早晚出门可以多搭一件薄外套。 **一句话总结**:薄长袖 + 防水外套 + 长裤 + 防滑鞋,出门务必带伞。☔4. 利用约束控制工具的可见性
默认情况下所有注册的工具都会发送给LLM,但是我们知道发送太多与当前任务完全不相关的工具列表会造成负面的影响,此时我们可用在Agent的范围内(这里的范围指的就是DeepSeek Harness深度拆解-09:基于Scope的隔离机制是如何实现的?介绍的Scope)调用ToolRuntime的restrict方法允许或者拒绝相应的工具。比如在如下的演示程序中,我们通过注册agent/created事件,得到当前执行的Agent,并在它提供的Context中以上述的方式禁用我们注册的get_weather工具。
import{Context}from'@deepseek-ai/cordis'import{defineTool}from'@deepseek-ai/dsh-tools'import{GenerateOptions}from'@deepseek-ai/dsh-llm'import'@deepseek-ai/dsh-agent'exportconstname="tool_explor"exportasyncfunctionapply(ctx:Context){...ctx.on('agent/created',(payload)=>{payload.agent.ctx.tools.restrict({deny:["get_weather"]})})}再次按照相同的方式启动DSH,注册的get_weather工具将不会发送给LLM。通过LLM的推理可用看出,它会启动一个web search任务访问和风天气 · 苏州得到真实的天气,并最终给出着装建议。
E:\dsh\deepseek-harness>npx tsx apps/cli/src/bin.ts --profile headless --patch ./explor/cordis.patch.yml 根据苏州目前的天气,给出适合的着装建议 npm notice run @deepseek-ai/dsh-root@0.1.5-alpha.2 npx npm notice run tsx apps/cli/src/bin.ts --profile headless --patch ./explor/cordis.patch.yml 根据苏州目前的天气,给出适合的着装建议 dsh: reasoning: The user is asking for clothing suggestions based on current weather in Suzhou. This is a simple web search task. I need to find current weather in Suzhou and give clothing advice. Let me search for current weather in Suzhou. dsh: reasoning: Let me fetch a weather page to get current weather in Suzhou. Let me fetch the qweather or weather.com page. dsh: reasoning: I have the qweather data which is useful. The date shown is 2026-09-22 (this is a simulated/snapshot date, but I'll use what's available). Current conditions: 25°C, fog (雾), AQI 优 (excellent), 东北风 2级, humidity 80%, UV 中等, feels like 27°, today: 晴白天 23-29°C. Tomorrow 24-29°C, Thursday 24-30°C, Friday 25-31°C, etc. Note the date is 2026-09-22 in the fetched content. This is likely the harness's simulated current date. I should present based on the fetched weather data. Current: 25°C, foggy, humidity 80%, feels 27°. Today high 29°, low 23°, sunny during day, cloudy at night. Northeast wind level 2. UV medium. AQI excellent. Clothing advice for Suzhou: late September, warm but humid, temp 23-29°C. Recommend lightweight breathable clothing, short sleeves, thin long sleeves, thin pants. Morning fog/humidity 80% means it may feel a bit muggy. Evening cooler ~23°C, bring a light jacket/cardigan. UV medium, sun protection. Let me give concise clothing advice in Chinese, citing sources. 根据我查到的苏州当前天气数据,给出以下着装建议。 ## 苏州当前天气概览 - **气温**:当前 25℃,体感 27℃;今天白天 23~29℃(晴,夜间转多云) - **天气现象**:雾,相对湿度 80% - **风力**:东北风 2 级 - **紫外线**:中等 - **空气质量**:优(AQI 25,PM2.5 25) - 未来几日气温走高:周三至周五最高 29~31℃,最低 24~25℃ 数据来源:[和风天气 · 苏州](https://www.qweather.com/weather/suzhou-101190401.html) ## 着装建议 **核心思路:夏末秋初,白天偏热、早晚微凉、湿度大,主打轻薄透气 + 一件可脱的外搭。** 1. **上身**:短袖 T 恤、棉麻衬衫、薄款 Polo 衫即可;怕晒或通勤可叠穿一件**薄款长袖衬衫或防晒衣**。 2. **下身**:薄长裤(休闲裤、薄牛仔裤、阔腿裤)或及膝短裤都合适。 3. **早晚保暖**:早间有雾、湿度达 80%,体感偏闷;夜间最低约 23℃ 并转多云,建议随身带一件**薄外套、针织开衫或连帽卫衣**,早晚和空调房内穿脱方便。 4. **鞋袜**:透气运动鞋、帆布鞋或船鞋,配薄棉袜,适合长时间走动和苏州园林、古街游览。 5. **防晒与配件**:紫外线中等,白天户外建议戴**遮阳帽、太阳镜**并涂防晒霜;湿度高,可备把**晴雨两用伞**(应对晨雾和可能的阵性降雨)。 **一句话总结**:白天轻装上阵(短袖/薄长袖 + 薄长裤),早晚加件薄外套即可,兼顾防晒和透气。 需要我进一步结合某一天(比如周末)的具体气温再细化吗?5. 工具执行前的最后一道关卡
ToolRuntime还提供了一个guard方法,我们将它视为执行工具的最后一道门卫。具体来说,我们可以利用这个方法注册一个回调,当Agent在执行工具函数之前会先调用此回调。如果此回调返回undefined,意味着门卫放行,继续完成目标工具的执行,否则返回的字符串将会作为本次工具调用的结果并反馈给LLM。以下面的演示程序为例,我们在agent/created事件的处理函数中改调guard方法,注册的回调会根据作为参数的ToolExecution得到工具名称,如果是get_weather,我们直接返回这段文字:'get_weather' tool provides fake weather information only.
import{Context}from'@deepseek-ai/cordis'import{defineTool}from'@deepseek-ai/dsh-tools'import{GenerateOptions}from'@deepseek-ai/dsh-llm'import'@deepseek-ai/dsh-agent'exportconstname="tool_explor"exportasyncfunctionapply(ctx:Context){...ctx.on('agent/created',(payload)=>{payload.agent.ctx.tools.guard(toolExecution=>{if(toolExecution.name=="get_weather"){return"'get_weather' tool provides fake weather information only."}returnundefined})})}我们依然采用相同的方式启动DSH,此时会得到如下输出:
E:\dsh\deepseek-harness>npx tsx apps/cli/src/bin.ts --profile headless --patch ./explor/cordis.patch.yml 根据苏州目前的天气,给出适合的着装建议 npm notice run @deepseek-ai/dsh-root@0.1.5-alpha.2 npx npm notice run tsx apps/cli/src/bin.ts --profile headless --patch ./explor/cordis.patch.yml 根据苏州目前的天气,给出适合的着装建议 'get_weather' tool in LLM (deepseek-official/deepseek-v4-pro) request { "name": "get_weather", "description": "Get the specified city's weather information.", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "The city to look up weather for." } } } } dsh: reasoning: The user asks for clothing advice based on the current weather in Suzhou. I need to get the weather for Suzhou. I have a get_weather tool. Let me use it. 'get_weather' tool in LLM (deepseek-official/deepseek-v4-pro) request { "name": "get_weather", "description": "Get the specified city's weather information.", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "The city to look up weather for." } } } } dsh: reasoning: The get_weather tool provides fake weather information only. So I cannot use it for real weather. I should use web search to get current weather for Suzhou. Let me search the web for the current weather in Suzhou. 'get_weather' tool in LLM (deepseek-official/deepseek-v4-pro) request { "name": "get_weather", "description": "Get the specified city's weather information.", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "The city to look up weather for." } } } } dsh: reasoning: The search results don't give me the actual temperature. I need to fetch a page to get current weather. Let me fetch the qweather or weather.com page. Let me try to fetch a page that gives concrete data. Let me fetch the moji or qweather page. Actually, let me fetch the qweather page for Suzhou to get current conditions. 'get_weather' tool in LLM (deepseek-official/deepseek-v4-pro) request { "name": "get_weather", "description": "Get the specified city's weather information.", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "The city to look up weather for." } } } } dsh: reasoning: I got the weather data from qweather. Let me extract the key facts: - 苏州, 江苏, 2026-09-22, 星期二 - 当前 25°C, 阵雨 (shower) - 西北风 2级 - 湿度 81% - 体感温度 26° - 今天白天晴,夜晚多云 - 穿衣指数: 热 - 舒适度指数: 较舒适 - 紫外线: 中等 - 今日 29°~23°, 明天 29°~24° So I can give dressing advice based on this. Current: 25°C, 阵雨, humidity 81%, feels like 26°, daytime high 29°, low 23°. Advice: light summer clothing, short sleeves, thin shirts. Bring umbrella/rain gear due to showers. Maybe light jacket for evening (23°). Breathable fabrics due to 81% humidity. Sun protection for moderate UV. Let me write a concise Chinese answer with citation. 根据和风天气的最新数据,苏州当前(2026-09-22)天气为**阵雨,25℃,体感 26℃**,西北风 2 级,相对湿度 81%,今天白天晴、夜间多云,全天 23℃~29℃。穿衣指数为「热」,舒适度「较舒适」。 ## 着装建议 **白天(25~29℃,体感偏热)** - 以**轻薄透气**为主:短袖 T 恤、薄衬衫、薄连衣裙、短裤或轻薄长裤。 - 面料选**纯棉、亚麻、速干**类,避免不透气的化纤,应对 81% 的高湿度。 **早晚(23℃ 左右)** - 温差约 6℃,早晚略凉,可备一件**薄款长袖、防晒衫或薄外套**,进出空调房也能挡一挡。 **针对阵雨** - 务必带**伞或轻便雨具**,穿**防水或快干**的鞋,避免帆布鞋湿了难受。 **防晒** - 紫外线中等,白天长时间户外可涂防晒、戴遮阳帽。 一句话总结:**短袖 + 薄长裤/短裤为主,随身带伞和一件薄外套**,既应对阵雨又兼顾早晚温差。 来源:[和风天气 · 苏州市](https://www.qweather.com/weather/suzhou-101190401.html)结合携带推理的输出,我们可以大致看出内部的执行流程:
get_weather工具相关信息被发送给LLM,并被选择;- Agent在执行此工具前会先执行
guard方法注册的回调,并返回一段文字。所有get_weather工具对应的函数并不会被执行,前者返回的字符串将作为它的结果反馈给LLM; - LLM得到执行结果后,决定采用
web search方式获取天气,但是得到信息不包含气温,决定访问和风天气 · 苏州市获取完整天气信息; - 在得到完整天气信息后,作出最终的着装建议。
这篇文章涉及到定义和注册工具相关的一些重要的接口、类型和函数,比如ToolDefinition、ToolRestriction和ToolGuard,以及defineTool函数,在DeepSeek Harness深度拆解-10:深入探讨工具定义、约束和执行防御