30天从零开始学AI应用开发(Day 12):像产品一样稳:流式输出、报错重试、花钱控制

这是系列的第 12 篇。整个系列写给零基础、想入行 AI 的朋友,每天一篇,30 天后你会做出 3 个能写进简历的项目。

这篇解决什么问题

昨天加工具那个作业,交上来的代码我已经看了,功能基本都对。但有个共性问题:代码太脆了。

举几个真实场景。用户问一句话,转圈七八秒什么都不显示,以为程序卡死了;网络抖一下程序直接崩,用户一脸懵;跑了几天想看花了多少钱,完全不知道。

功能能用只是及格,能稳稳地交付出去才是合格。今天讲三件事:流式输出解决"等太久"、重试机制解决"太脆弱"、成本控制解决"不知道在烧多少钱"。这三件事做完,你的程序才像个正经产品。明天开工的项目一,会全程用上。

一、流式输出:让字一个个蹦出来

你现在用的写法,是等 AI 把整段话想完了再一次性给你。回答长了,用户要干等好几秒。

看看网页版大模型的体验:字是一个个往外蹦的。这不是特效,是流式输出(stream),AI 生成一个字就传一个字。

原理很好理解。像点外卖:非流式是把整桌菜做好了端上来,流式是开放式厨房,做一道上一道。后者等待感明显更小。

代码只改一个地方,加 stream=True:

python 复制代码
response = client.chat.completions.create(
    model="deepseek-chat",
    messages=[{"role": "user", "content": "讲个笑话"}],
    stream=True                        # 关键:开启流式
)

for chunk in response:                 # 一段一段地接收
    piece = chunk.choices[0].delta.content
    if piece:                          # 有些片段是空的,跳过
        print(piece, end="", flush=True)   # end="" 不换行,flush 立刻输出

注意几个变化:返回值从"一个完整的响应"变成了"一段可以遍历的流";取内容从 message.content 变成了 delta.content。

有个坑要提前说:开了流式之后,你拿不到完整的结果文本了,因为它是碎片式给你的。所以要把碎片拼起来:

python 复制代码
full_text = ""
for chunk in response:
    piece = chunk.choices[0].delta.content
    if piece:
        full_text += piece             # 一边显示一边拼接
        print(piece, end="", flush=True)
# 循环结束后,full_text 才是完整回答,后面要用就用它

二、报错重试:别让程序死在网络抖动上

调 API 是网络请求,网络请求就一定会失败。三类最常见的错误:

  • 网络抖动、超时(一时的问题,重试就好)
  • 限流(请求太频繁,等一会儿重试)
  • 余额不足、密钥错误(重试一万次也没用)

第三类要直接告诉用户,前两类应该自动重试。这是个通用套路,值得记住:

python 复制代码
import time

def call_with_retry(messages, max_try=3):
    for i in range(max_try):
        try:
            response = client.chat.completions.create(
                model="deepseek-chat",
                messages=messages
            )
            return response.choices[0].message.content   # 成功就返回
        except Exception as e:
            print(f"第{i + 1}次失败了:{e}")
            if i < max_try - 1:
                time.sleep(2)                 # 等两秒再试,给服务器缓一缓
            else:
                return "抱歉,服务暂时不可用,请稍后再试"   # 三次都不行,兜底

关键在最后那句兜底:程序不能因为一次网络问题就崩给用户看。错误要能被接住,并且翻译成人话。

三、成本控制:知道你花了多少钱

Day 6 说过,API 按 token 计费。但很多人跑了一个月都不知道自己花了多少,直到收到账单。

好消息是,每次 API 返回里都带着账单信息,就藏在响应对象里:

python 复制代码
response = client.chat.completions.create(
    model="deepseek-chat",
    messages=[{"role": "user", "content": "你好"}]
)

usage = response.usage
print("输入 token:", usage.prompt_tokens)         # 你发过去的内容
print("输出 token:", usage.completion_tokens)     # AI 回给你的内容
print("总计:", usage.total_tokens)

