ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

前端开发者视角:从Function Calling到自主编排的AI Agent实战指南

前端开发者视角:从Function Calling到自主编排的AI Agent实战指南 1. 从“工具人”到“智能体”前端视角下的Agent演进最近和几个后端同事聊天他们聊起正在搞的“智能体”Agent项目什么自主决策、任务编排听起来玄乎得很。我凑过去一听发现核心逻辑其实和我们前端天天打交道的“事件驱动”、“状态管理”有异曲同工之妙。尤其是当提到“Function Calling”函数调用时我瞬间就明白了这不就是我们前端在写一个复杂的、带条件分支的异步操作流吗过去前端在AI应用里常常扮演“界面渲染”和“数据展示”的“工具人”角色。大模型返回一段文本或JSON我们负责把它漂亮地展示出来。但Agent的概念把前端开发者推到了一个更核心的位置我们不仅要处理展示还要理解、调度和执行一系列由模型“思考”后产生的具体动作。这其中的关键桥梁就是Function Calling。它让大模型从一个“聊天机器人”变成了一个可以调用真实世界能力比如查询天气、发送邮件、操作数据库的“智能体”。而如何设计这些函数的接口、如何安全地管理它们的执行、如何编排它们的调用顺序这些恰恰是前端工程师在构建复杂交互应用时积累的深厚经验所在。所以别再觉得Agent是后端或算法工程师的专属领域了。今天我就从一个前端开发者的视角带你彻底搞懂Agent的核心——从Function Calling的基础实现到多步骤任务的自主编排最后我们甚至可以用Node.js搭建一个简单的、能自己“思考”下一步该做什么的演示项目。你会发现那些你熟悉的JavaScript异步流程控制、事件监听、状态管理库如Redux、XState的设计思想在这里都能找到用武之地。2. Function Calling大模型与真实世界的“握手协议”理解Agent必须从Function Calling说起。你可以把它想象成大模型如GPT-4和我们实际业务代码之间约定好的一套“握手协议”或“远程过程调用RPC规范”。2.1 它解决了什么问题在没有Function Calling之前我们和大模型的交互基本是“单次问答”。用户问“今天上海天气怎么样” 模型回答“上海今天晴气温15-22度。” 这个回答是纯文本我们的前端应用拿到后可以显示它但无法自动触发后续动作比如把天气信息添加到用户的日历提醒中。Function Calling的出现改变了这一模式。模型在理解了用户意图“查询天气”后不再直接生成答案文本而是生成一个结构化的调用请求“请执行getWeather函数参数为{“city”: “上海”}。” 然后由我们的应用程序运行在安全环境下的Node.js服务或前端可控的云函数来实际执行这个getWeather函数获取真实的天气数据最后再把结果返回给模型或直接展示给用户。这个过程的核心价值在于将大模型的“思考”或“规划”能力与外部系统、API、数据库的“执行”能力解耦并连接起来。模型负责理解“要做什么”我们的代码负责“具体怎么做”。2.2 一个完整的Function Calling流程拆解让我们以OpenAI的API为例看看一次完整的Function Calling交互是怎样的。假设我们要实现一个“智能助理”它能帮用户查天气。第一步定义“函数工具”首先我们需要告诉模型我们有哪些“工具”可以用。这通过一个函数描述列表来实现。这个描述至关重要它就像是给模型的“工具说明书”。// 定义可供模型调用的函数列表 const tools [ { type: “function“, function: { name: “getCurrentWeather“, description: “获取指定城市的当前天气信息“, parameters: { type: “object“, properties: { location: { type: “string“, description: “城市名称例如上海北京“, }, unit: { type: “string“, enum: [“celsius“, “fahrenheit“], description: “温度单位摄氏度或华氏度“, }, }, required: [“location“], }, }, }, ];注意这里的几个关键点name: 函数名模型在决定调用时会引用这个名字。description: 函数的自然语言描述。这部分写得是否清晰直接决定了模型能否正确理解和使用该工具。要像给新人写文档一样明确说明这个函数是干什么的参数代表什么。parameters: 严格遵循JSON Schema格式定义参数。这定义了模型需要“填充”的数据结构。required字段指明了哪些参数是必填的。第二步模型接收请求并返回“调用指令”我们将用户的问题和工具列表一起发送给大模型。const response await openai.chat.completions.create({ model: “gpt-3.5-turbo“, messages: [{ role: “user“, content: “上海今天天气怎么样“ }], tools: tools, // 传入我们定义的工具列表 tool_choice: “auto“, // 让模型自行决定是否以及调用哪个工具 });模型的回复response不会直接包含天气答案而是如下结构{ “id“: “chatcmpl-xxx“, “object“: “chat.completion“, “created“: 1700000000, “model“: “gpt-3.5-turbo-0613“, “choices“: [ { “index“: 0, “message“: { “role“: “assistant“, “content“: null, “tool_calls“: [ { “id“: “call_abc123“, “type“: “function“, “function“: { “name“: “getCurrentWeather“, // 模型决定调用这个函数 “arguments“: “{\”location\“:\”上海\“,\”unit\“:\”celsius\“}“ // 模型生成的参数 } } ] }, “finish_reason“: “tool_calls“ // 完成原因是“需要调用工具” } ] }关键字段解读message.content: 为null因为模型没有生成直接回复而是选择了调用工具。message.tool_calls: 一个数组包含了模型希望调用的函数列表。每个调用有唯一id、函数name和模型拼装好的参数字符串arguments。finish_reason: 值为tool_calls表示模型因需要调用工具而停止了文本生成。第三步在本地执行函数并返回结果我们的应用程序需要解析这个调用指令找到本地对应的getCurrentWeather函数将arguments字符串解析为JSON对象并执行它。// 1. 提取模型调用的信息 const toolCall response.choices[0].message.tool_calls[0]; const functionName toolCall.function.name; const functionArgs JSON.parse(toolCall.function.arguments); // 2. 根据函数名执行本地对应的函数 let functionResult; if (functionName ‘getCurrentWeather’) { // 这里应该是真实的API调用例如调用和风天气、OpenWeatherMap等 functionResult await getCurrentWeather(functionArgs.location, functionArgs.unit); // 假设返回{ “temperature“: 20, “condition“: “Sunny“, “location“: “上海“ } } // 3. 将执行结果构造成模型要求的格式准备发送回去 const toolMessage { role: “tool“, content: JSON.stringify(functionResult), // 结果必须是字符串 tool_call_id: toolCall.id, // 必须对应之前的调用ID };第四步将结果送回模型获取最终回答我们把函数执行的结果以role: “tool“的消息形式追加到对话历史中再次请求模型。const secondResponse await openai.chat.completions.create({ model: “gpt-3.5-turbo“, messages: [ { role: “user“, content: “上海今天天气怎么样“ }, response.choices[0].message, // 包含tool_calls的助理消息 toolMessage, // 我们刚构造的工具执行结果消息 ], }); console.log(secondResponse.choices[0].message.content); // 输出“上海今天天气晴朗气温大约20摄氏度是个出门的好天气。”至此一个完整的Function Calling闭环就完成了。模型从“知道要查天气”到“给出友好回答”中间通过一个结构化的调用借助了外部数据源。注意安全是Function Calling的生命线。模型生成的arguments在解析和执行前必须进行严格的验证和清洗防止注入攻击。例如如果函数是执行数据库查询绝不能直接将模型生成的参数拼接到SQL语句中。3. 从单次调用到自主编排Agent的核心逻辑如果Function Calling是让模型学会了“使用一个工具”那么Agent就是让模型学会了“为完成一个复杂目标自主规划并使用一系列工具”。这就像从“给你螺丝刀拧下这颗螺丝”升级到了“把这个书架组装好”模型需要自己决定先装A柱还是B板需要用螺丝刀还是锤子。3.1 Agent的基本工作循环ReAct模式目前最主流的Agent推理模式是ReAct (Reason Act)。它的核心思想是让模型在“思考”和“行动”之间循环。思考根据当前目标用户问题和已有的上下文历史对话、已执行动作的结果模型分析下一步应该做什么。是直接回答还是调用某个函数如果调用参数是什么行动执行思考后决定的动作。如果是调用函数就执行Function Calling的流程如果是最终回答就生成文本。观察获取行动的结果函数返回值、API响应、错误信息等。循环将“观察”到的结果作为新的上下文再次进入“思考”步骤直到问题被解决或达到终止条件。这个循环用前端熟悉的语言来描述非常像一个状态机State Machine或一个复杂的、带条件判断的Promise链。3.2 用代码构建一个简易的旅行规划Agent让我们设想一个场景用户说“我想下周末去杭州玩两天帮我规划一下”。这是一个典型的多步骤任务涉及信息查询、逻辑判断和资源协调。我们的Agent需要自主调用多个函数。首先我们定义Agent可用的工具集const travelAgentTools [ { type: “function“, function: { name: “searchAttractions“, description: “搜索某个城市的旅游景点信息可以按类型过滤“, parameters: { /* ... JSON Schema ... */ } } }, { type: “function“, function: { name: “checkHotelAvailability“, description: “检查指定城市、日期和价格的酒店是否有空房“, parameters: { /* ... JSON Schema ... */ } } }, { type: “function“, function: { name: “getWeatherForecast“, description: “获取指定城市未来几天的天气预报“, parameters: { /* ... JSON Schema ... */ } } }, { type: “function“, function: { name: “calculateItinerary“, description: “根据景点、酒店和天气生成时间安排的行程草稿“, parameters: { /* ... JSON Schema ... */ } } } ];然后我们实现Agent的核心循环逻辑async function runTravelAgent(userQuery, maxSteps 10) { const messages [{ role: “user“, content: userQuery }]; let finalAnswer null; for (let step 0; step maxSteps; step) { // 1. 思考询问模型下一步该怎么做 const response await openai.chat.completions.create({ model: “gpt-4-turbo“, messages: messages, tools: travelAgentTools, }); const assistantMessage response.choices[0].message; messages.push(assistantMessage); // 将模型的“思考”记录到历史 // 2. 判断模型是想直接回答还是调用工具 if (!assistantMessage.tool_calls) { // 没有工具调用说明模型认为可以给出最终答案了 finalAnswer assistantMessage.content; break; } // 3. 行动并行执行模型要求的所有工具调用 const toolCallPromises assistantMessage.tool_calls.map(async (toolCall) { const { name, arguments: argsStr } toolCall.function; const args JSON.parse(argsStr); let result; // 根据函数名调用对应的本地函数 switch (name) { case ‘searchAttractions’: result await searchAttractions(args.city, args.type); break; case ‘checkHotelAvailability’: result await checkHotelAvailability(args.city, args.checkInDate, args.checkOutDate, args.maxPrice); break; // ... 其他case default: result { error: 未知的工具函数: ${name} }; } // 4. 观察将每个工具的执行结果构造为消息 return { role: “tool“, content: JSON.stringify(result), tool_call_id: toolCall.id, }; }); const toolMessages await Promise.all(toolCallPromises); messages.push(...toolMessages); // 将所有工具结果追加到历史 // 循环继续模型将基于这些新结果进行下一轮“思考” } if (!finalAnswer) { finalAnswer “规划任务过于复杂或步骤超限未能完成。“; } return finalAnswer; }这个简易的Agent已经具备了自主编排的能力。当你输入“杭州周末游”时它可能会第一轮思考后决定同时调用searchAttractions搜索杭州景点和getWeatherForecast获取杭州周末天气。第二轮收到景点和天气信息后思考决定调用checkHotelAvailability查找西湖附近的酒店。第三轮收到酒店信息后思考认为信息已齐全调用calculateItinerary生成行程。第四轮收到行程草稿后思考认为任务完成直接生成一段友好的、包含所有信息的最终回复给用户。3.3 前端思维在Agent设计中的体现你会发现设计和实现这样一个Agent非常像在写一个前端的状态管理逻辑。消息数组 (messages) 就是状态它完整记录了整个对话和工具调用的历史是Agent的“记忆”。每次循环我们都在修改这个状态。工具调用就是副作用调用外部API、查询数据库这些是纯函数之外的“副作用”。我们需要像管理Redux Thunk或Saga一样管理它们的执行、错误处理和结果整合。循环控制就是流程管理maxSteps防止无限循环就像前端需要处理加载超时。根据finish_reason或是否还有tool_calls来决定是继续循环还是结束这本质是一个状态机判断。错误处理与用户体验如果某个工具调用失败如酒店查询API挂了我们需要将错误信息作为tool消息返回给模型。一个聪明的Agent应该能“观察”到这个错误并尝试替代方案比如换个查询条件或向用户坦诚说明。这和我们前端处理接口错误、展示友好提示的逻辑一模一样。4. 实战用Node.js构建一个命令行智能体理解了原理我们动手实现一个更具体、可运行的例子一个能帮你管理本地文件系统的命令行智能体。它可以通过自然语言命令执行创建文件夹、列出文件、查找文件内容等操作。4.1 项目初始化与工具定义首先创建一个新的Node.js项目并安装依赖。mkdir file-agent cd file-agent npm init -y npm install openai dotenv创建.env文件存放你的OpenAI API Key请务必妥善保管不要上传到Git。OPENAI_API_KEYsk-your-api-key-here接下来在index.js中我们定义这个文件管理Agent可以使用的工具。为了安全起见我们将所有操作限制在当前项目目录下。import OpenAI from ‘openai’; import fs from ‘fs/promises’; import path from ‘path’; import { fileURLToPath } from ‘url’; const __dirname path.dirname(fileURLToPath(import.meta.url)); const PROJECT_ROOT path.resolve(__dirname); // 将操作限制在项目根目录 const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); // 定义工具集 const tools [ { type: “function“, function: { name: “listFiles“, description: “列出指定目录下的文件和文件夹。如果不指定目录则列出当前目录。“, parameters: { type: “object“, properties: { directory: { type: “string“, description: “目录路径相对于项目根目录。例如’src’, ‘./docs’。“, }, }, required: [], }, }, }, { type: “function“, function: { name: “readFile“, description: “读取指定文件的内容。“, parameters: { type: “object“, properties: { filePath: { type: “string“, description: “文件的相对路径例如’src/index.js’, ‘README.md’。“, }, }, required: [“filePath“], }, }, }, { type: “function“, function: { name: “createFile“, description: “创建一个新文件并写入初始内容。“, parameters: { type: “object“, properties: { filePath: { type: “string“, description: “要创建的新文件的相对路径。“, }, content: { type: “string“, description: “要写入文件的初始内容。可以是空字符串。“, }, }, required: [“filePath“], }, }, }, { type: “function“, function: { name: “searchInFiles“, description: “在当前目录及其子目录的所有文件中搜索包含特定关键词的内容。“, parameters: { type: “object“, properties: { keyword: { type: “string“, description: “要搜索的关键词。“, }, fileExtension: { type: “string“, description: “可选限制搜索特定扩展名的文件例如’.js’, ‘.md’。“, }, }, required: [“keyword“], }, }, }, ];4.2 实现工具函数与安全边界接下来实现这些工具函数。安全是重中之重我们必须防止模型通过../../../这样的路径遍历攻击访问到系统敏感文件。// 安全工具函数将用户提供的相对路径解析为绝对路径并确保它在项目根目录内 function resolveSafePath(userPath) { const resolvedPath path.resolve(PROJECT_ROOT, userPath); // 检查解析后的路径是否仍在项目根目录内 if (!resolvedPath.startsWith(PROJECT_ROOT)) { throw new Error(访问被拒绝路径“${userPath}”超出了允许的范围。); } return resolvedPath; } // 实现 listFiles async function listFiles({ directory ‘.’ }) { try { const targetDir resolveSafePath(directory); const items await fs.readdir(targetDir, { withFileTypes: true }); const result items.map(item ({ name: item.name, type: item.isDirectory() ? ‘directory’ : ‘file’, })); return { success: true, cwd: targetDir, items: result }; } catch (error) { return { success: false, error: error.message }; } } // 实现 readFile async function readFile({ filePath }) { try { const safePath resolveSafePath(filePath); const stats await fs.stat(safePath); if (!stats.isFile()) { return { success: false, error: “${filePath}”不是一个文件。 }; } const content await fs.readFile(safePath, ‘utf-8’); // 返回文件内容可以截断过长的文件 return { success: true, content: content.slice(0, 5000) }; } catch (error) { return { success: false, error: 读取文件失败${error.message} }; } } // 实现 createFile async function createFile({ filePath, content ‘’ }) { try { const safePath resolveSafePath(filePath); // 检查文件是否已存在 try { await fs.access(safePath); return { success: false, error: 文件“${filePath}”已存在。 }; } catch { // 文件不存在继续创建 } // 确保目录存在 await fs.mkdir(path.dirname(safePath), { recursive: true }); await fs.writeFile(safePath, content, ‘utf-8’); return { success: true, message: 文件“${filePath}”创建成功。 }; } catch (error) { return { success: false, error: 创建文件失败${error.message} }; } } // 实现 searchInFiles (简易递归搜索) async function searchInFiles({ keyword, fileExtension }) { const results []; async function searchDir(dir) { const items await fs.readdir(dir, { withFileTypes: true }); for (const item of items) { const fullPath path.join(dir, item.name); if (item.isDirectory()) { await searchDir(fullPath); // 递归搜索子目录 } else if (item.isFile()) { if (fileExtension !item.name.endsWith(fileExtension)) { continue; } try { const content await fs.readFile(fullPath, ‘utf-8’); if (content.includes(keyword)) { const relativePath path.relative(PROJECT_ROOT, fullPath); results.push({ file: relativePath, matches: ‘包含关键词’ }); } } catch (error) { // 忽略无法读取的文件如二进制文件 } } } } try { await searchDir(PROJECT_ROOT); return { success: true, keyword, results }; } catch (error) { return { success: false, error: error.message }; } } // 工具名称到实现函数的映射 const availableFunctions { listFiles, readFile, createFile, searchInFiles, };4.3 实现Agent主循环与交互最后我们实现一个简单的命令行交互界面让Agent运行起来。import readline from ‘readline’; const rl readline.createInterface({ input: process.stdin, output: process.stdout, }); async function runAgentLoop() { console.log(‘文件管理智能体已启动。输入“quit”退出。\n‘); const messages []; // 对话历史 while (true) { const userInput await new Promise(resolve { rl.question(‘ ‘, resolve); }); if (userInput.toLowerCase() ‘quit’) { console.log(‘再见’); rl.close(); break; } messages.push({ role: ‘user’, content: userInput }); try { let finalResponse null; let isFinished false; let stepCount 0; const MAX_STEPS 8; // 防止无限循环 // Agent思考-行动循环 while (!isFinished stepCount MAX_STEPS) { stepCount; const response await openai.chat.completions.create({ model: “gpt-3.5-turbo“, // 或 “gpt-4” messages: messages, tools: tools, tool_choice: “auto“, }); const assistantMessage response.choices[0].message; messages.push(assistantMessage); // 检查是否需要执行工具 if (assistantMessage.tool_calls) { console.log([Agent思考中... 步骤 ${stepCount}]); const toolMessages []; for (const toolCall of assistantMessage.tool_calls) { const functionName toolCall.function.name; const functionToCall availableFunctions[functionName]; if (!functionToCall) { toolMessages.push({ role: “tool“, content: JSON.stringify({ error: 函数 ${functionName} 未定义。 }), tool_call_id: toolCall.id, }); continue; } const functionArgs JSON.parse(toolCall.function.arguments); console.log( - 调用: ${functionName}(${JSON.stringify(functionArgs)})); const result await functionToCall(functionArgs); console.log( - 结果: ${JSON.stringify(result).slice(0, 100)}...); toolMessages.push({ role: “tool“, content: JSON.stringify(result), tool_call_id: toolCall.id, }); } messages.push(...toolMessages); } else { // 没有工具调用生成最终回答 finalResponse assistantMessage.content; isFinished true; } } if (finalResponse) { console.log(\n智能体${finalResponse}\n); // 将最终回答也加入历史保持上下文连贯 messages.push({ role: ‘assistant’, content: finalResponse }); } else { console.log(‘\n智能体任务似乎过于复杂或步骤超限未能完成。\n‘); } } catch (error) { console.error(‘运行出错‘, error.message); } } } // 启动Agent runAgentLoop().catch(console.error);现在运行node index.js你就可以和你的文件管理智能体对话了文件管理智能体已启动。输入“quit”退出。 帮我看看当前目录下有什么文件 [Agent思考中... 步骤 1] - 调用: listFiles({}) - 结果: {success:true,cwd:/Users/xxx/file-agent,items:[{name:node_modules,type:direc... 智能体当前目录/Users/xxx/file-agent下有以下内容一个名为“node_modules”的文件夹可能是依赖库以及“package.json”、“package-lock.json”、“index.js”和“.env”文件。 创建一个叫“notes”的文件夹然后在里面建一个“todo.md”文件内容写“学习Agent开发” [Agent思考中... 步骤 1] - 调用: createFile({filePath:notes/todo.md,content:学习Agent开发}) - 结果: {success:true,message:文件“notes/todo.md”创建成功。} 智能体已经按照您的要求在“notes”文件夹下创建了“todo.md”文件并写入了“学习Agent开发”的内容。 在所有.md文件里搜索“Agent” [Agent思考中... 步骤 1] - 调用: searchInFiles({keyword:Agent,fileExtension:.md}) - 结果: {success:true,keyword:Agent,results:[{file:notes/todo.md,matches:包含关键词}]} 智能体在所有的.md文件中搜索关键词“Agent”在“notes/todo.md”文件中找到了匹配项。通过这个简单的项目你已经亲手实现了一个具备自主编排能力的Agent。它能够理解你的自然语言指令将其分解为多个具体的文件操作步骤创建目录、创建文件、写入内容并依次执行。5. 深入思考前端开发者在Agent时代的机遇与挑战构建完这个Demo我们回到更宏观的视角。Agent技术给前端开发者带来的远不止是一个新的玩具。5.1 机遇从“界面工匠”到“交互架构师”传统前端的工作流是产品给需求 - 设计给稿子 - 后端给接口 - 前端实现界面和交互。在这个链条里前端处于下游主要负责“呈现”和“局部交互”。而Agent驱动的应用其核心是“基于自然语言的复杂任务流”。这要求应用具备意图理解与分解将用户模糊的目标拆解为具体步骤。工具调度与编排决定每一步用什么工具处理工具间的依赖和并行。状态管理与错误恢复维护任务执行上下文处理中途失败并尝试恢复或降级。交互式澄清与确认在信息不足时主动询问用户。这些能力恰恰是构建一个健壮、复杂的前端应用所需要的核心架构能力。前端开发者熟悉的状态管理Redux, Zustand, XState、异步流程控制Promise, async/await, RxJS、组件生命周期、错误边界等概念和经验可以直接迁移到Agent的“大脑”即Orchestrator编排器开发中。未来前端开发者可能不再仅仅负责“按钮点击后调用哪个API”而是负责设计“当用户说‘帮我安排一次旅行’时整个应用应该如何思考、决策和执行”。你的战场从浏览器页面扩展到了整个应用的核心决策逻辑。5.2 挑战新范式下的必备技能当然要抓住这个机会我们需要补充一些新的技能树提示工程如何为模型设计清晰、无歧义的工具描述description和parameters如何通过系统提示词System Prompt设定Agent的角色和行为边界这直接决定了Agent的可靠性和智商。流式响应与用户体验Agent的思考过程可能是多步的、耗时的。如何将“思考中...”、“调用搜索API...”、“正在生成行程...”这些中间状态流畅地展示给用户而不是让用户面对一个长时间的空白界面这需要结合Server-Sent Events (SSE) 或WebSocket实现流式输出对前端实时交互设计提出了更高要求。评估与测试如何测试一个Agent传统的单元测试给定输入断言输出可能不适用因为Agent的输出具有非确定性。我们需要建立新的评估体系比如用一系列标准问题Benchmark来检验其成功率或测试其工具调用的准确率。成本与性能优化大模型的API调用是按Token收费的。Agent的多轮对话意味着更高的成本。如何设计高效的提示词以减少不必要的Token消耗如何缓存一些中间结果如何对工具调用进行节流或合并这些都是工程实践中必须考虑的问题。5.3 安全与责任必须坚守的底线最后也是最重要的一点是安全。当我们赋予模型调用真实世界能力的权限时必须建立牢不可破的护栏。权限最小化就像我们Demo里做的将文件操作严格限制在项目目录内。任何工具函数都必须假设模型的输入可能是恶意的进行严格的输入验证和权限检查。用户确认机制对于高风险操作如删除文件、发送邮件、支付不能完全让Agent自主决定。必须在执行前通过界面明确向用户请求确认。透明性与可解释性Agent的“黑箱”特性让人不安。好的设计应该让用户能看到Agent的“思考过程”比如它计划做什么、调用了什么工具、结果是什么。这不仅能建立信任也便于调试。内容安全过滤对模型生成的内容和工具返回的内容都要进行必要的安全过滤防止生成有害、偏见或不合规的信息。Agent不是魔法它是一套由我们精心设计和控制的技术架构。作为前端开发者我们既是最贴近用户的人也是最擅长构建复杂交互逻辑的人。理解并掌握从Function Calling到自主编排的整套技术栈意味着我们能在下一代AI原生应用的浪潮中从功能的实现者转变为体验的定义者。这不再只是画界面而是设计智能体与人类协作的交互范式本身。
返回列表