探索AI助手智能体开发实战
探索AI助手智能体开发实战
从后端调用大模型、流式传输原理、前端实时渲染,到Prompt工程和RAG检索增强,全面拆解AI智能体的核心技术栈。
本文用大白话讲透原理,助你轻松应对技术面试和架构设计。
第一部分:整体架构鸟瞰
一句话说清楚整个流程:用户在网页上打字提问 → 前端POST请求发给后端 → 后端调用AI大模型(stream模式) → AI一块块吐数据 → 后端用SSE流推给前端 → 前端把Markdown转成HTML展示 → 用户看到"AI正在打字"的效果。
在动手之前,先搞清楚整个系统的数据流转路径。下面这张时序图展示了从用户提问到AI回答的完整过程:
{messages: [...]} 后端->>AI大模型: 3. 调用API(stream: true) Note over AI大模型: AI开始生成内容 loop 流式返回 AI大模型-->>后端: 4. 返回chunk("闭") 后端-->>前端: 5. SSE推送 data: {"content":"闭"} 前端-->>用户: 6. 实时渲染显示 AI大模型-->>后端: 返回chunk("包") 后端-->>前端: SSE推送 data: {"content":"包"} 前端-->>用户: 继续渲染 end AI大模型-->>后端: 7. [DONE] 结束标记 后端-->>前端: 8. 关闭SSE连接 前端-->>用户: 9. 完整展示
| 优势 | 说明 | 技术价值 |
|---|---|---|
| 用户体验优化 | 边生成边显示,不用干等几秒甚至几十秒 | 降低用户跳出率,提升产品留存 |
| 避免超时问题 | AI生成慢,一次性等完容易触发HTTP超时(默认30-60s) | 保障服务稳定性 |
| 内存优化 | 不需要在内存中缓存完整响应 | 支持更高并发,降低服务器成本 |
| 实时性 | TTFB(首字节时间)可控制在1-2秒内 | 满足实时交互场景需求 |
大白话解释:这就像你在微信上跟朋友聊天,朋友一边想一边打字,你能看到"对方正在输入...",然后文字一点点冒出来。AI智能体也是这个原理,不是写完整句话才发,而是想一个字发一个字。
第二部分:后端如何调用AI模型
后端是整个系统的枢纽,承担三个核心职责:接收前端请求、调用AI模型、转发流式响应。
2.1 流式请求 vs 普通请求
首先要搞清楚两种请求方式的本质区别:
| 对比维度 | 普通请求(stream: false) | 流式请求(stream: true) |
|---|---|---|
| 数据返回方式 | 等AI全部生成完,一次性返回 | AI边生成边返回,分块(chunk)传输 |
| 响应时间 | 5-30秒(取决于内容长度) | TTFB < 2秒,持续流式输出 |
| 内存占用 | 需缓存完整响应(可能几MB) | 只需缓存当前chunk(几KB) |
| 超时风险 | 高(容易触发HTTP超时) | 低(持续有数据传输) |
| 适用场景 | 内容短、不在乎等待时间 | 对话场景、内容长、用户体验要求高 |
2.2 调用AI的核心参数
以DeepSeek为例(兼容OpenAI API格式),关键就是一个参数:stream: true。
// 标准的流式请求参数(OpenAI格式,DeepSeek兼容)
{
"model": "deepseek-chat", // 模型选择
"messages": [ // 对话历史(含上下文)
{"role": "system", "content": "你是一名资深前端工程师"},
{"role": "user", "content": "什么是JavaScript闭包?"}
],
"stream": true, // 核心!开启流式
"temperature": 0.7, // 随机性(0-2),越高越发散
"max_tokens": 2000, // 最大生成长度
"top_p": 0.9, // 核采样(0-1),控制多样性
"frequency_penalty": 0.0, // 重复惩罚(-2到2)
"presence_penalty": 0.0 // 主题惩罚(-2到2)
}
- temperature:技术问答用0.3-0.5(准确性优先),创意写作用0.7-1.0(多样性优先)
- max_tokens:预估生成长度,避免浪费(中文约1.5字/token,英文约0.75词/token)
- top_p:通常固定0.9,与temperature二选一调整即可
2.3 Prompt Engineering:让AI听话的艺术
一句话解释:Prompt就是给AI的"任务说明书",你说得越清楚,AI干得越好。
核心技巧对比
| 技巧 | 作用 | 代码示例 |
|---|---|---|
| System Prompt | 给AI设定角色和行为规范 | {"role": "system", "content": "你是一名资深架构师,擅长系统设计"} |
| Few-Shot Learning | 给AI看2-3个输入输出示例 | 先给示例:"输入:优化性能 → 输出:使用虚拟列表" |
| Chain of Thought | 让AI逐步推理,提升准确性 | "请先分析问题,列出思路,再给出答案" |
| 输出格式控制 (程序处理场景) |
让AI输出结构化文本(如JSON),便于程序解析 注:对话场景不需要控制,AI默认用Markdown文本 |
"返回JSON格式:{\"answer\": \"...\", \"confidence\": 0.9}" |
好Prompt vs 差Prompt
// 差的Prompt(模糊、缺乏上下文)
{
"role": "user",
"content": "帮我优化这段代码"
}
// 好的Prompt(角色清晰、需求明确、有约束)
{
"role": "system",
"content": "你是一名资深前端工程师,精通React性能优化。请遵循:\n1. 指出具体问题\n2. 给出优化方案和原理\n3. 提供可运行的代码示例"
},
{
"role": "user",
"content": "这段React代码有性能问题,每次输入都触发全局重渲染:\n\n```jsx\nfunction App() {\n const [text, setText] = useState('');\n const expensiveList = items.map(...);\n return ...;\n}\n```\n\n请:\n1. 分析性能瓶颈\n2. 用useMemo/useCallback优化\n3. 给出优化后代码"
}
场景1:对话展示(不控制格式)
- Prompt:正常提问,不要求格式
- AI输出:Markdown格式的文本(因为可读性好)
- 前端处理:用marked.js转HTML,展示给用户
场景2:程序处理(控制格式)
- Prompt:明确要求"返回JSON格式:{...}"
- AI输出:JSON格式的文本
- 前端处理:用JSON.parse()解析,提取数据使用
核心理解:AI永远只输出纯文本,后端原样转发,前端根据文本格式决定怎么处理(Markdown转HTML,JSON解析)。
- 坑1:上下文污染 - 历史对话太长会稀释关键信息,建议定期清理无关上下文
-
坑2:指令冲突 - System Prompt说"简洁回答",User Prompt说"详细解释",AI会懵
默认行为:AI通常会折中处理,输出既不够简洁也不够详细的结果
解决方案:- 方案1:明确优先级 - 在System中说明"默认简洁,但用户要求详细时以用户为准"
- 方案2:正确分工 - System只定角色和风格,不设死规则;User负责具体任务要求
- 方案3:覆盖式说明 - User Prompt里明确说"忽略简洁要求,详细解释"
- 坑3:过度约束 - 限制太多反而束缚AI能力,适度留白让AI发挥
2.4 后端如何处理AI的流式响应
AI开启stream后,返回的不是JSON对象,而是SSE格式的文本流:
// AI返回的原始流(每块以 data: 开头,双换行分隔)
data: {"choices":[{"delta":{"content":"闭"}}]}
data: {"choices":[{"delta":{"content":"包"}}]}
data: {"choices":[{"delta":{"content":"是"}}]}
data: [DONE]
后端的核心逻辑:当"管道工",把AI的水管接到前端的水管。
关键处理步骤:
- 逐块读取:后端收到AI的一块数据,立即往下传,不等待完整响应
- 错误处理:AI超时/断连时,向前端发送错误标记并关闭连接
- 背压控制:如果前端处理慢,后端需要暂停读取AI流,避免内存溢出
第三部分:后端如何推送数据给前端
后端收到AI的chunk后,需要实时推给前端。有三种技术方案可选:
3.1 三种实时推送方案对比
| 方案 | 原理 | 优点 | 缺点 | AI对话适配度 |
|---|---|---|---|---|
| 轮询(Polling) | 前端定时发请求问"有数据吗" | 简单,兼容性好 | 延迟高,浪费资源(大量空请求) | 不适合(延迟明显,用户体验差) |
| WebSocket | 双向全双工通信 | 实时性强,双向通信 | 协议复杂,需处理重连/心跳 | △ 可以用,但大材小用 |
| SSE | 服务器单向推送 | 简单,自动重连,基于HTTP | 只能服务器→客户端 | 最适合(需求匹配度100%) |
- 需求匹配:AI对话是典型的"服务器推送"场景,不需要双向通信
- 协议简单:基于HTTP,不需要升级协议,防火墙/代理兼容性好
- 自动重连:浏览器原生支持断线重连,不需要手写逻辑
- 开发效率高:后端只需设置响应头,前端用EventSource/fetch即可
3.2 SSE的数据格式规范
SSE有严格的协议要求,后端必须遵守:
// 响应头(必须)
Content-Type: text/event-stream // 告诉浏览器这是SSE流
Cache-Control: no-cache // 禁止缓存
Connection: keep-alive // 保持连接
Access-Control-Allow-Origin: * // 跨域支持(可选)
// 数据格式(每条消息格式)
data: {"content": "你好"} // data: 开头
// 空行(双换行)表示消息结束
data: {"content": "!"}
data: [DONE] // 约定的结束标记
- 错误1:忘记双换行 → 浏览器认为消息未结束,一直等待
- 错误2:响应头写错(如Content-Type写成application/json)→ 浏览器无法识别SSE流
- 错误3:data中包含换行符 → 需转义为
\n或拆成多个data行
3.3 错误处理与容错机制
生产环境中,必须考虑各种异常情况:
| 异常场景 | 后端处理策略 | 前端表现 |
|---|---|---|
| AI API超时 | 设置超时时间(如30s),超时后发送错误消息:data: {"error": "TIMEOUT"} |
显示"AI响应超时,请重试" |
| AI API异常 | 捕获异常,发送:data: {"error": "API_ERROR", "message": "..."} |
显示错误提示,提供重试按钮 |
| 用户主动停止 | 前端发送stop请求,后端中断AI调用,发送:data: [STOPPED] |
显示"已停止生成" |
| 网络断连 | 检测到连接断开,清理资源,取消AI调用 | SSE自动重连,或提示用户刷新 |
第四部分:前端如何接收和渲染
前端要做两件核心的事:接收SSE流 + 把Markdown实时转成漂亮的HTML。
4.1 接收SSE流的两种方式
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| EventSource | 原生API,代码简单 | 只能GET请求,不能带请求体,不能自定义Headers | 简单场景,无需POST |
| fetch + ReadableStream | 灵活,支持POST,可自定义Headers | 代码稍复杂,需手动解析SSE格式 | AI对话(需POST发送消息) |
AI对话通常用POST发送消息(消息内容在body里),所以一般选fetch + ReadableStream。
4.2 Markdown转HTML的挑战
AI对话场景通常返回Markdown格式的文本(因为训练数据中技术文档多用Markdown,AI默认选择这种方式保证可读性),前端需要实时转换成HTML。
注:如果在Prompt中明确要求其他格式(如JSON),AI会按要求输出对应格式的文本。但对话展示场景通常不控制格式,让AI用默认的Markdown即可。
常用的Markdown解析库:
marked.js:轻量快速(~20KB),最常用,API简单markdown-it:功能更强,插件丰富(支持表格、脚注等),体积稍大(~40KB)
4.3 流式渲染的核心难点
流式传输中,Markdown可能处于"写了一半"的状态:
- 代码块只有开头的
```python,没有结尾的``` - 列表写了一半:
- 第一项\n- 第二(第二项未写完) - 链接语法不完整:
[点击这里](http(URL未完成)
解决方案:
- 渲染前检测未闭合的代码块、引用等,临时补上结束标记
- 下一块数据来了,移除临时标记,重新渲染完整内容
- 或者使用支持"部分Markdown"解析的库(如marked的partial选项)
4.4 性能优化:防抖渲染
AI吐字很快,可能每隔几十毫秒就来一块数据。如果每收到一块就渲染一次,DOM操作太频繁,页面会卡。
| 优化策略 | 原理 | 效果 |
|---|---|---|
| 防抖 + rAF渲染 | 收集数据后,在浏览器下一帧渲染(~16ms) | 和浏览器刷新同步,流畅且性能好 |
| 虚拟滚动 | 内容太长时,只渲染可见区域 | 内容超过1000行时显著提升性能 |
| 代码高亮延迟 | 先渲染内容,代码高亮放到空闲时异步做 | 避免高亮阻塞UI,提升响应速度 |
防抖 + rAF渲染示意代码(核心思路,非完整代码):
let buffer = ""; // 内容缓冲区
let rafId = null; // requestAnimationFrame ID
function onReceiveChunk(chunk) {
buffer += chunk; // 先攒起来,不立即渲染
// 如果还没请求渲染,就请求一次
if (!rafId) {
rafId = requestAnimationFrame(() => {
// 在浏览器下一帧渲染(约16.6ms)
const html = marked.parse(buffer);
domElement.innerHTML = html;
highlightCode(); // 代码高亮
rafId = null; // 重置标记
});
}
}
- 和浏览器同步:浏览器60fps = 16.6ms一帧,rAF在下一帧执行,不会浪费渲染
- 自动节流:多次数据到达只触发一次渲染(在下一帧),自动合并更新
- 流畅体验:16.6ms远比50ms快,用户看到的是接近"逐字"的流畅效果
- 性能优先:浏览器会在最优时机执行,且标签页不可见时自动暂停
第五部分:核心技术点深度思考
核心思路(从三个维度分析):
- 用户体验:TTFB从15-30秒降到1-2秒,用户边等边看,心理感知更快
- 技术稳定性:避免HTTP超时(默认30-60s),AI生成长内容不会触发超时
- 资源优化:不需要在内存中缓存完整响应(可能几MB),降低内存峰值,支持更高并发
延伸思考:流式架构体现了"渐进式加载"理念,和图片懒加载、代码分割等前端优化思想一脉相承。
我的选型思路:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| AI对话、通知推送 | SSE | 单向推送,简单可靠 |
| 在线游戏、协同编辑 | WebSocket | 需要双向实时通信 |
| 数据更新不频繁(分钟级) | 轮询 | 简单,无需维护长连接 |
实践经验:SSE有自动重连机制省心;WebSocket要自己处理心跳保活;轮询适合用指数退避策略减少无效请求。
核心概念:
- Token定义:AI处理文本的基本单位,不等于字符。中文约1.5字/token,英文约0.75词/token
- 计费规则:输入Token + 输出Token分别计费。例如DeepSeek:输入¥2/百万token,输出¥8/百万token
- 我的优化策略:
- 精简System Prompt,去掉冗余描述
- 控制上下文长度,定期清理历史对话
- 用max_tokens限制输出长度
实际成本计算:举例一次对话 - 输入500 tokens + 输出1500 tokens = 500×0.000002 + 1500×0.000008 = ¥0.013(约1.3分钱)
我遇到的瓶颈:
- 频繁DOM操作:每收到chunk就渲染 → 解决方案:用requestAnimationFrame攒一批在下一帧渲染
- 代码高亮耗时:highlight.js处理大代码块可能几十ms → 解决方案:改用Web Worker异步高亮
- 长文档卡顿:内容超过1000行,DOM节点过多 → 解决方案:虚拟滚动(react-window)
进一步思考:可以用requestIdleCallback在浏览器空闲时处理代码高亮,或用content-visibility: auto让浏览器自动优化渲染。
我的实现方案:
- 存储位置选择:
- MVP阶段:前端localStorage(简单快速,适合快速验证)
- 生产环境:后端Redis(按session_id存,TTL 30分钟,支持多端同步)
- 上下文裁剪策略:
- 保留最近N轮对话(如最近10轮,避免上下文过长)
- 或保留Token数限制内的对话(如最近4000 tokens)
- System Prompt始终保留(保持AI角色一致)
- 清理时机:用户点击"清空对话"、session超时、切换话题时主动清理
我的防护措施:
- Markdown转HTML时:用支持XSS防护的库(如marked开启sanitize选项,或用DOMPurify二次清洗)
- 禁止危险标签:过滤
<script>、<iframe>、onerror等可执行脚本 - CSP策略:设置Content-Security-Policy响应头,从根源禁止内联脚本执行
- 用户输入过滤:前端也要过滤用户输入的特殊字符,防止Prompt注入攻击
实践经验:AI有时会幻觉(hallucination)生成恶意代码示例,展示前必须二次校验,特别是代码块内容。
我的容错方案:
- SSE自动重连:浏览器会自动重连,服务器可通过
id字段支持断点续传(记录上次传到哪) - 前端用户体验:检测到连接断开,显示"连接中断,正在重连...",避免用户焦虑
- 后端幂等性设计:重连时用session_id + message_id去重,避免重复处理同一条消息
- 降级方案:重连失败3次后,降级为轮询或提示用户刷新页面
我监控的关键指标:
| 维度 | 指标 | 健康范围 |
|---|---|---|
| 性能 | TTFB、总响应时间、Token生成速率 | TTFB < 2s, 生成速率 > 20 tokens/s |
| 稳定性 | 错误率、超时率、重试率 | 错误率 < 1%, 超时率 < 2% |
| 成本 | 平均Token消耗、单次对话成本 | 根据业务设定阈值 |
| 质量 | 用户满意度、回答准确率 | 满意度 > 80%, 准确率 > 90% |
我的理解:
- Prompt:直接把知识写进提示词 → AI依赖训练数据 + 提示词中的信息回答
- RAG:先检索相关文档,再把文档注入Prompt → AI基于实时检索的私有数据回答
选型依据:
- 知识量小(几百字)、不常变:用Prompt(如"你是客服,产品价格是99元")
- 知识量大(几千页文档)、经常更新:用RAG(如企业知识库、产品手册)
实践体会:RAG最大的价值是减少AI幻觉,因为回答有真实文档依据,可追溯可验证。
我的优化方向:
- 优化Prompt:角色设定清晰、给示例、要求逐步推理(Chain of Thought)
- 调整参数:降低temperature(0.3-0.5),减少随机性,提升确定性
- 引入RAG:让AI基于真实文档回答,减少幻觉问题
- 多轮验证:关键答案让AI自我验证:"请检查上面的答案是否有逻辑错误"
- 人工校验:高风险场景(如金融、医疗)必须加人工审核环节,AI辅助而非替代
第六部分:进阶方向(横向学习)
掌握了前面的核心技术,你已经可以做出一个能用的AI助手。下面是两个进阶方向,可以让AI更强大:
6.1 RAG(检索增强生成)
一句话解释:让AI学会你的私有数据。AI原本不知道你公司的业务,通过RAG让它先搜相关文档,再回答。
按段落/章节切分"] B --> C["向量化
转成768维向量"] C --> D[("向量数据库
Pinecone/Milvus")] end subgraph online["在线阶段"] E["用户提问
报销流程"] --> F["问题向量化"] F --> G["相似度检索
Top-K文档"] G --> D D --> H["相关文档片段"] H --> I["注入Prompt"] I --> J["AI生成答案"] end style A fill:#fef3c7 style J fill:#d1fae5
核心流程:
- 离线阶段:把文档切成小块(如500字一块),用Embedding模型转成向量(一串数字),存到向量数据库(专门存数字、找相似数字的数据库,如Pinecone/Milvus)
- 在线阶段:用户提问时,先把问题向量化,在数据库里搜最相似的3-5个文档块(这里的"3-5"就是Top-K,K=取前几个),把这些文档塞进Prompt,再让AI回答
- 文档切片策略:按段落切 vs 按Token数切 vs 语义切分
- Embedding模型选择:OpenAI text-embedding-3($0.02/百万tokens)、国内用bge-large-zh(开源免费)
- 检索优化:混合检索(向量检索 + 关键词检索),重排序(Rerank)
- 上下文控制:Top-K数量(取前几个最相关的文档)、相似度阈值,避免塞太多无关内容
6.2 MCP协议(Model Context Protocol)
一句话解释:让AI能调用外部工具的协议。像给AI装了手和脚,它可以查数据库、调API、操作文件。
MCP的三大核心概念:
- Resources:暴露资源给AI(如文件列表、数据库表结构)
- Tools:注册工具给AI使用(如查天气、发邮件、执行代码)
- Prompts:预设的提示词模板,快速启动特定任务
应用场景:
- 让AI查询数据库:"帮我查一下上个月的销售额"
- 让AI调用API:"给张三发一封会议通知邮件"
- 让AI操作文件:"把这个报告转成PDF"
- 让AI执行代码:"帮我运行这段Python脚本"
- 权限控制:必须限制AI可调用的工具范围,不能让AI随意执行高危操作
- 参数校验:工具参数需严格校验,防止注入攻击(如SQL注入)
- 审计日志:记录AI的每次工具调用,便于追溯问题
总结
做AI智能体,核心就是搞清楚数据流转的每个环节:
技术路线总结
| 环节 | 关键技术 | 核心要点 |
|---|---|---|
| 后端调AI | stream: true、Prompt Engineering | 开启流式,写好Prompt,处理AI响应流 |
| 后端推前端 | SSE协议 | 设置正确响应头,按SSE格式推送,处理异常 |
| 前端渲染 | fetch + ReadableStream、Markdown转HTML、防抖优化 | 实时接收、实时渲染、性能优化 |
进阶能力图谱
别光看理论,我已经把这套方案落地成产品了:
你可以体验到:
- 和ChatGPT一样流畅的打字效果
- 代码自动高亮、Markdown完美渲染
- 记得你说过的话,多轮对话无压力
- 网络不好也能自动重连,不会断聊
体验地址:AI智能助手