LlamaIndex + Qwen3.5-4B 关闭 Thinking 模式调试记录

问题:使用 LlamaIndex 的 HuggingFaceLLM 加载本地 Qwen3.5-4B 模型时,模型输出内部思维链(thinking process)而非正常回答。Qwen3.5-2B 无此问题。

原始代码

python 复制代码
from llama_index.core.llms import ChatMessage
from llama_index.llms.huggingface import HuggingFaceLLM

llm = HuggingFaceLLM(
    model_name="/home/sun/data/model/Qwen/Qwen3.5-4B",
    tokenizer_name="/home/sun/data/model/Qwen/Qwen3.5-4B",
    model_kwargs={"trust_remote_code": True},
    tokenizer_kwargs={"trust_remote_code": True}
)
rsp = llm.chat(messages=[ChatMessage(content="猪八戒是什么?")])

输出问题: 模型输出完整的思维推理过程("Here's a thinking process..."),而非直接回答问题。

根本原因: Qwen3 系列从 4B 开始内置"思维链"能力,模型默认在回答前输出 <think>...</think> 推理块。2B 模型参数量小,不具备此功能,所以正常。


第一次改动:自定义 messages_to_prompt + enable_thinking=False

改动内容

  • 预加载 tokenizer,自定义 messages_to_prompt 函数
  • 在 apply_chat_template 中传入 enable_thinking=False
  • 在 generate_kwargs 中传入 max_new_tokens 和 temperature
python 复制代码
_tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)

def messages_to_prompt(messages):
    msg_list = [{"role": m.role.value, "content": m.content} for m in messages]
    return _tokenizer.apply_chat_template(
        msg_list, tokenize=False, add_generation_prompt=True,
        enable_thinking=False
    )

llm = HuggingFaceLLM(
    ...,
    messages_to_prompt=messages_to_prompt,
    generate_kwargs={"max_new_tokens": 512, "temperature": 0.7},
)

效果

输出乱码: 产生完全不可读的混乱文本(中英文碎片混杂)。

原因分析

LlamaIndex 内部在 messages_to_prompt 的结果上又套了一层 completion_to_prompt,导致 prompt 被双重包装,模型接收到的输入格式错乱。


第二次改动:添加 completion_to_prompt + 去除 <think> 标签 + 修复 max_new_tokens

改动内容

  1. 添加 completion_to_prompt:设为透传函数,防止二次包装
  2. 用 re.sub 强制移除 prompt 中残留的 <think>...</think> 标签
  3. 将 max_new_tokens 从 generate_kwargs 移到 HuggingFaceLLM 的直接参数
  4. 显式指定 ChatMessage 的 role="user"
python 复制代码
def messages_to_prompt(messages):
    ...
    prompt = _tokenizer.apply_chat_template(...)
    # 去除 think 标签
    prompt = re.sub(r'<think>.*?</think>\s*', '', prompt, flags=re.DOTALL)  
    return prompt

def completion_to_prompt(completion):
    return completion  # 透传,不再包装

llm = HuggingFaceLLM(
    ...,
    messages_to_prompt=messages_to_prompt,
    completion_to_prompt=completion_to_prompt,  # 新增
    max_new_tokens=512,                          # 从 generate_kwargs 移出
    generate_kwargs={"temperature": 0.7, "do_sample": True},
)

效果

  • Prompt 格式正确: <|im_start|>user\n猪八戒是什么?<|im_end|>\n<|im_start|>assistant\n
  • 但模型仍然输出 <think> + 思维链内容

原因分析

Prompt 虽然干净了,但模型在生成阶段自主产生了 <think> token,然后进入思考模式。问题不在输入端,而在输出端。


第三次改动(最终方案):用 suppress_tokens 屏蔽 <think> token

改动内容

在 generate_kwargs 中添加 suppress_tokens,将 <think> 的 token ID 加入屏蔽列表,从生成层面阻止模型产生该 token。

python 复制代码
# 获取 <think> 的 token id
think_token_ids = _tokenizer.encode("<think>", add_special_tokens=False)

