第七篇:大模型API调用——从Token到流式输出

  1. 第一篇:Embedding与向量语义------大模型是怎样"理解"文字的?
  2. 第二篇:Transformer的核心思想------Attention机制直观理解
  3. 第三篇:大模型为什么会有"幻觉"------从训练方式到推理局限
  4. 第四篇:Prompt Engineering------从随意提问到工程化调用
  5. 第五篇:RAG检索增强生成------让大模型学会"开卷作答"
  6. 第六篇:大模型的"记忆"------从上下文窗口到会话管理
  7. 第七篇:大模型API调用------从Token到流式输出
  8. 第八篇:LangChain不是"套壳"------它解决了什么实际问题

前言

在前面六篇文章中,我们从Embedding一路拆解到RAG和会话管理。但这些技术最终都要落到一个具体的操作上------调用大模型API

你可能会觉得:"调用API不就是发个HTTP请求吗?有什么好讲的?" 但真正做项目时,你会发现一堆问题:Token是什么?为什么按Token计费?Temperature设多少合适?为什么我的SSE流式输出在Nginx后失效了?

这些问题,每一个都能在面试中被追问。而且,你的简历上写了"SSE流式输出",面试官大概率会问一句:"SSE和WebSocket有什么区别?你项目里为什么选SSE?" 本文就是帮你准备好这些问题的完整答案。

本文核心问题:

  1. Token是什么?为什么大模型按Token计费而不是按字数?
  2. 一个中文字等于几个Token?怎么估算API调用成本?
  3. Temperature和Top-p分别控制什么?你的课程问答项目设的多少?为什么?
  4. 流式输出和普通请求有什么区别?用户体验的差异在哪?
  5. SSE和WebSocket各自的原理和适用场景?你为什么选SSE?
  6. SSE在Nginx反向代理后失效怎么办?proxy_buffering off做了什么?
  7. 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 Token 3个字≈1Token
1个英文单词 ~1.3 Token 1单词≈1Token
1个常见汉字 ~0.5-1 Token 1汉字≈1Token
1000字中文文章 ~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决定了这个概率分布有多"尖锐":

java 复制代码
低温度(T=0.1):
  "今天天气" →
    真: 85%
    很: 10%
    还: 3%
    太: 2%
  → 几乎一定选"真" → 输出"真不错"

高温度(T=1.0):
  "今天天气" →
    真: 50%
    很: 25%
    还: 15%
    太: 10%
  → "很"和"还"也有机会被选中 → 更富变化

温度越低=越保守=越不容易产生幻觉。温度越高=越有创意=越容易产生幻觉。

2.2 Top-p------核采样

Top-p决定了模型在生成时考虑多少候选词。p=0.1表示只考虑累积概率达到10%的最可能的候选词;p=0.9表示考虑累积概率达到90%的更广范围的候选词。Top-p从候选池大小来控制多样性,和Temperature从概率分布陡峭度来控制形成互补。

两者的关系 :Temperature是"调节概率分布",Top-p是"调节候选词数量"。实际使用中可以单独调整一个,也可以联动调整:低Temperature+低Top-p可以强制确定性输出,低Temperature+高Top-p在保持主题稳定的同时允许更多样的表达。对于不熟悉模型行为的新手,建议先固定Top-p,调温度------这样只有一个变量需要关注,更容易找到适合的参数组合。

2.3 课程问答项目的设置

java 复制代码
OpenAI openAI = OpenAI.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 本质区别

复制代码
SSE(Server-Sent Events):
  服务端 ──→ 客户端  单向流
  基于HTTP协议
  客户端向服务端发起请求后持续保持连接
  服务端不断发送数据,客户端接收

