环境就绪后的第一步:用 Python 连接本地大模型

当你在 AMD Instinct GPU 上成功跑通 vLLM 服务,看到终端里跳出"Uvicorn running on…"的那一刻,最让人兴奋的其实是接下来这一步:用自己的代码去“唤醒”它。很多教程止步于服务启动,但作为应用层开发者,我们更关心如何把这个推理能力集成到业务里。今天我们就跳过理论,直接上手写一段 Python 脚本,实现真正的流式文本输出,让你体验那种“打字机”般的实时响应感。

构造标准的 API 请求

vLLM 的一大优势是原生兼容 OpenAI API 格式,这意味着你不需要学习新的协议,只需使用熟悉的 requests 库即可。在编写调用代码前,先确保你的服务正在运行(默认监听 0.0.0.0:8000)。

我们需要构造一个标准的 POST 请求。关键在于 headerspayload 的设置。Content-Type 必须指定为 application/json,否则服务端会拒绝解析。而在 payload 中,除了常规的 modelmessagesmax_tokens 外,想要实现流式输出,必须显式设置 "stream": True

import requests
import json

url = "http://localhost:8000/v1/chat/completions"
headers = {"Content-Type": "application/json"}

payload = {
    "model": "meta-llama/Meta-Llama-3-8B-Instruct",
    "messages": [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "简述 ROCm 生态在大模型推理中的优势。"}
    ],
    "max_tokens": 512,
    "temperature": 0.7,
    "stream": True  # 关键参数:开启流式模式
}

这段配置看似简单,却决定了后续的交互模式。如果不加 stream 参数,客户端会一直阻塞等待直到模型生成完所有 token,对于长文本生成,用户可能面对几十秒的空白屏幕,体验极差。而开启流式后,服务端会建立一条持久连接,边生成边推送数据。

处理分块数据与逐字打印逻辑

开启 stream=True 后,requests.post 的返回对象不再是一个完整的 JSON 响应,而是一个迭代器。我们需要通过 iter_lines() 方法逐行读取服务端推送的数据块。这里有一个细节需要注意:SSE(Server-Sent Events)协议返回的每一行通常以 data: 开头,且最后会有一个 [DONE] 标记表示结束。

下面的代码展示了如何解析这些流式数据,并实现逐字打印的效果:

try:
    response = requests.post(url, headers=headers, json=payload, stream=True)
    response.raise_for_status()  # 检查 HTTP 状态码

    print("模型开始生成:\n", end="", flush=True)

    for line in response.iter_lines():
        if line:
            decoded_line = line.decode('utf-8')
            
            # 过滤掉非数据行
            if decoded_line.startswith("data: "):
                content = decoded_line[6:]  # 去掉 "data: " 前缀
                
                if content.strip() == "[DONE]":
                    break
                
                try:
                    data = json.loads(content)
                    # 提取增量生成的 token
                    token = data['choices'][0]['delta'].get('content', '')
                    if token:
                        print(token, end='', flush=True)
                except json.JSONDecodeError:
                    continue
    
    print("\n\n生成完毕。")

except requests.exceptions.ConnectionError:
    print("错误:无法连接到服务端,请检查 vLLM 是否已启动且端口正确。")
except requests.exceptions.Timeout:
    print("错误:请求超时,可能是模型加载过慢或网络拥堵。")
except Exception as e:
    print(f"发生未知错误:{e}")
finally:
    if 'response' in locals():
        response.close()  # 确保关闭连接,释放资源

这段代码的核心在于 print(token, end='', flush=True)flush=True 强制立即刷新缓冲区,让每个 token 一到达就显示在终端上,从而营造出流畅的打字机效果。同时,try-except-finally 结构保证了即使中途出错或用户中断,网络连接也能被正确关闭,避免资源泄漏。

同步与流式:体验差异与场景选择

为了直观感受两者的区别,我们可以对比一下两种模式的执行逻辑。在同步模式下(stream=False),客户端发送请求后进入休眠状态,直到服务端完成全部计算并返回几 KB 甚至更大的 JSON 包。如果生成 500 个 token 耗时 10 秒,用户就要干等 10 秒才能看到结果。

而在流式模式下,首字延迟(TTFT)通常只有几百毫秒。用户几乎在发送请求的瞬间就能看到第一个字,随后的内容源源不断地涌现。这种“即时反馈”在聊天机器人、代码辅助生成或长篇文档摘要场景中至关重要,它能显著降低用户的等待焦虑感。

当然,流式调用对网络的稳定性要求稍高。如果在传输过程中连接断开,客户端需要具备一定的重连或错误提示机制,上面的代码已经做了基础示范。对于内部局域网部署的 vLLM 服务,网络波动较小,流式输出几乎是首选方案。

常见 HTTP 状态码排查指南

在实际调试过程中,你可能会遇到各种 HTTP 状态码,快速定位问题能节省大量时间:

  • 404 Not Found:通常是 URL 路径写错了。vLLM 的标准接口是 /v1/chat/completions/v1/completions,请检查是否有拼写错误,或者服务端是否修改了默认路由。
  • 503 Service Unavailable:这往往意味着服务端正在忙碌或模型尚未加载完成。如果是刚启动服务,稍等片刻再试;如果是高并发场景,可能需要调整 vLLM 的 --max-num-seqs 参数。
  • 500 Internal Server Error:大概率是服务端崩溃了。此时应立刻回到 vLLM 启动的终端窗口查看日志,常见原因包括显存溢出(OOM)或算子不支持。如果是 OOM,尝试调低 --gpu-memory-utilization 参数。
  • Connection Refused:最基础的网络问题。确认 vLLM 进程是否在运行,监听地址是否为 0.0.0.0(允许外部访问),以及防火墙是否放行了 8000 端口。

通过这段实战代码,你应该已经掌握了在 Python 中调用本地大模型的核心技巧。无论是构建智能客服还是自动化写作工具,这套流式交互逻辑都能为用户提供更自然、更高效的体验。接下来,你可以尝试将这段逻辑封装成类或异步函数,进一步融入你的项目架构中。

200小时GPU算力已就位,快来领取:https://marketing.csdn.net/questions/Q2604140858304426315?utm_source=AIpaper

文章海报

更多推荐