第七篇:大模型API调用——从Token到流式输出
第一篇Embedding与向量语义——大模型是怎样“理解”文字的第二篇Transformer的核心思想——Attention机制直观理解第三篇大模型为什么会有“幻觉”——从训练方式到推理局限第四篇Prompt Engineering——从随意提问到工程化调用第五篇RAG检索增强生成——让大模型学会“开卷作答”第六篇大模型的“记忆”——从上下文窗口到会话管理第七篇大模型API调用——从Token到流式输出第八篇LangChain不是“套壳”——它解决了什么实际问题前言在前面六篇文章中我们从Embedding一路拆解到RAG和会话管理。但这些技术最终都要落到一个具体的操作上——调用大模型API。你可能会觉得“调用API不就是发个HTTP请求吗有什么好讲的” 但真正做项目时你会发现一堆问题Token是什么为什么按Token计费Temperature设多少合适为什么我的SSE流式输出在Nginx后失效了这些问题每一个都能在面试中被追问。而且你的简历上写了SSE流式输出面试官大概率会问一句“SSE和WebSocket有什么区别你项目里为什么选SSE” 本文就是帮你准备好这些问题的完整答案。本文核心问题Token是什么为什么大模型按Token计费而不是按字数一个中文字等于几个Token怎么估算API调用成本Temperature和Top-p分别控制什么你的课程问答项目设的多少为什么流式输出和普通请求有什么区别用户体验的差异在哪SSE和WebSocket各自的原理和适用场景你为什么选SSESSE在Nginx反向代理后失效怎么办proxy_buffering off做了什么API密钥安全怎么保障生产环境有哪些防护手段读完本文你将对大模型API调用的每个参数和设计决策都有清晰的解释能力。一、Token是什么——大模型的计费单位疑问为什么大模型按Token计费而不是按字数或者按次回答因为Token是大模型理解和生成文本的最小单位它是模型内部计算量的直接反映。1.1 Token的本质Token是大模型处理文本的基本单元。一个Token可以是一个完整的单词、一个汉字、一个标点符号、或者一个词的一部分我喜欢学习Java → Token化 → [我, 喜欢, 学习, Java] ChatGPT is amazing → Token化 → [Chat, G, PT, is, amazing]为什么英文单词可能被拆成多个Token因为大模型的词表大小是有限的通常是几万到几十万个。常见词如is“the”整个作为1个Token低频词如ChatGPT会被拆成更小的子词单元。这样做的好处是遇到没见过的词也能处理而不需要无限的词汇表。1.2 为什么按Token计费大模型的计算量和Token数量直接相关。生成一个Token需要做一次完整的神经网络前向传播计算。输入1000个Token模型就要处理1000次输出100个Token模型就要依次生成100次。Token数量直接决定了GPU算力的消耗。按字数计费无法反映实际的模型计算开销一个复杂概念可能需要100个Token来精确表达一个简单陈述可能只需要20个Token两者的计算成本是5倍之差但对用户来说都说了一句话。按Token计费时用户可以直接控制成本——减少不必要的上下文输入、限制输出长度都能降低费用。这种透明度让成本更加可控。1.3 中英文Token换算内容Token数换算1个英文字母/标点~0.3 Token3个字≈1Token1个英文单词~1.3 Token1单词≈1Token1个常见汉字~0.5-1 Token1汉字≈1Token1000字中文文章~1500 Token—1000单词英文文章~1300 Token—粗略估算中文约1个字≈1个Token英文约1个单词≈1个Token。在做API成本预算时这个比例就够用了不需要精确到小数位。1.4 如何看自己的Token消耗可以直接在OpenAI官方提供的Tokenizer工具中输入文本它会明确告诉你这段文本的Token数量。也可以安装tiktoken这个官方Python库用代码自动统计Token数量再自动计算预估费用。二、Temperature和Top-p——控制输出的随机性疑问Temperature和Top-p都是控制输出的参数它们有什么区别你的项目是怎么设的回答两者目标相同但作用方式不同。Temperature是调节概率分布的陡峭程度Top-p是限制候选词的范围。2.1 Temperature——温度模型在生成下一个Token时对每个可能的词都有一个概率。Temperature决定了这个概率分布有多尖锐低温度T0.1今天天气→ 真:85%很:10%还:3%太:2%→ 几乎一定选真→ 输出真不错高温度T1.0今天天气→ 真:50%很:25%还:15%太:10%→很和还也有机会被选中 → 更富变化温度越低越保守越不容易产生幻觉。温度越高越有创意越容易产生幻觉。2.2 Top-p——核采样Top-p决定了模型在生成时考虑多少候选词。p0.1表示只考虑累积概率达到10%的最可能的候选词p0.9表示考虑累积概率达到90%的更广范围的候选词。Top-p从候选池大小来控制多样性和Temperature从概率分布陡峭度来控制形成互补。两者的关系Temperature是调节概率分布Top-p是调节候选词数量。实际使用中可以单独调整一个也可以联动调整低Temperature低Top-p可以强制确定性输出低Temperature高Top-p在保持主题稳定的同时允许更多样的表达。对于不熟悉模型行为的新手建议先固定Top-p调温度——这样只有一个变量需要关注更容易找到适合的参数组合。2.3 课程问答项目的设置OpenAIopenAIOpenAI.builder().temperature(0.3)// 偏确定性减少幻觉.topP(0.8)// 适度保留候选.maxTokens(500)// 输出上限配合Prompt不超过300字.build();Temperature设0.3的原因课程问答是知识问答不是创意写作。回答应该稳定准确而不是每次都不一样。0.3在确定性和避免重复啰嗦之间找到了平衡——足够低以减少编造事实的风险但又不是0非完全固定保留了回答措辞的多样性学生不会觉得每次都在读同一句话。Top-p设0.8的原因课程内容涉及大量专业术语。如果Top-p设得太低某些专业词汇可能被排除在候选池之外模型找不到合适的表达就只能用更泛化的词替代长期来看会稀释回答的专业感。0.8给了模型足够的词汇空间来选择准确的技术表达。maxTokens设500的原因配合Prompt中的回答不超过300字。这个限制是一种成本兜底——即使Prompt约束失效了API层面的硬限制也能防止一次性输出数千Token账单不会失控。三、流式输出 vs 普通请求疑问普通请求和流式输出有什么区别为什么不直接用普通HTTP请求回答普通请求是等全部生成完再返回流式输出是生成一个字就返回一个字。对用户来说体验差异巨大。3.1 普通请求的体验用户提问Java线程池有哪些参数 时间线 0.0s - 发送请求 0.5s - RAG检索 1.0s - 开始生成 2.5s - 生成完毕500字的回答 2.6s - 返回完整回答 用户的体感点发送 → 等2.6秒 → 突然出现一大段文字3.2 流式输出的体验用户提问Java线程池有哪些参数 时间线 0.0s - 发送请求 0.5s - RAG检索 0.8s - 首个Token生成 → 前端显示✨ 正在思考中... 1.0s - 开始逐字显示内容 2.5s - 生成完毕 用户的体感点发送 → 0.8秒就有反馈 → 看着文字逐字出现完全不觉得在等实际生成速度没有变化但用户的感知等待时间从2.6秒降到了0.8秒。3.3 技术实现对比维度普通请求SSE流式输出连接方式请求-响应一次性长连接持续推送用户感知延迟等到全文本返回首Token延迟通常1秒前端实现异步请求即可需处理EventSource流网络适应性任何HTTP环境都支持需反向代理支持适用场景短回答、对延迟不敏感长回答、需快速反馈四、SSE vs WebSocket——为什么选SSE疑问SSE和WebSocket都能做流式输出有什么区别为什么不选WebSocket回答SSE是单向的服务端→客户端WebSocket是双向的。AI回答场景是单向的——服务端生成内容推给前端前端不需要反向推送——SSE比WebSocket更轻量更匹配。4.1 本质区别SSEServer-Sent Events 服务端 ──→ 客户端 单向流 基于HTTP协议 客户端向服务端发起请求后持续保持连接 服务端不断发送数据客户端接收 WebSocket 服务端 ←→ 客户端 双向通道 独立协议ws://, wss:// 需要一次握手升级从HTTP升级到WebSocket 双方都可以主动发消息4.2 为什么AI流式输出适合SSEAI回答场景中通信模式非常明确用户发一个问题然后AI开始生成并持续推送最后结束。整个过程只有服务端向客户端的单向推送——生成的内容不需要双向实时交互用户不会在回答生成到一半时插入新的指令一个新指令本身就是新的一轮对话不是中断当前生成流。SSE的优势在于基于标准HTTP协议不需要WebSocket的握手升级过程浏览器原生支持EventSource API前端只需几行代码就能连接Nginx等反向代理天然支持HTTP协议配置相对简单SSE连接断开后浏览器会自动重连不需要手动实现重试逻辑。WebSocket的优势在于双向通信——但AI流式输出场景根本不需要这个能力WebSocket反而增加了一层协议切换的复杂度和一个手动实现断线重连的负担。4.3 一句话总结SSE就是为服务端持续推送数据这个场景设计的。AI流式输出恰好就是这个场景。WebSocket适合聊天室SSE适合AI回答。在面试时这句话比背区别表更有说服力。4.4 前端代码对比// SSE更简单consteventSourcenewEventSource(/api/ai/chat/stream?questionxxx);eventSource.onmessage(event){answerDiv.innerTextevent.data;// 追加显示};eventSource.onerror()eventSource.close();// 异常关闭// WebSocket更复杂constwsnewWebSocket(wss://example.com/ws/chat);ws.onopen()ws.send(JSON.stringify({question:xxx}));ws.onmessage(event){constdataJSON.parse(event.data);if(data.isComplete){ws.close();// 手动关闭}else{answerDiv.innerTextdata.token;}};// 还需要处理重连逻辑、心跳保活...SSE在前端几行代码搞定这就是AI流式输出的正确打开方式。五、Nginx反向代理的SSE配置陷阱疑问为什么我的SSE流式输出开发环境正常部署到Nginx后变成一次性返回全部内容回答这是因为Nginx默认对HTTP响应做缓冲——它会等后端把完整响应全部生成完再一次性推给前端。可SSE需要的不是等全部完成而是边生成边发送。5.1 缓冲是如何破坏SSE的没有Nginx缓冲 后端生成J → 立即发给客户端 → 前端显示J 后端生成a → 立即发给客户端 → 前端显示Ja 后端生成v → 立即发给客户端 → 前端显示Jav ...用户看到逐字输出 有Nginx缓冲默认 后端生成J → Nginx收到存起来 后端生成a → Nginx收到存起来 ...全部生成完毕 Nginx把整段Java线程池...一次性发给客户端 ...用户等了2秒然后看到一整段文字5.2 解决方案关闭缓冲在Nginx配置中定位到你的AI接口路径添加proxy_buffering off;location /api/ai/ { proxy_pass http://backend-server; proxy_buffering off; # 关闭缓冲 proxy_cache off; # 关闭缓存 }proxy_buffering off告诉Nginx后端返给我什么我就立刻转发给客户端不等。这样SSE的效果就能正确传递给用户。5.3 如果还需要优化长连接稳定性location /api/ai/ { proxy_pass http://backend-server; proxy_buffering off; proxy_cache off; proxy_http_version 1.1; # HTTP/1.1支持长连接 proxy_set_header Connection ; # 清除默认的close连接头 proxy_read_timeout 300s; # 长连接超时5分钟AI生成可能较慢 }六、API密钥安全疑问大模型API密钥放在哪提交代码到GitHub会不会泄露回答这是生产环境必须处理的问题。API密钥泄露可能导致严重的经济损失。6.1 必须遵守的原则原则做法后果如果不遵守不硬编码密钥密钥不写在代码文件里代码提交到公开仓库密钥立即泄露环境变量隔离密钥放在环境变量或配置中心不同环境共用同一密钥无法独立计费和控制前端永远不存密钥API密钥不出现在前端代码中浏览器开发者工具可以查看所有前端代码和请求头生产环境加IP白名单API平台设置只允许服务器IP调用密钥被泄露后仍有被滥用的窗口期6.2 项目中的实践// ❌ 绝对不要写死OpenAIopenAIOpenAI.builder().apiKey(sk-abc123def456...)// 这一行提交到Git就完蛋.build();// ✅ 从环境变量读取Value(${openai.api-key})privateStringapiKey;OpenAIopenAIOpenAI.builder().apiKey(apiKey).build();# application-dev.yml本地开发openai:api-key:${OPENAI_API_KEY}# 从系统环境变量读取model:gpt-3.5-turbo# application-prod.yml生产环境openai:api-key:${OPENAI_API_KEY}# 从K8s Secret或配置中心读取model:gpt-4七、课程问答项目的完整API调用方案疑问你的课程问答项目中API调用是怎么设计的整体的技术参数和架构是什么7.1 技术概览参数/组件设置/选择原因大模型GPT-3.5-TurboDemo阶段性价比最高后续可平滑升级到GPT-4流式输出SSE课程答疑需要较长回答用户不应该等Temperature0.3知识问答需确定性减少幻觉Top-p0.8保留足够词汇多样性处理专业术语maxTokens500API层面的成本兜底防止一次调用耗光预算API密钥环境变量安全第一代码提交不走私Nginxproxy_buffering off保证SSE的正确行为前端EventSource API原生支持SSE几行代码搞定7.2 后端完整实现RestControllerRequestMapping(/api/ai)publicclassChatController{GetMapping(value/chat/stream,producesMediaType.TEXT_EVENT_STREAM_VALUE)publicFluxStringchatStream(RequestParamStringquestion){returnFlux.create(sink-{// 1. RAG检索相关文档ListDocumentdocsvectorStore.similaritySearch(question,5);// 2. 拼接Prompt包含对话历史和检索文档StringpromptbuildPrompt(question,docs,getChatHistory());// 3. 调用大模型流式接口openAI.chatCompletion(prompt,newStreamCallback(){OverridepublicvoidonToken(Stringtoken){sink.next(token);// 每生成一个token就推送给前端}OverridepublicvoidonComplete(){sink.complete();saveChatHistory(question,fullAnswer);// 保存对话历史}OverridepublicvoidonError(Throwablee){sink.error(e);}});});}}7.3 前端完整实现functionaskAI(question){consteventSourcenewEventSource(/api/ai/chat/stream?question${encodeURIComponent(question)});constanswerDivdocument.getElementById(ai-answer);answerDiv.innerHTML✨ AI正在思考...;eventSource.onmessage(event){if(event.data[DONE]){eventSource.close();// 完成关闭连接return;}answerDiv.innerTextevent.data;// 逐字追加};eventSource.onerror(){answerDiv.innerText\n[连接中断请重试];eventSource.close();};}总结Token是大模型处理文本的最小单位按Token计费是因为它直接反映GPU计算开销。中文约1字≈1Token这对做预算是极强的参考Temperature控制随机性Top-p控制候选范围。课程问答设置Temperature0.3知识问答需确定性Top-p0.8保证专业术语在候选池中流式输出提升用户体验不是生成完再返回而是逐字推送。用户感知到的首次响应时间显著缩短SSE和WebSocket的区别SSE单向、轻量、原生支持自动重连WebSocket双向、需握手、需手动处理断线重连。AI回答场景是单向推送SSE天然匹配Nginx缓冲是SSE的隐形杀手需配置proxy_buffering off必要时添加长连接超时配置API密钥安全永远不在代码中硬编码、永远不暴露在前端、通过环境变量或配置中心注入、生产环境加IP白名单课程问答项目的技术参数总结GPT-3.5-Turbo SSE流式 Temperature0.3 Top-p0.8 maxTokens500 环境变量管理密钥下一篇预告AI理论学习八——LangChain不是“套壳”它解决了什么实际问题。拆解Chain、Agent、Tool各自的设计意图LangChain的优势和局限性以及为什么有人觉得它是“过度封装”。这一篇将同时作为AI理论学习专栏的收官文章帮你形成对AI应用开发工具链的独立判断。