三件事顺手就能做:

第一,记日志。把每次调用的 token 数写进文件,月底一看就知道花在哪了。

第二,砍浪费。Day 10 讲的滑动窗口就是最简单的节流手段,别把几百轮对话全都发过去。

第三,主次分明。便宜的模型干粗活(分类、提取关键词),贵的模型干细活(写作、复杂推理)。这是行业里的普遍做法,成本能差出好几倍。

四、把三件事装在一起

实际项目里,这三件事是叠着用的。给你一个可以复用的骨架:

python 复制代码
import time

def chat_safe(messages, max_try=3):
    """带重试和成本记录的对话函数"""
    for i in range(max_try):
        try:
            response = client.chat.completions.create(
                model="deepseek-chat",
                messages=messages,
                stream=True                # 流式输出
            )
            full_text = ""
            for chunk in response:
                piece = chunk.choices[0].delta.content
                if piece:
                    print(piece, end="", flush=True)
                    full_text += piece
            print()                       # 结束后换行
            return full_text
        except Exception as e:
            print(f"\n第{i + 1}次失败:{e}")
            if i < max_try - 1:
                time.sleep(2)
            else:
                return "服务暂时不可用,请稍后再试"

以后写任何 AI 程序,直接从这段改,能省下很多重复工作。

常见报错排查

报错一:开了 stream 之后取 content 报错或拿到 None。

流式要取 delta.content 而不是 message.content,并且要判断空值(有些片段没有内容)。

报错二:重试了三次还是失败。

先看报错原文。如果是余额或密钥问题,重试是无效的,要直接去控制台处理。

报错三:流式输出在 VSCode 里看着正常,打包成程序就不显示。

输出缓冲的问题,确认用了 flush=True。有些环境需要手动刷新缓冲区。

报错四:response.usage 是 None。

开了 stream 之后,部分平台的 usage 需要额外设置才会返回。想统计成本又用流式,可以自己用字符数粗略估算,或者不开流式单独计数。

今天的作业

把 chat_safe 这段拿去跑,打印一句话,观察两件事:字是不是一个个出来的;每次调用用了多少 token。把 token 数字贴到评论区,看看大家问同一句话,谁的花费高。

明天预告

Day 13:《项目一开工:AI 文件整理助手,几百个乱文件一键归类》。铺垫了十二天,明天正式开始做简历上的第一个项目。上午搭骨架,下午就能看到它真的帮你整理文件。我会把完整代码拆成几段讲,你跟着敲就行。


*系列目录:30天从零开始学AI应用 开发

相关推荐
傻啦嘿哟1 小时前
Python的默认参数把我坑惨了,原来写[]和写None的区别这么大
开发语言·python·机器学习
镜象科技1 小时前
AI临床心理大模型:离临床最近的AI长什么样?
人工智能
zhanghaha13141 小时前
AI Agent_4 Agent工作原理详解:从“会聊天“到“会办事“
人工智能
DeepAgent2 小时前
AI Agent 工程实践(49):一次真实优化——从 Agent v1 到 v2
开发语言·人工智能·agent
小蒋观天下2 小时前
两轮车检测AI摄像头——2026-2030年未来市场规模、增长逻辑与行业天花板
大数据·人工智能·安全·计算机视觉·ai大模型
美狐美颜sdk2 小时前
直播APP接入美颜SDK后出现卡顿怎么办?从性能瓶颈寻找解决方案
android·人工智能·音视频·美颜sdk·直播美颜sdk
知识分享小能手2 小时前
C++ 学习教程,从入门到精通,C++对象和类 — 详细知识点总结(10)
开发语言·c++·学习
m0_587383002 小时前
深圳24小时自助健身房解决方案实战:从系统架构到部署指南
java·人工智能·spring boot·spring·系统架构·需求分析
罗西的思考2 小时前
[Agent Memory / 强化学习] MemPO源码学习笔记 ---(5)--- GRPO
人工智能·算法·机器学习