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

原始代码

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
_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"
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。

# 获取 <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

正常输出

从生成层面阻断思考模式


最终完整代码

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 功能,无需此处理
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