Series · Aliyun Bailian · Chapter 2

阿里云百炼实战(二):Qwen 文本大模型在生产环境的用法

按延迟和成本选 Qwen 变体、function calling 写对、JSON mode 不再哭、enable_thinking + 流式必绑这条文档没明说的事。

整个系列里生产收益最大的就是这篇。其它模型有趣,LLM 才是我每个百炼上线产品每分钟都在调的东西。官方 Qwen API reference 详尽但稠密,本文是从中挑一条可读路径走完。

按场景选 Qwen 变体

Qwen 家族很大。多数团队默认 qwen-max 全量花冤枉钱,少数团队默认 qwen-turbo 省了质量。正确答案是"按任务匹配变体"。

Qwen 模型家族

我的生产经验:

  • qwen-turbo — 分类、意图识别、短摘要、单次用户请求里调 >10 次的事。最便宜的那档 Qwen,抽取场景出乎意料地好。
  • qwen-plus — 日常主力。对话、RAG 综合、多步推理。性价比拐点。
  • qwen-max / qwen3-max — 代码评审、复杂推理、答错代价比答慢大的事。
  • qwen3-coder-plus — 所有代码任务。同参数规模下比通用 qwen-plus 在代码上明显好。
  • qwen3-vl-plus / qwen3-omni-flash — 图像/视频/音频输入。第三篇专门讲。

经验: 常见错误是用 qwen-max 做 embedding 类分类任务。别这么干。qwen-turbo 配紧凑 system prompt 能省 10 倍成本无质量损失。

一次请求实际发的什么

不论走 OpenAI 兼容还是原生,一次 chat 的本质都一样:模型 ID、messages 数组、参数块。

Chat completion 请求流

最常碰的字段:

  • messages{role, content} 数组。role 是 system / user / assistant / tool。文档说多模态模型 content 可以是带类型的部分数组(text、image_url、input_audio、video_url)——见第三篇。
  • temperature — 0.0-2.0。我抽取/分类用 0.0,默认对话 0.2-0.4,创作 0.7+。文档默认大约 0.7,对多数 agent 用法太高了。
  • top_p — 不知道为啥要改就别改。temperaturetop_p 一起调是混乱配方。
  • max_tokens(兼容)/ parameters.max_tokens(原生)— 是输出上限,不是总上限。要设。否则一次失控算钱。
  • stream — 切 SSE 流式。
  • response_format={"type": "json_object"} — JSON mode。比"请返回 JSON"提示靠谱。
  • tools / tool_choice — function calling。

Function calling:一来一回

Qwen 的 function calling 协议就是 OpenAI 的 tool-calls。两次 LLM 调用 + 中间你的代码:

Function calling 一来一回

完整可跑示例——一个能查天气的小 agent:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import json, os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ['DASHSCOPE_API_KEY'],
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询某城市当前天气",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

def call_weather(city):
    # 真实场景:调你自己的 API。这里 stub。
    return {"city": city, "temp_c": 22, "conditions": "晴"}

messages = [{"role": "user", "content": "去上海要带伞吗?"}]
resp = client.chat.completions.create(
    model="qwen-plus", messages=messages, tools=tools,
)
msg = resp.choices[0].message

if msg.tool_calls:
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = call_weather(**args)
        messages.append(msg)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": json.dumps(result),
        })
    final = client.chat.completions.create(model="qwen-plus", messages=messages)
    print(final.choices[0].message.content)
else:
    print(msg.content)

三个易踩的坑:

  • 第一次响应和工具结果之间必须 messages.append(msg)。模型需要在历史里看到自己的 tool_call 消息,否则第二次调用会 400 报"orphan tool result"。
  • tool_choice="auto" 是默认。 必要时强制指定工具:tool_choice={"type": "function", "function": {"name": "..."}}——工作流首次调用时常用。
  • parallel_tool_calls=True 支持。工具相互独立时用——模型会一次返回多个 tool_calls

JSON mode

要结构化输出,别靠 prompt。用:

1
2
3
4
5
6
7
8
9
resp = client.chat.completions.create(
    model="qwen-plus",
    messages=[
        {"role": "system", "content": '返回 JSON:{"sentiment": "positive|negative|neutral"}'},
        {"role": "user", "content": "这个产品我超喜欢。"},
    ],
    response_format={"type": "json_object"},
)
data = json.loads(resp.choices[0].message.content)

生产里两个注意:

  • 模型有时还是会用 ```json 包起来。json.loads 前剥一下围栏更稳。
  • 严格结构化(Pydantic schema)改用 function calling。更严格、调试更友好。

enable_thinking 和流式陷阱

Qwen3 系列模型支持 enable_thinking=True——让模型先产推理链再产最终答案。质量提升明显,尤其重推理任务。但必须开流式。非流式 400。

enable_thinking + 流式

实操模式——推理链丢日志,答案流到 UI:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
stream = client.chat.completions.create(
    model="qwen3-max",
    messages=[{"role": "user", "content": "如果一只表每天慢 5 分钟,10 天后差多少?"}],
    extra_body={"enable_thinking": True},
    stream=True,
)

reasoning, answer = [], []
for chunk in stream:
    delta = chunk.choices[0].delta
    rc = getattr(delta, "reasoning_content", None)
    if rc:  reasoning.append(rc)
    if delta.content: answer.append(delta.content)

print("ANSWER:", "".join(answer))
print("(推理链已隐藏,", sum(len(r) for r in reasoning), "字符)")

我把 reasoning 转给日志系统,永远不给用户看。三个原因:(1) 客户看到了会泄露 chain-of-thought IP,(2) 非技术读者看了会困惑,(3) 让可见响应长度翻倍。

异步 chat(少见但有用)

特别长的对话(比如 30k token 的 RAG 综合),可以加 X-DashScope-Async: enable 异步提交后轮询,跟万相一样。Qwen API reference 里"异步调用"章节有。我用它处理不需要立即响应的定时批量摘要任务。

真正能省钱的成本控制

  • 永远设 max_tokens 默认上限是"模型最大",失控循环要命。
  • 每环境一个工作空间 key。 控制台里给生产 key 设硬性日预算。
  • 打印 token 数。 每个响应都有 usage.prompt_tokensusage.completion_tokens。每周聚合,能发现某个 prompt 不知不觉膨胀了 3 倍。
  • 边缘缓存相同 prompt。 DashScope 现在没像 Anthropic 那样的 prompt caching 暴露——高 QPS 同前缀场景请自己缓存。

下一篇

第三篇 Qwen-Omni —— 多模态兄弟。最大区别是:流式必须(不是可选)、content 数组带类型部分(image / audio / video)、还得想清楚像素预算和帧率。如果你的产品涉及非文本内容,这是百炼里杠杆最大的能力。

Liked this piece?

Follow on GitHub for the next one — usually one a week.

GitHub