WebSocket:
  服务端 ←→ 客户端  双向通道
  独立协议(ws://, wss://)
  需要一次"握手升级":从HTTP升级到WebSocket
  双方都可以主动发消息

4.2 为什么AI流式输出适合SSE

AI回答场景中,通信模式非常明确:用户发一个问题,然后AI开始生成并持续推送,最后结束。整个过程只有服务端向客户端的单向推送------生成的内容不需要双向实时交互,用户不会在回答生成到一半时插入新的指令(一个新指令本身就是新的一轮对话,不是中断当前生成流)。

SSE的优势在于:基于标准HTTP协议,不需要WebSocket的握手升级过程;浏览器原生支持EventSource API,前端只需几行代码就能连接;Nginx等反向代理天然支持HTTP协议,配置相对简单;SSE连接断开后浏览器会自动重连,不需要手动实现重试逻辑。WebSocket的优势在于双向通信------但AI流式输出场景根本不需要这个能力,WebSocket反而增加了一层协议切换的复杂度和一个手动实现断线重连的负担。

4.3 一句话总结

SSE就是为"服务端持续推送数据"这个场景设计的。AI流式输出恰好就是这个场景。WebSocket适合聊天室,SSE适合AI回答。

在面试时,这句话比背区别表更有说服力。

4.4 前端代码对比

javascript 复制代码
// SSE(更简单)
const eventSource = new EventSource("/api/ai/chat/stream?question=xxx");
eventSource.onmessage = (event) => {
    answerDiv.innerText += event.data;  // 追加显示
};
eventSource.onerror = () => eventSource.close();  // 异常关闭

// WebSocket(更复杂)
const ws = new WebSocket("wss://example.com/ws/chat");
ws.onopen = () => ws.send(JSON.stringify({ question: "xxx" }));
ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.isComplete) {
        ws.close();  // 手动关闭
    } else {
        answerDiv.innerText += data.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;

nginx 复制代码
location /api/ai/ {
    proxy_pass http://backend-server;
    proxy_buffering off;  # 关闭缓冲
    proxy_cache off;      # 关闭缓存
}

proxy_buffering off 告诉Nginx:后端返给我什么,我就立刻转发给客户端,不等。这样SSE的效果就能正确传递给用户。

5.3 如果还需要优化长连接稳定性

nginx 复制代码
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 项目中的实践

java 复制代码
// ❌ 绝对不要写死
OpenAI openAI = OpenAI.builder()
    .apiKey("sk-abc123def456...")  // 这一行提交到Git就完蛋
    .build();

// ✅ 从环境变量读取
@Value("${openai.api-key}")
private String apiKey;

OpenAI openAI = OpenAI.builder()
    .apiKey(apiKey)
    .build();
yaml 复制代码
# 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-Turbo Demo阶段性价比最高;后续可平滑升级到GPT-4
流式输出 SSE 课程答疑需要较长回答,用户不应该等
Temperature 0.3 知识问答需确定性,减少幻觉
Top-p 0.8 保留足够词汇多样性处理专业术语
maxTokens 500 API层面的成本兜底,防止一次调用耗光预算
API密钥 环境变量 安全第一,代码提交不走私
Nginx proxy_buffering off 保证SSE的正确行为
前端 EventSource API 原生支持SSE,几行代码搞定

7.2 后端完整实现

java 复制代码
@RestController
@RequestMapping("/api/ai")
public class ChatController {
    
    @GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatStream(@RequestParam String question) {
        return Flux.create(sink -> {
            // 1. RAG检索相关文档
            List<Document> docs = vectorStore.similaritySearch(question, 5);
            
            // 2. 拼接Prompt(包含对话历史和检索文档)
            String prompt = buildPrompt(question, docs, getChatHistory());
            
            // 3. 调用大模型流式接口
            openAI.chatCompletion(prompt, new StreamCallback() {
                @Override
                public void onToken(String token) {
                    sink.next(token);  // 每生成一个token就推送给前端
                }
                
                @Override
                public void onComplete() {
                    sink.complete();
                    saveChatHistory(question, fullAnswer);  // 保存对话历史
                }
                
                @Override
                public void onError(Throwable e) {
                    sink.error(e);
                }
            });
        });
    }
}

7.3 前端完整实现

javascript 复制代码
function askAI(question) {
    const eventSource = new EventSource(
        `/api/ai/chat/stream?question=${encodeURIComponent(question)}`
    );
    
    const answerDiv = document.getElementById('ai-answer');
    answerDiv.innerHTML = '✨ AI正在思考...';
    
    eventSource.onmessage = (event) => {
        if (event.data === '[DONE]') {
            eventSource.close();  // 完成,关闭连接
            return;
        }
        answerDiv.innerText += event.data;  // 逐字追加
    };
    
    eventSource.onerror = () => {
        answerDiv.innerText += '\n[连接中断,请重试]';
        eventSource.close();
    };
}

总结

  • Token是大模型处理文本的最小单位,按Token计费是因为它直接反映GPU计算开销。中文约1字≈1Token,这对做预算是极强的参考
  • Temperature控制"随机性",Top-p控制"候选范围"。课程问答设置Temperature=0.3(知识问答需确定性),Top-p=0.8(保证专业术语在候选池中)
  • 流式输出提升用户体验:不是生成完再返回,而是逐字推送。用户感知到的首次响应时间显著缩短
  • SSE和WebSocket的区别:SSE单向、轻量、原生支持自动重连;WebSocket双向、需握手、需手动处理断线重连。AI回答场景是单向推送,SSE天然匹配
  • Nginx缓冲是SSE的隐形杀手 :需配置proxy_buffering off,必要时添加长连接超时配置
  • API密钥安全:永远不在代码中硬编码、永远不暴露在前端、通过环境变量或配置中心注入、生产环境加IP白名单
  • 课程问答项目的技术参数总结:GPT-3.5-Turbo + SSE流式 + Temperature=0.3 + Top-p=0.8 + maxTokens=500 + 环境变量管理密钥

下一篇预告:AI理论学习(八)------LangChain不是"套壳":它解决了什么实际问题。拆解Chain、Agent、Tool各自的设计意图,LangChain的优势和局限性,以及为什么有人觉得它是"过度封装"。这一篇将同时作为AI理论学习专栏的收官文章,帮你形成对AI应用开发工具链的独立判断。

相关推荐
weixin_553654482 小时前
有没有一种可能,现在的大语言模型已经发展得接近极限了?
人工智能·语言模型·大模型
.唉2 小时前
05. 从入门到实践: LlamaIndex与 RAG 应用构建
大模型·rag·llamaindex
庞轩px16 小时前
大模型推理网关——从负载均衡到故障注入的完整设计
网关·大模型·负载均衡·webflux·token限流·api密钥
Cyber4K17 小时前
【Python专项】Nginx访问日志分析时间范围处理示例
开发语言·python·nginx
JSLove17 小时前
nginx入门
前端·nginx
哥本哈士奇(aspnetx)19 小时前
SQLServer RAG笔记5:为SQLServer 2025配置Ollama
大模型
AI绘画哇哒哒19 小时前
RAG 系统中文档切分策略:如何选择合适的 chunk size?| 收藏这份实用指南,小白也能轻松上手大模型学习
人工智能·学习·ai·程序员·大模型·产品经理·转行
Jinkxs20 小时前
深度评测 GLM-5:AtomGit 首发模型的代码生成实战体验
人工智能·深度学习·大模型·atomgit·glm-5
python零基础入门小白20 小时前
从0到1:手把手教你用Coze打造AI Agent,小白也能转行AI!
人工智能·学习·程序员·大模型·agent·产品经理·ai大模型