news 2026/9/10 8:17:43

前端手摸手跑路AI应用开发:从大模型API到SSE流式对话实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端手摸手跑路AI应用开发:从大模型API到SSE流式对话实战

说实话,前端圈这两年最热门的话题之一,就是“前端到底能不能搞AI应用开发”。我作为一个写了多年页面的老前端,一开始也觉得这东西是算法工程师的专利,直到真的被丢了个“聊天机器人”需求,硬着头皮从零啃了一遍大模型API、流式输出、上下文管理这些玩意之后才反应过来——大部分AI应用开发,本质上还是工程接入问题,前端不但能做,而且能做得很顺。这篇是“前端手摸手跑路之AI应用开发”系列的第一篇,目标很朴素:不讲训练模型、不谈数学原理,就按我们前端最熟悉的套路,把大模型API调用、SSE流式输出、跨域与密钥安全、上下文管理这些基础打牢,带你在3小时内跑通一个能用的AI对话小助手。

1. 为什么前端做AI应用开发,反而不是件难事

1.1 技术栈没有变,变的是数据来源

很多前端同学一听“AI应用开发”,第一反应是“这不得先学Python、学PyTorch、学机器学?”其实完全不是这么回事。你去看看市面上大量号称AI产品的应用,拆开来看,最核心的逻辑还是:收集用户输入,组装参数,请求一个HTTP接口,然后把返回的内容渲染到页面上。这跟我们以前调后端接口做列表页、详情页没有本质区别,只不过这次返回的不再是固定结构的JSON,而是一段动态生成的文本流。

我用一个生活化的类比来解释:以前我们做前端,是从数据库里取数据,数据库里有什么,我们就展示什么;现在做AI应用开发,是从大模型里“取生成结果”,模型会根据我们给的对话历史,现场编一段内容出来。前者是“查字典”,后者是“让一个很聪明但有时候不太靠谱的同事帮你写方案”。所以,你之前会的Vue、React、状态管理、构建部署、HTTP请求,在这里全部用得上,一项都不会白学。

1.2 你不需要自己训练模型,只需要学会和模型对话

这是新手最容易绕进去的误区。有人总觉得AI应用开发=自己造一个大模型,于是先去研究几万张显卡的集群部署,结果还没开始就放弃了。真实的行业分工早就清晰了——模型训练和推理优化是专门的团队在搞,应用开发者只需要做一件事:把用户的输入整理成模型能理解的格式,调用现成的API,再把模型返回的结果包装成产品。

这就像你要做一款外卖App,完全不需要自己开一家餐厅。你只需要把“用户想吃什么”翻译成订单,交给后厨去炒菜,再把菜端到用户面前。如今各大云厂商、AI平台都提供了标准化的模型服务接口,你拿到一个API Key,就能接入几十种模型。所以,别被“AI应用开发”这四个字唬住,它真正的门槛不在AI理论,而在工程能力:怎么把流式响应处理好、怎么管理对话记忆、怎么处理并发和错误,这些恰恰是前端的主场。

1.3 前端开发者在AI应用里的三个独特优势

我踩过一圈坑之后,愈发觉得前端在这个领域不是“过来打杂的”,而是有实打实的优势:

第一,交互敏感度。AI应用最典型的体验特征是什么?是“打字机”式的流式输出,是用户点“停止生成”能随时中断,是对话列表的动态插入和滚动定位。这些交互对后端同学来说可能需要重新理解,但对前端来说就是老本行,属于基本功范畴。

第二,工程化思维。现在AI应用越来越复杂,动不动就是Agent工作流、多轮工具调用、消息状态机。你能不能把一条消息的状态(发送中、流式接收中、完成、失败)管理清楚?能不能把各种回调事件拆成清晰的组件?这很考验组件化能力和状态管理能力,前端在这方面有天然优势。

