)
在前三篇文章中,我们实现了 AI 组件渲染、流式 JSON 解析和多工具并发。然而,所有这些交互依然局限在模型的离线知识库中…你是否遇到过这样的场景:用户问:“今天北京天气怎么样?”AI 回答:“根据我的训练数据,北京春季平均气温 10-20°C…”用户心里想:“我要的是今天的实时天气,不是历史数据!”今天我们来解决这个问题,从离线知识库升级到实时感知! 回顾问题:离线知识库的致命缺陷在前三篇文章中,我们实现了完美的 AI 组件渲染系统,但存在一些致命问题:❌ 数据时效性缺失用户: 今天北京天气怎么样? ↓ AI 搜索训练数据... ↓ [AI 回答] 根据我的训练数据,北京春季平均气温 10-20°C... ↓ 用户: 我要的是今天!不是历史数据!问题:AI 的知识截止于训练时间,无法获取实时信息。❌ 幻觉风险用户: 马斯克最近发推了吗? ↓ AI 可能回答: 马斯克昨天发推说... ↓ 实际上:马斯克根本没发这条推! ↓ 用户: 被误导 ❌问题:AI 会幻觉出不存在的信息,误导用户。 网页端 vs SDK:本质区别深度解析很多开发者习惯了在网页端使用 DeepSeek 的联网功能,但在开发 AI 应用时,使用import { ChatDeepSeek } from langchain/deepseek拥有完全不同的维度! 对比表格维度网页端 DeepSeekLangChain SDK决策透明度❌ 黑盒,看不到思考过程✅ 可观察完整逻辑链路交互闭环❌ 搜索结果只能看✅ 数据可直接注入下游组件环境控制❌ 系统自动注入,开发者不可控✅ 开发者主动注入,完全可控数据复用❌ 搜索结果无法传递✅ 可传递给后续工具自定义控制❌ 受限于网页功能✅ 完全可控的执行逻辑 深度解析:三大核心差异1️⃣ 决策透明度:黑盒 vs 白盒网页端(黑盒):用户输入 ↓ [黑盒] AI 内部处理(看不见) ↓ 输出答案SDK(白盒):用户输入 ↓ AI 思考(可观察) → 决定调用工具 ↓ 工具执行(可拦截) → 返回结果 ↓ AI 分析结果 → 生成最终答案 ↓ 输出答案 完整链路在 SDK 中,我们可以看到 AI 从思考到决定调用工具的完整逻辑链路,这对于调试和优化至关重要。2️⃣ 交互闭环:只读 vs 可写网页端(只读):AI 搜索 → 显示结果 → 用户只能看 ↓ 无法将搜索到的数据(如经纬度)传递给其他组件SDK(可写):AI 搜索 → 获取经纬度 → 注入地图组件 → 渲染地图 ↓ 数据在工具间流动,形成完整的交互闭环类比:网页端像看报纸:只能获取信息,无法利用SDK 像指挥官:获取信息后,可以指挥后续行动3️⃣ 环境感应:静态 vs 动态网页端(静态):// 系统自动注入当前时间,但开发者无法控制// 开发者无法自定义注入的上下文格式// 用户位置等信息由浏览器获取,开发者难以精确控制SDK(动态):// 开发者可以主动、精确地注入上下文constcurrentTimenewDate().toLocaleString(zh-CN,{year:numeric,month:long,day:numeric,weekday:long,hour:2-digit,minute:2-digit})// 可以动态注入用户地理位置constuserLocation{lat:39.9,lng:116.4,city:北京,country:中国}// 可以注入业务相关的上下文constbusinessContext{userRole:VIP用户,accountStatus:active}通过代码,我们可以动态告知模型当前的精确时间、用户地理位置等上下文,这是静态 Web 界面难以实现的自动化体验。 实时搜索集成:引入 TavilySearch要让 AI 能够检索实时天气或新闻,我们需要引入TavilySearch。它是专为 LLM 设计的搜索引擎,能够过滤掉网页广告,只返回干净的文本摘要。 工具定义import{TavilySearch}fromlangchain/community/tools/tavily_searchconstrawTools[// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 实时搜索工具// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━newTavilySearch({tavilyApiKey:TAVILY_API_KEY,// 关键:动态注入当前日期,防止 AI 获取到过期的快照description:【必备工具】用于获取实时信息。搜索时请务必带上当前日期${newDate().toLocaleDateString()}以确保获取的是最新结果。}),// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// UI 渲染工具// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━tool(async(args)地图路线已生成,{name:render_map_itinerary,schema:MapItinerarySchema,description:【必须调用】当用户询问地点、路线规划时使用。}),] 为什么不需要tool()包裹?这是一个经常被问到的问题!TavilySearch:// ❌ 不需要 tool() 包裹newTavilySearch({...})自定义渲染函数:// ✅ 必须用 tool() 包裹tool(async(args)地图路线已生成,{...})原因分析:类型类继承内置能力是否需要 tool()TavilySearch继承LangChain BaseTool已有name、description、schema❌ 不需要自定义函数纯 JS 函数无任何元数据✅ 必须用技术细节:TavilySearch本身就是一个已经扩展了LangChain BaseTool基类的类实例它内部已经内置了模型所需的name、description和输入schema我们自定义的渲染函数只是一个纯 JS 函数,必须通过tool()装饰器进行包装,才能被识别为合法的 Agent 工具️ 规则前置:用 System Prompt 强制模型认清现实要让 AI 在不知道答案时主动搜索,必须通过System Prompt建立强硬的执行准则。 强约束系统提示词constSYSTEM_PROMPT当前时间:${newDate().toLocaleString()}## 核心准则 1. 禁止幻觉:你本身不具备联网能力,严禁凭空编造气温、新闻或地点坐标。 2. 搜索前置:涉及任何实时性、位置详情的问题,必须先调用 tavily_search_results_json。 3. 数据去噪:如果搜索结果日期陈旧如当前是3月,搜到的是1月数据,必须忽略该结果并告知用户无法获取实时数据。在实际测试中,我们发现搜索工具可能会返回过时的网页。例如:当前时间: 2026年3月12日 搜索结果: 2026年1月15日的数据 ← 过期了! ❌ 优化的反幻觉策略策略实现方式作用动态上下文注入每次调用时实时计算new Date()确保 AI 知道当前时间置信度校准在工具描述中明确日期偏差限制让 AI 主动过滤过期数据结果导向AI 对比数据逻辑,不合理则报错避免渲染错误数据具体实现:// 1. 动态上下文注入constSYSTEM_PROMPT当前时间:${newDate().toLocaleString()}// 2. 置信度校准newTavilySearch({description:如果搜索结果中的日期与当前日期偏差超过 3 天,请视为失效数据。})// 3. 结果导向if(result.temperature-50||result.temperature60){thrownewError(温度数据异常,可能是幻觉)}⚙️ 核心逻辑:invoke 深度解析在 Agent 架构中,最优雅的设计是多态执行。无论 AI 决定是去搜索还是去渲染,我们的结算代码应该是一致的。 完整代码实现// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 结算阶段:真正执行工具并回传数据// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━if(toolCalls.length0){consttoolMessagesawaitPromise.all(toolCalls.map(async(call){constidcall.id!;// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 第一步:动态寻找工具实例// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━consttoolInstancerawTools.find(tt.namecall.name);letresult;try{if(toolInstance){// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 【核心点】调用 invoke 触发执行// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// - 对于 Tavily:invoke 内部运行的是 _call 方法,执行真正的 HTTP 请求// - 对于 自定义 tool:invoke 会自动调用你传入的异步函数// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━resultawaittoolInstance.invoke(call.args);}else{resultError: Tool not found;}}catch(error){result执行失败:${error.message};}// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 第二步:更新插件状态(如果是 UI 渲染工具)// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━finalPlugins[id]{...messages[messages.length-1]?.pluginMap?.[id],isPartial:false,argsParsed:call.args,};// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 第三步:将真实的执行结果封装为 ToolMessage// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━returnnewToolMessage({tool_call_id:id,content:typeofresultstring?result:JSON.stringify(result),});}));// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━// 第四步:将结果推入上下文,开启下一轮迭代// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━currentMessages.push(...toolMessages);}优势:这种设计让我们的 while 循环变得极其简洁:模型说要用什么工具代码就 invoke 什么工具结果再喂给模型完全统一的执行逻辑! 核心技术点总结实时搜索数据时效性缺失new TavilySearch({ ... })|动态上下文注入AI 不知道当前环境new Date().toLocaleString()|多态 invoke统一执行逻辑toolInstance.invoke(call.args)| 完整闭环架构感知(AI 决策) → 检索(搜索工具) → 校验(反幻觉) → 展现(UI 组件) ↓ ↓ ↓ ↓ 识别意图 获取实时数据 验证数据合理性 渲染可视化组件 常见问题Q1: 为什么不直接用网页端的 DeepSeek?A: 网页端适合个人使用,但 SDK 提供了:完整的控制权数据复用能力自定义执行逻辑更高的可定制性Q2: TavilySearch 是免费的吗?A: Tavily 提供免费额度(每月 1000 次搜索),对于原型开发足够。生产环境可能需要付费升级。Q3: 如何确保搜索结果的时效性?A: 三重保障:动态注入当前日期System Prompt 强制 AI 检查日期代码层校验数据合理性Q4: 搜索失败怎么办?A: 优雅降级:try{resultawaittoolInstance.invoke(call.args);}catch(error){result搜索失败,请稍后重试;}Q5: 可以使用其他搜索引擎吗?A: 可以!LangChain 支持多个搜索引擎:TavilySearch(推荐,专为 LLM 设计)SerpAPIGoogleSearchAPI自定义搜索引擎 推荐资源Tavily Search 文档LangChain ToolsLangChain AgentsDeepSeek API 文档 总结至此我们的 AI 助手正式拥有了感官!通过toolInstance.invoke的统一分发,我们构建了一个感知 → 检索 → 校验 → 展现的完整闭环。TavilySearch- 专为 LLM 设计的实时搜索引擎动态上下文注入- 让 AI 知道当前环境多态 invoke- 统一的执行逻辑反幻觉策略- 确保数据准确性现在才算真正赋予了 AI“感官”!AI 不再是一个只能背诵知识的书呆子,它是一个能够感知现实世界的智能助手。