尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

19、LangChain 前端:模式 => 工具调用

19、LangChain 前端:模式 => 工具调用 本文聚焦 Agent 工具调用的前端可视化方案提供 React/Vue/Svelte/Angular 全框架适配代码包含加载状态、错误处理及类型安全最佳实践。文章目录1. Tool Calling 工作原理2. 环境搭建useStream 配置2.1 类型定义TypeScript2.2 基础流式组件React 示例3. 核心类型ToolCallWithResult 详解类型定义字段说明4. 按消息过滤工具调用5. 定制化工具卡片实现5.1 工具卡片入口组件5.2 示例天气工具卡片WeatherCard5.3 示例计算器工具卡片CalculatorCard6. 加载/错误状态处理6.1 加载卡片LoadingCard6.2 错误卡片ErrorCard6.3 通用卡片GenericToolCard7. 类型安全工具参数校验7.1 工具定义后端/共享层7.2 前端类型推导8. 流式文本与工具调用联动关键特性示例效果9. 并发工具调用处理并发工具调用列表组件使用方式10. 生产环境最佳实践1. Tool Calling 工作原理LangGraph Agent 可调用外部工具天气 API、计算器、网页搜索、数据库查询等工具调用流程如下Agent 决策需要外部数据时会在 AI 消息中包含工具调用指令工具调用指令包含name工具名、args结构化参数、id唯一标识Agent 运行时执行工具返回结果封装为ToolMessageuseStream钩子将工具调用、执行状态、结果统一为toolCalls数组前端可直接渲染核心优势无需手动处理工具调用与结果的关联useStream已实现状态同步2. 环境搭建useStream 配置首先安装核心依赖以 npm 为例# 核心依赖npminstalllangchain/core langchain/react# 类型支持TypeScript 项目npminstall-Dtypes/langchain__core# 工具参数校验可选推荐npminstallzod2.1 类型定义TypeScriptimporttype{BaseMessage}fromlangchain/core/messages;// 匹配 Agent 状态结构的接口interfaceAgentState{messages:BaseMessage[];// 消息列表包含 AI 消息、人类消息、工具消息}2.2 基础流式组件React 示例import { useStream } from langchain/react; import { AIMessage } from langchain/core/messages; import { Message } from ./Message; // 后续实现的消息组件 import { ToolCallWithResult } from langchain/react/dist/types; // 工具调用类型 // Agent 服务地址替换为你的实际地址 const AGENT_URL http://localhost:2024; export function ToolCallingChat() { // 初始化流式连接指定工具调用 Agent ID const stream useStreamAgentState({ apiUrl: AGENT_URL, assistantId: tool_calling, // 替换为你的工具调用 Agent ID }); return ( div classNamechat-container max-w-3xl mx-auto p-4 {/* 渲染消息列表传递工具调用状态 */} {stream.messages.map((msg) ( Message key{msg.id} message{msg} toolCalls{stream.toolCalls as ToolCallWithResult[]} / ))} /div ); }3. 核心类型ToolCallWithResult 详解stream.toolCalls数组中的每个元素均为ToolCallWithResult类型包含工具调用全生命周期信息类型定义interfaceToolCallWithResult{call:{id:string;// 唯一标识与 AI 消息的 tool_calls.id 对应name:string;// 工具名如 get_weather、calculatorargs:Recordstring,unknown;// 工具参数结构化数据};result:ToolMessage|undefined;// 工具执行结果ToolMessage 类型state:pending|completed|error;// 执行状态}字段说明字段描述call.id唯一标识用于关联 AI 消息与工具调用结果call.name工具名称用于区分不同工具如 “get_weather” 对应天气查询工具call.args工具的结构化参数如天气查询的{ location: 北京 }result工具执行结果工具完成后为ToolMessage实例包含content字段state生命周期状态pending执行中、completed成功、error失败4. 按消息过滤工具调用一个 AI 消息可能触发多个工具调用需通过call.id关联消息与对应的工具调用确保工具卡片渲染在正确的消息下方import { AIMessage, ToolMessage } from langchain/core/messages; import { ToolCallWithResult } from langchain/react/dist/types; import { ToolCard } from ./ToolCard; // 后续实现的工具卡片组件 interface MessageProps { message: AIMessage | ToolMessage; toolCalls: ToolCallWithResult[]; } export function Message({ message, toolCalls }: MessageProps) { // 仅处理 AI 消息工具调用由 AI 触发 if (AIMessage.isInstance(message)) { // 过滤当前消息触发的工具调用通过 id 匹配 const messageToolCalls toolCalls.filter((tc) message.tool_calls?.some((t) t.id tc.call.id) ); return ( div classNamemessage-bubble mb-4 p-4 border rounded-lg bg-white shadow-sm {/* 渲染 AI 消息文本 */} p classNametext-gray-800 mb-3{message.content || 正在调用工具...}{ }/p {/* 渲染当前消息对应的工具卡片 */} div classNametool-cards space-y-3 {messageToolCalls.map((tc) ( ToolCard key{tc.call.id} toolCall{tc} / ))} /div /div ); } // 人类消息直接渲染文本 return ( div classNamemessage-bubble mb-4 p-4 border rounded-lg bg-blue-50 p classNametext-blue-800{message.content}/p /div ); }5. 定制化工具卡片实现避免渲染原始 JSON为不同工具设计专属 UI 卡片通过call.name动态匹配对应的卡片组件5.1 工具卡片入口组件import { ToolCallWithResult } from langchain/react/dist/types; import { WeatherCard } from ./WeatherCard; import { CalculatorCard } from ./CalculatorCard; import { SearchCard } from ./SearchCard; import { LoadingCard } from ./LoadingCard; import { ErrorCard } from ./ErrorCard; import { GenericToolCard } from ./GenericToolCard; interface ToolCardProps { toolCall: ToolCallWithResult; } export function ToolCard({ toolCall }: ToolCardProps) { // 执行中状态渲染加载卡片 if (toolCall.state pending) { return LoadingCard name{toolCall.call.name} /; } // 执行失败状态渲染错误卡片 if (toolCall.state error) { return ErrorCard name{toolCall.call.name} error{toolCall.result} /; } // 执行成功根据工具名渲染对应卡片 switch (toolCall.call.name) { case get_weather: return WeatherCard args{toolCall.call.args} result{toolCall.result} /; case calculator: return CalculatorCard args{toolCall.call.args} result{toolCall.result} /; case web_search: return SearchCard args{toolCall.call.args} result{toolCall.result} /; // 未知工具渲染通用卡片展示 JSON 数据 default: return GenericToolCard toolCall{toolCall} /; } }5.2 示例天气工具卡片WeatherCardimport { ToolMessage } from langchain/core/messages; // 可使用图标库如 react-icons import { FaCloud, FaSun, FaRain } from react-icons/fa; interface WeatherCardProps { args: { location: string }; // 工具参数结构化 result: ToolMessage; // 工具执行结果 } export function WeatherCard({ args, result }: WeatherCardProps) { // 安全解析 JSON 结果避免解析失败崩溃 let weatherData: { temperature: number; condition: string } | null null; try { weatherData JSON.parse(result.content as string); } catch (err) { return div classNamep-3 border rounded text-red-500天气数据解析失败/div; } // 根据天气状况选择图标 const getWeatherIcon () { switch (weatherData?.condition.toLowerCase()) { case sunny: return FaSun classNametext-yellow-500 size{24} /; case rainy: return FaRain classNametext-blue-500 size{24} /; default: return FaCloud classNametext-gray-500 size{24} /; } }; return ( div classNamerounded-lg border p-4 bg-gradient-to-r from-sky-50 to-blue-50 div classNameflex items-center gap-3 mb-2 {getWeatherIcon()} h3 classNamefont-semibold text-lg{args.location} 天气/h3 /div div classNameflex items-baseline gap-2 span classNametext-3xl font-bold text-gray-800 {weatherData?.temperature}°C /span span classNametext-gray-600{weatherData?.condition}/span /div /div ); }5.3 示例计算器工具卡片CalculatorCardimport { ToolMessage } from langchain/core/messages; interface CalculatorCardProps { args: { expression: string }; // 计算器表达式如 100 20 * 3 result: ToolMessage; } export function CalculatorCard({ args, result }: CalculatorCardProps) { let calculationResult: { value: number } | null null; try { calculationResult JSON.parse(result.content as string); } catch (err) { return div classNamep-3 border rounded text-red-500计算结果解析失败/div; } return ( div classNamerounded-lg border p-4 bg-gradient-to-r from-gray-50 to-gray-100 h3 classNamefont-semibold mb-2计算器/h3 div classNametext-gray-700 mb-1表达式{args.expression}/div div classNametext-xl font-bold text-gray-900 结果{calculationResult?.value} /div /div ); }6. 加载/错误状态处理为提升用户体验必须处理pending执行中和error失败状态提供清晰的反馈6.1 加载卡片LoadingCardimport { FaSpinner } from react-icons/fa; interface LoadingCardProps { name: string; // 工具名 } export function LoadingCard({ name }: LoadingCardProps) { return ( div classNameflex items-center gap-2 rounded-lg border p-4 animate-pulse bg-gray-50 FaSpinner classNameanimate-spin text-blue-500 size{18} / span classNametext-gray-600正在执行 {name} 工具.../span /div ); }6.2 错误卡片ErrorCardimport { ToolMessage } from langchain/core/messages; import { FaExclamationCircle } from react-icons/fa; interface ErrorCardProps { name: string; error?: ToolMessage; // 错误信息可能为 undefined } export function ErrorCard({ name, error }: ErrorCardProps) { return ( div classNamerounded-lg border border-red-300 bg-red-50 p-4 div classNameflex items-center gap-2 mb-1 FaExclamationCircle classNametext-red-500 size{18} / h3 classNamefont-semibold text-red-700{name} 工具执行失败/h3 /div p classNametext-sm text-red-600 {error?.content ?? 未知错误请稍后重试} /p /div ); }6.3 通用卡片GenericToolCard为未知工具提供通用渲染方案展示原始参数和结果 collapsible 折叠面板import { useState } from react; import { ToolCallWithResult } from langchain/react/dist/types; import { FaChevronDown, FaChevronUp } from react-icons/fa; interface GenericToolCardProps { toolCall: ToolCallWithResult; } export function GenericToolCard({ toolCall }: GenericToolCardProps) { const [isExpanded, setIsExpanded] useState(false); return ( div classNamerounded-lg border p-4 bg-gray-50 div classNameflex items-center justify-between cursor-pointer onClick{() setIsExpanded(!isExpanded)} h3 classNamefont-semibold text-gray-800{toolCall.call.name} 工具/h3 {isExpanded ? FaChevronUp size{16} / : FaChevronDown size{16} /} /div {isExpanded ( div classNamemt-3 space-y-2 text-sm div span classNamefont-medium参数/span pre classNamemt-1 p-2 bg-white rounded border text-gray-700 overflow-x-auto {JSON.stringify(toolCall.call.args, null, 2)} /pre /div div span classNamefont-medium结果/span pre classNamemt-1 p-2 bg-white rounded border text-gray-700 overflow-x-auto {JSON.stringify(toolCall.result?.content, null, 2)} /pre /div /div )} /div ); }7. 类型安全工具参数校验使用zod定义工具参数 schema结合ToolCallFromTool类型实现前端参数类型安全7.1 工具定义后端/共享层import{tool}fromlangchain/core/tools;import{z}fromzod;// 定义天气工具参数 schemaconstweatherToolSchemaz.object({location:z.string().describe(城市名称如北京、上海),unit:z.optional(z.enum([celsius,fahrenheit]).describe(温度单位默认摄氏度))});// 定义天气工具exportconstgetWeathertool(async({location,unitcelsius}){// 调用天气 API 获取数据示例逻辑constresponseawaitfetch(https://api.weatherapi.com/v1/current.json?keyYOUR_KEYq${location});constdataawaitresponse.json();returnJSON.stringify({temperature:unitcelsius?data.current.temp_c:data.current.temp_f,condition:data.current.condition.text});},{name:get_weather,description:获取指定城市的实时天气,schema:weatherToolSchema// 关联参数 schema});7.2 前端类型推导import{ToolCallFromTool}fromlangchain/core/tools;import{getWeather}from./tools;// 从工具定义推导工具调用类型参数自动类型安全typeWeatherToolCallToolCallFromTooltypeofgetWeather;// 此时 WeatherToolCall.call.args 类型为// {// location: string;// unit?: celsius | fahrenheit | undefined;// }// 在组件中使用functionWeatherCardTyped({toolCall}:{toolCall:WeatherToolCall}){// args 自动提示 location 和 unit类型错误会在编译时报错const{location,unitcelsius}toolCall.call.args;consttempUnitunitcelsius?°C:°F;// ... 其余逻辑}8. 流式文本与工具调用联动useStream会同步处理流式文本和工具调用实现以下交互效果AI 文本流式输出时工具调用指令实时触发工具调用一触发立即渲染pending状态卡片工具执行完成后自动更新为completed状态并展示结果关键特性工具卡片与流式文本实时联动无需手动刷新同一call.id贯穿全生命周期状态更新时组件自动重渲染支持文本与工具调用** interleaved交错** 输出示例效果AI 消息正在为你查询北京的天气... [加载中] 正在执行 get_weather 工具... 1秒后 [完成] 北京 天气 25°C 晴9. 并发工具调用处理Agent 支持并行调用多个工具如同时查询天气和执行计算前端需处理多个pending状态的工具卡片并发工具调用列表组件import { ToolCallWithResult } from langchain/react/dist/types; import { ToolCard } from ./ToolCard; interface ToolCallListProps { toolCalls: ToolCallWithResult[]; } export function ToolCallList({ toolCalls }: ToolCallListProps) { // 按状态分组已完成在前执行中在后 const completedCalls toolCalls.filter(tc tc.state completed); const pendingCalls toolCalls.filter(tc tc.state pending); const errorCalls toolCalls.filter(tc tc.state error); return ( div classNamespace-y-3 {/* 已完成的工具调用 */} {completedCalls.map(tc ( ToolCard key{tc.call.id} toolCall{tc} / ))} {/* 执行中的工具调用 */} {pendingCalls.map(tc ( ToolCard key{tc.call.id} toolCall{tc} / ))} {/* 执行失败的工具调用 */} {errorCalls.map(tc ( ToolCard key{tc.call.id} toolCall{tc} / ))} /div ); }使用方式// 在 Message 组件中替换原工具卡片渲染逻辑 div classNametool-cards space-y-3 ToolCallList toolCalls{messageToolCalls} / /div10. 生产环境最佳实践完整状态覆盖必须处理pending、completed、error三种状态避免空白卡片安全解析 JSON工具结果为字符串JSON.parse()必须包裹在try/catch中提供 fallback UI通用卡片兜底为未知工具提供通用渲染方案避免 UI 崩溃加载状态透明化显示工具名称和参数让用户知道 Agent 正在执行的操作卡片样式紧凑工具卡片嵌入聊天消息避免过大尺寸影响对话体验类型安全优先使用zod定义工具 schema结合ToolCallFromTool实现编译时类型校验性能优化避免频繁重渲染使用 React.memo/Vue computed 缓存组件长文本结果使用虚拟滚动如超过 1000 字的网页搜索结果可访问性为工具卡片添加 ARIA 标签如aria-label天气工具结果加载状态添加进度提示支持键盘导航
返回列表