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 功能,无需此处理
相关推荐
书到用时方恨少!2 小时前
Python os 模块使用指南:系统交互的瑞士军刀
开发语言·python
带娃的IT创业者3 小时前
WeClaw_40_系统监控与日志体系:多层次日志架构与Trace追踪
java·开发语言·python·架构·系统监控·日志系统·链路追踪
亓才孓3 小时前
【提示词五要素】
python·ai·prompt
财经资讯数据_灵砚智能3 小时前
全球财经资讯日报(夜间-次晨)2026年3月28日
大数据·人工智能·python·语言模型·ai编程
水哥ansys4 小时前
Pyansys-PyMAPDL基本语法01-APDL原生命令流改写格式
python·二次开发·水哥ansys·pyansys·apdl
迷藏4944 小时前
# 发散创新:低代码开发新范式——用可视化逻辑构建企业级业务系统 在当今快速迭代的软件工程实践
java·python·低代码
我的xiaodoujiao4 小时前
API 接口自动化测试详细图文教程学习系列7--相关Python基础知识6
python·学习·测试工具·pytest
山川行4 小时前
Python快速闯关8:内置函数
java·开发语言·前端·笔记·python·学习·visual studio
badhope4 小时前
10个高星GitHub项目推荐
python·深度学习·计算机视觉·数据挖掘·github