第三,离用户近。AI应用的迭代速度极快,今天上线一个功能,明天就可能根据用户反馈调整Prompt或交互。前端本来就处在用户和产品之间,改了效果立刻能看见,这种快速试错的能力,在AI应用开发中极其重要。另外,现在前端面试的考察范围也明显在往AI方向靠,什么“AI应用开发怎么入门”“你做过哪些AI相关项目”都快成常见题了,早一步动手,面试就多一份底气。

2. 告别黑盒:先把大模型API的几个核心概念整明白

2.1 一次最简单的模型调用请求

不管你是接哪家的大模型服务,只要它是OpenAI兼容接口,那请求格式基本上大同小异。我强烈建议第一次接触的人,先用curl把最原始的请求打一遍,亲眼看看返回长什么样,比看十篇教程都管用。这里我给一个简化版的调用示例:

curl https://你的API服务地址/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer 你的_API_Key" \ -d '{ "model": "gpt-4o-mini", "messages": [ {"role": "system", "content": "你是一个友好的助手"}, {"role": "user", "content": "你好,请用一句话介绍你自己"} ], "stream": false }'

注意看这里的请求体,关键点全在messages数组里。它是由一条条消息组成的,每条消息必须有rolecontent两个字段。role有三种:system表示系统设定,告诉模型“你是个什么样的角色”;user表示用户说的话;assistant表示模型之前回复过的内容。

前端同学可以把这个数组理解成聊天记录数组。每次请求新问题时,不是只把新问题发给模型,而是要把历史上所有的对话消息都带上。这是AI应用和普通接口开发最大的一个不同点:普通接口是无状态的,你传什么就是什么;大模型API是有“记忆”的,但这个记忆全靠你每次请求时手动把历史消息塞给它。你不带历史,它就完全失忆;你带太多历史,又会撞上上下文窗口限制。就这一个细节,能引出一堆工程问题,后面专门讲。

2.2 SSE流式输出:聊天不是等出来的,是“读”出来的

如果你把上面请求里的stream改成true,返回方式会完全不同。这就是AI聊天应用最核心的机制:SSE(Server-Sent Events)。理解SSE不需要什么高深知识,你只需要知道两件事。

第一,它和WebSocket不一样。WebSocket是双向通信,适合实时游戏、多人协作这类场景;SSE是单向的,服务器主动往客户端推数据,而且底层就是普通的HTTP,所以天然能复用浏览器原生的请求机制,不需要额外维护一个长连接协议。在AI对话场景里,模型只需要从服务端一路往客户端推内容,SSE是再合适不过的。

第二,它的数据格式很“朴素”,是普通的文本流。每行数据长这样:

data: {"choices":[{"delta":{"content":"你"}}]} data: {"choices":[{"delta":{"content":"好"}}]}

每段以data:开头,后面跟一段JSON字符串,消息之间用空行分隔。等模型输出完,最后会有一个特殊标记[DONE]。这种格式对前端来说非常好解析,但有一个隐藏的坑:因为它是流式的,你拿到的一个数据块里可能含有多行,也可能一行被劈成两半。你必须在代码里做“按行切分 + 缓存半截”的处理,这也是后面实操部分的核心代码逻辑。

那为什么AI聊天必须用流式,而不是等模型全生成完再一次性返回呢?因为大模型推理再快,生成几百个字也得要几秒到几十秒。如果用户点击发送后看着空页面干等十几秒,体验直接崩了。流式输出让用户看到第一个字,等后续内容陆续“打”出来,符合人脑对实时反馈的期待。你在ChatGPT、各种AI助手产品里看到的打字机效果,底层都是SSE。

2.3 上下文窗口:一个前端最容易忽略的预算问题

“上下文窗口”是每个做AI应用的人都绕不开的一个概念。简单说,模型每次能接收的文本总量是有限的,这个上限就叫上下文窗口。你可以把它想象成一次会议最多能同时容纳多少个发言记录,满了之后,只能把最早的发言“请出会议室”。