llm = HuggingFaceLLM(
    ...,
    messages_to_prompt=messages_to_prompt,
    completion_to_prompt=completion_to_prompt,
    max_new_tokens=512,
    generate_kwargs={
        "temperature": 0.7,
        "do_sample": True,
        "suppress_tokens": think_token_ids,  # 关键:屏蔽 <think> token
    },
)

效果

问题解决。 模型正常输出直接回答,不再产生思维链内容。


总结对照表

阶段 改动要点 效果 问题原因
原始代码 无任何处理 输出思维链 Qwen3.5-4B 默认开启 thinking 模式
第一次改动 enable_thinking=False + generate_kwargs 输出乱码 缺少 completion_to_prompt 导致 prompt 被双重包装
第二次改动 加 completion_to_prompt + 去除 <think> 标签 + 修复 max_new_tokens prompt 正确,但模型仍输出 <think> 模型在生成阶段自主产生 <think> token
第三次改动 suppress_tokens 屏蔽 <think> token 正常输出 从生成层面阻断思考模式

最终完整代码

python 复制代码
import re
from transformers import AutoTokenizer
from llama_index.core.llms import ChatMessage
from llama_index.llms.huggingface import HuggingFaceLLM

model_path = "/home/sun/data/model/Qwen/Qwen3.5-4B"
_tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)

# 获取 <think> 的 token id
think_token_ids = _tokenizer.encode("<think>", add_special_tokens=False)

def messages_to_prompt(messages):
    msg_list = []
    for m in messages:
        role = m.role.value if hasattr(m.role, 'value') else str(m.role)
        msg_list.append({"role": role, "content": m.content})
    return _tokenizer.apply_chat_template(
        msg_list,
        tokenize=False,
        add_generation_prompt=True,
        enable_thinking=False
    )

def completion_to_prompt(completion):
    return completion

llm = HuggingFaceLLM(
    model_name=model_path,
    tokenizer_name=model_path,
    model_kwargs={"trust_remote_code": True},
    tokenizer_kwargs={"trust_remote_code": True},
    messages_to_prompt=messages_to_prompt,
    completion_to_prompt=completion_to_prompt,
    max_new_tokens=512,
    generate_kwargs={
        "temperature": 0.7,
        "do_sample": True,
        "suppress_tokens": think_token_ids,
    },
)

rsp = llm.chat(messages=[ChatMessage(role="user", content="猪八戒是什么?")])
print(rsp)

备注

  • 如果 suppress_tokens 不被支持,可替换为 "bad_words_ids": [think_token_ids]
  • 此方案适用于 Qwen3 系列所有带 thinking 功能的模型(4B 及以上)
  • Qwen3.5-2B 不具备 thinking 功能,无需此处理
相关推荐
kishu_iOS&AI3 小时前
深度学习 —— 损失函数
人工智能·pytorch·python·深度学习·线性回归
好运的阿财3 小时前
OpenClaw工具拆解之canvas+message
人工智能·python·ai编程·openclaw·openclaw工具
wengqidaifeng3 小时前
python启航:1.基础语法知识
开发语言·python
观北海3 小时前
Windows 平台 Python 极简 ORB-SLAM3 Demo,从零实现实时视觉定位
开发语言·python·动态规划
FreakStudio4 小时前
做了个Claude Code CLI 电子宠物:程序员的实体监工代码搭子
python·单片机·嵌入式·面向对象·并行计算·电子diy·电子计算机
柴米油盐那点事儿4 小时前
python+mysql+bootstrap条件搜索分页
python·mysql·flask·bootstrap
AC赳赳老秦4 小时前
OpenClaw二次开发实战:编写专属办公自动化技能,适配个性化需求
linux·javascript·人工智能·python·django·测试用例·openclaw
Ulyanov5 小时前
《PySide6 GUI开发指南:QML核心与实践》 第二篇:QML语法精要——构建声明式UI的基础
java·开发语言·javascript·python·ui·gui·雷达电子对抗系统仿真
刀法如飞6 小时前
一款Python语言Django框架DDD脚手架,助你快速搭建项目
python·ddd·脚手架
刀法如飞6 小时前
一款Python语言Django框架DDD脚手架,适合中大型项目
后端·python·领域驱动设计