总结了 6 个 LLM 开发中常见的上线后问题:流式与非流式返回类型不一致、streaming 的上下文管理陷阱、prompt 注入防御缺失等。
说实话,你肯定有过这种感觉:AI 功能在测试里跑得完美无缺,周五上线时觉得自己简直是天才,然后周一早上发现出事了,完全不知道为啥。
是的,这种感觉有个名字,通常都是这六个问题之一。
这些问题都不罕见,也没有啥高深莫测的解决方案。它们只是一些很小的、容易忽略的决策,在 demo 里看起来完全没问题,但一旦真实用户来了,就悄悄变成了凌晨两点的告警。现在就解决掉它们,不用等到踩坑。
如果你写过这样的代码,请举手:
def ask(prompt, stream=False):
if stream:
# return a generator
else:
# return a string
感觉很高效对吧?一个函数搞定一切。但问题是,现在每一个调用 ask() 的地方都得记住 stream=True 会如何影响返回类型,哪天有人忘了,尝试在 generator 上调用 .upper(),然后就开始怀疑人生。
直接拆成两个函数就好了:
def ask(prompt: str) -> str:
"""Always returns a full string."""
...
def ask_streaming(prompt: str):
"""Always yields chunks. Always a generator."""
...
很无聊?确实。但现在函数名清楚地告诉你你会得到什么,没有人会需要额外在脑子里记一堆状态才能正确使用。未来的你会感谢现在的你。
这是个有意思的问题。你写了个循环来打印流入的 token,效果很好——直到循环中途抛出错误(渲染 bug、网络抖动、随便啥)。这时候连接真的关闭了吗?
用简单写法:通常不会。它就这么……挂着。悄悄地泄漏。直到你看着服务器的连接数一直往上涨,完全不知道为什么。
with client.messages.stream(...) as stream:
for text in stream.text_stream:
yield text
用上下文管理器包裹意味着无论发生什么都会清理——即使中途炸了也一样。这是个一行就能搞定的改动,平时根本注意不到……直到某一天救了你。
如果你在做一个聊天机器人,迟早需要限制发送给模型的历史量(上下文窗口不是无限的,你的 API 账单也不是)。自然的冲动是"只保留最后 N 条消息"。
这里有个陷阱:如果 N 恰好落在一条用户/助手的来回中间,你就会得到一条孤立的助手消息,前面没有用户消息——很多 API 会直接拒绝这种请求。
这个修复一旦看到就简单得有点可笑:
recent = self.history[-(self.max_history_turns * 2):]
乘以 2,从末尾切片——现在你永远保留的是完整的对话轮,不会出现半途而废的情况。
这是我最喜欢的一个,因为它很隐蔽。没有崩溃,没有错误信息,没有任何东西告诉你出问题了。只是……搜索结果感觉有点不对劲。
如果你用 FAISS 的 IndexFlatIP 做余弦相似度搜索,这个技巧只在向量先被归一化的情况下才有效。跳过这一步,你就是在悄悄地做普通点积搜索——它的排序方式不一样,没有任何红旗告诉你为什么"最相似"的结果感觉不太对劲。
norms = np.linalg.norm(vectors, axis=1, keepdims=True)
normalized = vectors / np.clip(norms, 1e-10, None)
就一行。容易忘。难以察觉,直到你开始深挖。
重试失败的 API 调用看起来很负责任——限流会发生、超时会发生、东西偶尔会坏,再试一次是成熟的做法。但如果你不加区分地重试所有东西,那你也会重试你自己的 bug。发了个格式错误的请求?好,现在你要一模一样地失败四次,白白浪费时间和你限流的配额。
对重试的对象要挑剔:
@retry(
retry=retry_if_exception_type((RateLimitError, APITimeoutError, APIError)),
stop=stop_after_attempt(4),
wait=wait_exponential(multiplier=1, min=1, max=20),
)
def call_model(...):
...
瞬时问题(限流、超时)?值得用退避重试。你自己的 bug?让它快速失败,这样你才能真正看到并修复它,而不是藏在四次相同的失败后面。
重试对"哎呀,小抖动"时刻很有效。但如果模型提供商连续好几天都很糟,重试就帮不上什么忙了。这时候备用模型就值回它的存在了——先试快的/便宜的,如果一直失败,就 fallback 到一个更强的(或者就是不一样的)模型,而不是直接给用户报个错。
try:
return call_model(primary_model, messages)
except RetryableErrors:
return call_model(fallback_model, messages)
这和大多数生产级 AI 网关幕后用的是同一套把戏。多写几行代码,省去一个很糟糕的下午。
说真的,这些东西一旦知道了就不难——这就是本文的重点。它们只是快速推进时容易被跳过的那些小事,等到终于踩坑时就很痛苦了。(如果你想要这些模式的完整可运行版本,外加更多——RAG、工具调用、记忆——都在 AI & LLM Integration Cookbook 里,但上面这六个已经足以省掉你一个糟糕的夜晚了。)
现在去加上那个上下文管理器,别忘了。😄