对大模型来说,衡量“发言长度”的单位不是字数,而是token。token可以粗略理解成“单词/词组/字的碎片”:一般来说,一个英文字符约等于0.25个token,一个汉字大概占1到2个token。不同模型的上下文窗口差距很大,小的可能只有4000个token(粗略算下来也就几千字的对话历史),大的有128K、200K甚至更多,能塞下一整本长篇小说。

这对前端意味着什么呢?意味着你不能无限地把聊天记录全往请求里塞。用户聊了一百轮之后,如果还是把全部历史都传上去,第一个撞墙的就是上下文窗口,API会直接报错。而且还有成本问题——大模型API基本都是按token计费的,你每次请求带的历史越多,花的钱就越多。很多新手做出来的Demo,上线之后用户聊多了,发现账单爆了,就是这个原因。所以,应用开发阶段必须设计一套上下文管理策略,比如只保留最近N条消息、把过长的历史做摘要压缩、或者让用户手动清空会话。这些策略我们后面实操部分会给出一个简单可用的版本。

2.4 温度与随机性:别忽略那几个“小参数”

除了model和messages之外,请求体里通常还有几个可选参数,很多前端第一次接触会直接忽略,但它们对产品形态影响很大。最典型的是temperature,它控制模型输出的随机性,取值范围一般是0到2。数值越低,输出越保守、越稳定,适合做客服、写代码、提取摘要;数值越高,输出越发散、越有“创意”,适合写文案、头脑风暴。另外一个常见参数是max_tokens,它限制单次生成内容的最大长度,假设你把模型接口并到前端但不做任何限制,一旦模型“话痨”起来,输出可能异常长,不仅拖慢渲染,费用也受不了。我个人的习惯是:MVP阶段先把它设成500到800,跑通之后再根据场景调。

3. 三小时跑通第一个AI对话小助手

3.1 工程初始化与总体结构

约定了原理,我们就开始实操。这一小节我会用 Vite + Vue3 来搭建项目,React 的思路完全一样,核心逻辑不变。为什么用 Vite?因为它是目前前端构建工具里开箱即用做得最好的几个之一,启动快、Proxy配置简单,特别适合快速原型验证。

我先说下这次Demo的整体结构:

  • 前端负责:用户输入、展示消息列表、解析和渲染SSE流、维护对话上下文。
  • 前端开发服务器的Proxy负责:把请求转发到大模型服务,并在转发时注入 Authorization 头,避免密钥暴露在浏览器。
  • 生产环境可选:一个极薄的Node服务做同样的事,或者用Nginx配置转发。

初始化项目这一步不用多讲,直接跑一遍初始化命令就行。跑完之后,创建src/api/chat.js放请求逻辑,创建src/components/ChatMessage.vue放单条消息展示,根组件里维护消息列表和输入框。目录别太复杂,先把流程跑通最重要。

3.2 前端代理层:解决跨域和密钥安全

前端同学第一次写AI应用最容易遇到的两个问题:一是浏览器跨域,二是API Key不小心写在前端代码里。这两个问题有一个共同的解决方案:不直接请求大模型的API,而是请求我们自己前端的同源地址,再由服务端去转发。

在Vite开发环境下,配置Proxy只需要几行代码。我在vite.config.js里会这样写:

import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [vue()], server: { proxy: { '/api': { target: 'https://你的大模型API域名', changeOrigin: true, rewrite: path => path.replace(/^\/api/, ''), configure(proxy) { proxy.on('proxyReq', (proxyReq) => { proxyReq.setHeader('Authorization', `Bearer ${process.env.LLM_API_KEY}`); }); }, }, }, }, });

这段配置干了三件事:第一,浏览器客户端请求的是/api/chat/completions,属于同源请求,不存在跨域问题;第二,Vite开发服务器收到请求后,会在内部转发到真实的大模型API地址;第三,转发的时候,由Node侧注入Authorization头,API Key只存在于开发服务器的环境变量里,浏览器永远接触不到。

