问题:使用 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
改动内容
- 添加 completion_to_prompt:设为透传函数,防止二次包装
- 用 re.sub 强制移除 prompt 中残留的 <think>...</think> 标签
- 将 max_new_tokens 从 generate_kwargs 移到 HuggingFaceLLM 的直接参数
- 显式指定 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 功能,无需此处理