可能有人会问:跨域问题不能通过后端开CORS解决吗?能解决一部分,但密钥问题不行。API Key放在前端代码里,等于把银行卡密码写在便利贴上贴门口,任何一个打开DevTools的用户都能看到。所以不管多麻烦,密钥必须留在服务端。这条原则我建议你从第一行代码开始就严格遵守。

3.3 核心代码:用fetch + ReadableStream解析流式响应

接下来是整个Demo最核心的部分:解析SSE流。EventSource虽然原生支持SSE,但它有几个硬伤——只能发GET请求、不能自定义请求头、断线重连逻辑不可控制。到大模型API这一趴基本都要求POST,所以实际开发中大家更常用fetch+ReadableStream自己手动解析。代码看起来有点绕,其实逻辑很清晰:

// src/api/chat.js export async function sendMessageStream({ messages, onData }) { const response = await fetch('/api/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'gpt-4o-mini', messages, stream: true, temperature: 0.7, max_tokens: 800, }), }); if (!response.ok) { const err = await response.json().catch(() => ({})); throw new Error(err.error?.message || `HTTP ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; // 把二进制流解码成字符串,并拼接到buffer里 buffer += decoder.decode(value, { stream: true }); // 按换行符拆分,注意最后一段可能是半截,要留在buffer里等下次拼接 const lines = buffer.split('\n'); buffer = lines.pop(); for (const line of lines) { const trimmed = line.trim(); if (!trimmed.startsWith('data:')) continue; const data = trimmed.slice(5).trim(); if (data === '[DONE]') continue; try { const json = JSON.parse(data); const content = json.choices?.[0]?.delta?.content; if (content) onData(content); } catch (e) { // 如果解析失败,通常是半截JSON,交给下一轮处理 } } } }

这段代码里有几个细节值得单独强调:

第一,buffer变量的作用。网络传输是分包进行的,V8的解析粒度、网卡的MTU、服务器的发送策略都会导致一个数据块里可能只有半行JSON。如果你不对buffer做缓存处理,很容易出现“解析JSON失败”的偶发报错。解决方案就是上面的思路:遇到换行符才认为是完整一行,没遇到的半截字符串留在buffer里继续拼接。

第二,decoder.decode(value, { stream: true })。大模型的流式返回是UTF-8编码的,但如果一个汉字被拆成两个二进制块,直接按单块解码会出现乱码。TextDecoder加上stream: true就是为了处理这种情况:它会缓存不完整的多字节字符,等下一块数据来了再拼接。

第三,data:后面不一定都有空格,有的是data:{"..."},所以解析时用slice(5)之后还要再trim()一下,避免混入空格污染JSON字符串。

这段代码写完之后,前端就已经“能收到”模型的流式输出片段了。下一步是把这些片段渲染到页面上。

3.4 维护对话上下文与状态管理

很多新手做完上面那步就以为万事大吉,结果发现模型“完全没有记忆”——你问它“我叫张三”,下一轮问“我叫什么”,它一脸茫然。原因前面说过了:每次请求都是独立的,你得手动把历史消息传给它。

所以,我会在组件里维护一个messages数组,结构如下:

const messages = ref([ { id: 1, role: 'assistant', content: '你好,我是AI助手,有什么可以帮你?', status: 'done' }, ]);

用户每次发送消息,就先往数组里push一条role: 'user'的消息;紧接着,再push一条role: 'assistant'的空消息,它的content会在流式回调里不断追加,同时给它打一个status: 'streaming'标记,用来控制“停止生成”按钮和打字机光标。

真正发请求时,要把UI里的消息数组转换成API需要的格式。注意两点:一是UI消息比API消息多了一些跟渲染相关的字段,要过滤掉;二是要控制历史消息条数,防止上下文爆炸。我习惯做一个简单的截断函数:

function buildRequestBody(messages) { // 保留最近的20条消息,防止上下文无限膨胀 const recent = messages.slice(-20); return { model: 'gpt-4o-mini', messages: recent.map(m => ({ role: m.role, content: m.content, })), stream: true, }; }

这里slice(-20)是一个简单粗暴但有效的方案。如果产品形态是“需要长时间记忆的智能体”,那就要上更复杂的策略,比如把更早的对话交给模型生成摘要,再把摘要作为system消息塞进去。这一步是目前实现“类人记忆”的常见做法,等系列后几篇再展开。

3.5 生产环境怎么加一层后端

Vite的Proxy只适用于开发环境。打包上线之后,你不可能还跑着一个Vite开发服务器。生产环境的主流做法是:前端静态文件放在Nginx/CDN上,API请求打到自己的一台轻量后端服务上,由后端转发到大模型API。如果你已经会Node,其实只要一小段代码就能搞定一个转发服务:

import express from 'express'; import fetch from 'node-fetch'; // Node 18+直接用内置fetch也行 const app = express(); app.use(express.json()); app.post('/api/chat/completions', async (req, res) => { const upstreamRes = await fetch('https://你的大模型API域名/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.LLM_API_KEY}`, }, body: JSON.stringify(req.body), }); res.status(upstreamRes.status); // 上游是流式响应时,直接把流pipe回给前端 upstreamRes.body.pipe(res); }); app.listen(3000, () => console.log('proxy server run at 3000'));

这段代码的核心逻辑就是“透传”:前端请求打到我们自己后端,后端加上密钥再请求大模型API,然后把响应流原样管道回给前端。这样做的好处一是密钥安全,二是可以在中间层做缓存、限流、日志、审计,后续要扩展Agent能力也方便。有些团队也用Serverless函数做这个转发层,成本更低,思路一样。这一块你可以根据自己的技术栈选,原理都是相同的。

4. 流式输出的进阶打磨:让体验从“能跑”到“好用”

4.1 文本增量渲染与Markdown处理

流式响应接进来之后,最“原始”的渲染方式是把content当纯文本直接塞进DOM。这样很快就能跑通,但如果模型返回的是带Markdown语法的内容,页面上全是#**、反引号,用户看着很难受。所以稍微正式一点的AI对话应用,都会引入Markdown渲染。

方案也很直接:组件里装一个markdown-it之类的库,把content实时渲染成HTML。不过流式场景有一个性能坑:如果你在每次onData回调触发时都重新渲染整个消息内容,页面很可能会越用越卡。原因在于Markdown解析和DOM更新都是CPU密集型操作,几十个字感觉不到,几百上千字时输入框可能开始掉帧。

我的经验是用“分帧渲染”的思路:不是每一小块内容到了马上去解析,而是把最新内容丢进一个待渲染队列,用requestAnimationFrame把渲染频率和浏览器刷新率对齐。这样用户看到的效果依然是“连续的流式输出”,实际渲染次数大幅减少,长文本下明显更流畅。需要注意的是,Markdown渲染方案也影响流式展示的稳定性,比如代码块在没闭合之前是会整体乱掉的,这个只能等整体出来再修复,或者给消息一个“渲染完成”的标记后再做一次最终渲染。

4.2 中断、取消与重试

用户点击“停止生成”是一个高频操作。想想你自己用聊天产品时,如果模型开始“胡说八道”,第一反应就是点停止。前端实现这个交互的底层能力是AbortController

我给的sendMessageStream函数一开始其实应该接收一个signal信号。调用时创建AbortController,然后把signal传进fetch的配置项里。用户点击停止时,执行controller.abort(),fetch 就会抛出一个AbortError。代码里要单独捕获这个错误,不能把它当成普通网络错误处理,否则界面上会错误地弹“网络异常”。

const controller = new AbortController(); try { await sendMessageStream({ messages, signal: controller.signal, onData }); } catch (err) { if (err.name === 'AbortError') { // 用户主动停止,不提示错误 } else { // 网络错误/接口异常,提示重试 } }

重试策略在AI应用里同样重要。大模型API的稳定性不像普通后端接口,高峰期经常会出现5xx、429(限流)。我建议在MVP阶段至少做一层“指数退避”重试:第一次失败等1秒重试,第二次等2秒,第三次等4秒,最多试3次。退避时间要递增,避免大家同时重试把服务打得更死。

4.3 请求并发与限流策略

聊天页面的并发问题容易被忽视。我见过一个真实案例:聊天应用上线后,有用户连点发送按钮,同一时间发出十几个请求,把模型API的配额瞬间打爆。前端要做的事情很简单——当有一条消息正在流式生成时,禁止再次发送。UI上表现为“发送按钮禁用”或“输入框锁死”,一股脑发出去的结果大概率是上下文错乱加账单失控。

还有一个浏览器层的并发限制:浏览器对同一域名下的HTTP/1.1连接数有限制(通常是6个左右)。SSE流式请求本身非常占用连接,如果同一个页面同时跑多个流式请求,其他资源请求会被阻塞,表现为页面图片、接口全部变慢。所以,同一个对话框里,一次只允许一个流式请求在跑,是最稳妥的规则。真有多线程生成需求的产品,也要通过后端统一调度,而不是让浏览器同时开多条长连接。

5. 前端AI应用踩坑实录:常见问题与排查思路

5.1 常见问题速查表

写到这里,我把实际开发里最常踩的坑整理成了一张速查表,按“症状→可能原因→解决方案”来组织,建议直接收藏备用。

症状可能原因解决方案
控制台跨域报错前端直接请求了大模型API域名走Vite/后端/Nginx代理,不要直连上游
流式内容一截一截乱码UTF-8字符被拆成多块解码TextDecoder(..., { stream: true })
偶发 JSON.parse 失败SSE数据行被网络分包截断用buffer缓存半截行,按换行符拆分
界面一直空白,内容不出请求没到后台,或后台没关缓冲检查Network请求状态,Nginx关proxy_buffering off;后端加X-Accel-Buffering: no
第一次请求成功,后续报错token超限历史消息无限堆积超出上下文窗口截断历史、摘要压缩、定期清空会话
收到401/403API Key无效,或没有在转发时注入确认环境变量、确认服务端转发逻辑
收到429触发限流或被并发打爆加指数退避重试,前端限制并发请求
内容过低级或出格被拒模型内容安全策略拦截检查返回里的content_filter字段,按规范提示用户
点击停止后仍不断弹出错误没有单独捕获AbortErrorerr.name === 'AbortError'时静默处理

5.2 用Network面板像“老司机”一样排查

排查AI应用问题,我第一件事永远是打开浏览器DevTools的Network面板,而不是去看业务代码。因为AI应用大量涉及流式传输、鉴权、代理转发,这些环节的状态码和响应数据比业务代码里的console更能说明问题。

看什么呢?第一看请求是不是发到了你以为的地址,尤其是代理配置最容易在这里露馅——你以为请求到了/api/...,实际上可能被rewrite成了别的路径;第二看HTTP状态码,如果是401/403就去查密钥和代理头,如果是429就去查限流策略,如果是5xx就去查上游服务状态;第三看Timing一栏,如果Content Download时间特别短,但页面又没显示流式内容,十有八九是后端把流给缓冲了,等到全部生成完才一次性返回。遇到这种情况,浏览器侧看Response是获取不到实时状态的,只能通过服务端日志或直接curl上游接口来判断。

5.3 关于部署时Nginx/Docker的stream缓冲坑

这是我在生产环境踩过最深的一个坑,必须单独拿出来说。前端项目打包之后,我用Docker起了一个Nginx容器,同时反代后端API。结果上线测试时发现,用户问一个问题后,页面等了足足十几秒,然后模型答案“哗”一下整体出现,根本没有流式效果。当时第一反应是前端解析代码写错了,排查了半小时,最后发现根因在Nginx的缓冲上。

Nginx默认会把上游响应缓冲到一定大小再发给客户端,大型响应甚至会等上游完全结束才发送。这对静态资源、普通API没有影响,但直接毁掉了SSE流式体验。解决方案是在Nginx配置里手动关掉缓冲:

location /api/ { proxy_pass http://你的后端服务:3000; proxy_buffering off; proxy_cache off; proxy_set_header X-Accel-Buffering no; }

如果你用的不是Nginx而是其他网关,也要记得处理同样的缓冲逻辑。判断自己是不是踩了这个坑很简单:用curl -N直接请求后端API,如果curl能收到流而浏览器收不到,那基本就是中间层缓冲在作怪。另外,如果你用了CDN刷新,也注意有些CDN会缓冲SSE响应,生产环境尽量只用CDN缓存静态资源,API请求绕开CDN直连源站。

5.4 最后的调试小技巧:curl是你的好朋友

趁结尾再分享一个我个人的调试习惯。每次对接大模型API,我基本不用前端直接测,而是先用curl模拟请求,把上游返回的原始格式看清楚。这样能明确责任边界——是上游返回格式不对,还是我前端解析出了问题。

curl -N https://你的大模型API域名/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer 你的_API_Key" \ -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"你好"}],"stream":true}'

-N参数的意思是禁用curl自身的缓冲,让它把流式内容实时打印出来。如果这行命令能正常输出一段段带data:前缀的文本,说明上游没问题,接下来再从前端、代理、网关逐层排查。反过来,如果curl这里就已经是空响应或报错,那就不用再在前端代码里反复折腾了。

坦白讲,前端做AI应用开发和传统前端最大的区别,不是技术栈换了,而是思维模式换了:以前我们处理的是静态数据,渲染完就结束了;现在我们要处理的是“持续生成的、有上下文、有随机性的数据”,需要时刻想着流式状态、历史约束和异常恢复。很多坑只有自己踩一遍才能真正理解。这篇算是把地基打完了,下一篇我们再来聊聊怎么给AI助手加上工具调用(Function Calling),让它不止会聊天,还能帮你查天气、查数据库、操作外部系统。对前端来说,那才是真正打开AI应用开发大门的时刻。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 8:15:14

LeetCode 110:平衡二叉树的递归后序遍历核心解法与工程启发

LeetCode 110这道平衡二叉树题目,我愿称它为“树结构入门体检单”。你去看大厂的笔试面试题单,几乎每一份都会把这道题放在二叉树板块的前十题里。它看起来就是判断一棵树是不是平衡的,但真正动手写的时候,你会发现不少人在递归边…

作者头像 李华
网站建设 2026/9/10 8:12:32

已读不回、语音条与撤回:即时通讯如何重塑我们的社交方式

你有没有过这样的瞬间:消息发出去了,屏幕显示“已读”,对方却像人间蒸发;手机明明满格信号,电话却怎么都拨不出去;对话框上方飘着一行“对方正在输入…”,几分钟后却一个字都没蹦出来。这些细碎…

作者头像 李华
网站建设 2026/9/10 8:06:51

愚人节课堂设计:用体验式教学提升中小学生信息辨别力

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 8:06:49

网站SEO问题快速诊断:从收录异常到排名下滑的排查思路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 8:06:48

DeepSeek V4接入Claude Code实操指南:配置步骤与避坑经验

最近后台和群里被同一个问题刷屏:“DeepSeek V4 已经能接进 Claude Code 了吗?”我一开始以为又是哪个营销号在炒冷饭,点进去才发现不光是新手,连不少老玩家都在问。大家的意思很明确:Claude Code 写代码确实爽&#x…

作者头像 李华