解决Llama 3只返回函数调用问题:驯服语言模型指南
2025-03-17 03:32:32
Llama 3 只返回函数调用?驯服你的语言模型!
最近在使用 Llama 3 Instruct 模型时,我发现了一个问题:不管我问什么,它都只返回函数调用。即使是像“你是谁?”这样简单的问题,也无法得到正常的回复。 这让我很困惑,难道它只能进行函数调用了吗?
问题根源:指令模板与工具的交互
经过一番调查,我发现问题可能出在指令模板(chat template)与工具(tools)的交互上。Llama 3 Instruct 模型经过特殊训练,能够理解并执行函数调用。当我们提供工具(例如上面的get_current_temperature
)时,模型会优先考虑使用这些工具来响应用户输入。
原始代码中,直接把tools
参数放进了tokenizer.apply_chat_template
里, 即使是很普通的对话,它也会按照function call的格式去回复。这其实就是对模型的一种“过度约束”。
解决方案: 让模型自由呼吸
要解决这个问题,我们需要给模型一些“自由”,让它能够判断何时使用函数调用,何时进行正常的对话。 下面是几种可能的解决方案。
1. 精细化提示工程 (Prompt Engineering)
最直接的方法是通过优化提示工程。 我们可以更明确地告诉模型我们的意图。
原理: 通过修改用户输入的提示 (prompt),更清晰地指示模型执行何种操作。对于不需要调用工具的问题,可以通过提问方式,上下文等引导模型正常回复。
代码示例:
from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM
import os
import torch
from huggingface_hub import login
def get_current_temperature(location: str, unit: str) -> float:
"""Get the current temperature."""
return 22.
def get_current_wind_speed(location: str) -> float:
"""Get the current wind speed in km/h."""
return 6.
tools = [get_current_temperature, get_current_wind_speed]
# Suppress MPS log message (optional)
os.environ["TORCH_MPS_DEVICE"] = "1"
checkpoint = "models/Llama-3.2-1B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForCausalLM.from_pretrained(checkpoint, torch_dtype=torch.bfloat16, device_map="cpu")
def ask_llama(question, use_tools=False):
messages = [
{"role": "user", "content": question}
]
if use_tools:
inputs = tokenizer.apply_chat_template(messages, tools=tools, add_generation_prompt=True, return_dict=True, return_tensors="pt")
else:
inputs = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_dict=True, return_tensors="pt")
inputs = {k: v.to(model.device) for k, v in inputs.items()}
out = model.generate(**inputs, max_new_tokens=128)
return tokenizer.decode(out[0][len(inputs["input_ids"][0]):],skip_special_tokens=True)
# 不需要工具的问题
print(ask_llama("你是谁?请直接回答,不要调用工具。", use_tools=False))
print(ask_llama("苹果设备是什么?", use_tools=False))
# 需要工具的问题
print(ask_llama("法国巴黎的温度是多少?", use_tools=True))
操作步骤:
- 定义一个
ask_llama
函数, 通过use_tools
参数,控制是否传入tools
. - 对于不需要函数调用的问题, 设置
use_tools=False
, 并且在问题后加上明确的指令(如“请直接回答,不要调用工具。”)。 - 对于需要函数调用的问题,设置
use_tools=True
.
安全建议: 明确的提示工程有助于防止模型产生意外的输出,提高可控性。
2. 条件化工具注入
我们可以更进一步,只有在需要调用工具的时候才注入tools
参数。
原理: 通过编程逻辑控制,只在必要时才将tools
参数传递给apply_chat_template
函数。
代码示例(基于上述代码修改):
# 在上面的代码基础上, 只需更改调用方式.
# 不需要工具的问题
print(ask_llama("你是谁?", use_tools=False)) #直接用 False
# 需要工具的问题
print(ask_llama("法国巴黎的温度是多少?", use_tools=True)) # 使用 True
改进 : 对ask_llama
函数进行了优化,使用一个布尔变量use_tools
来决定是否将工具传递给模型.
3. 多轮对话中的工具管理
如果我们需要在多轮对话中使用工具,可以更精细地控制工具的注入时机。
原理: 在多轮对话中,根据对话历史和当前用户输入,动态决定是否需要注入tools
。
代码示例:
def ask_llama_multi_turn(messages, use_tools=False):
if use_tools:
inputs = tokenizer.apply_chat_template(messages, tools=tools, add_generation_prompt=True, return_dict=True, return_tensors="pt")
else:
inputs = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_dict=True, return_tensors="pt")
inputs = {k: v.to(model.device) for k, v in inputs.items()}
out = model.generate(**inputs, max_new_tokens=128)
return tokenizer.decode(out[0][len(inputs["input_ids"][0]):],skip_special_tokens=True)
# 对话历史
messages = []
# 第一轮:不需要工具
messages.append({"role": "user", "content": "你好!"})
response = ask_llama_multi_turn(messages, use_tools=False)
messages.append({"role": "assistant", "content": response})
print(f"Assistant: {response}")
# 第二轮:需要工具
messages.append({"role": "user", "content": "伦敦现在的风速是多少?"})
response = ask_llama_multi_turn(messages, use_tools=True)
messages.append({"role": "assistant", "content": response})
print(f"Assistant: {response}")
# 第三轮: 不需要工具
messages.append({"role": "user", "content": "谢谢你"})
response = ask_llama_multi_turn(messages, use_tools=False)
messages.append({"role": "assistant", "content": response})
print(f"Assistant: {response}")
操作步骤:
- 维护一个
messages
列表,存储对话历史。 - 根据每一轮的对话内容,决定是否设置
use_tools
为True
。
4. (进阶)自定义Chat Template
如果以上方法都不能完全满足你的需求,你可以尝试自定义 Chat Template。 这是因为, Llama3 的默认Chat Template在遇到tools
的时候,会自动给回复加上一些function call的前后缀。
原理:
通过修改分词器(tokenizer)的 chat_template
属性, 来控制模型生成文本的格式, 避免无差别加入function call的内容。
示例(非完整可运行示例, 仅供参考思路):
# 注意:这只是一个 *思路* 示例,具体实现可能需要根据您的模型和分词器进行调整。
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
# 一个简化的 chat_template 示例,可能需要根据实际情况进行调整。
custom_template = "{% for message in messages %}" \
"{% if message['role'] == 'user' %}" \
"{{ '<|start_header_id|>user<|end_header_id|>\n\n' + message['content'] + '<|eot_id|>' }}" \
"{% elif message['role'] == 'assistant' %}"\
"{% if message.content is not none %}"\
"{{ '<|start_header_id|>assistant<|end_header_id|>\n\n' + message['content'] + '<|eot_id|>' }}"\
"{% endif %}"\
"{% endif %}"\
"{% endfor %}"
tokenizer.chat_template = custom_template
# 测试
messages = [
{"role": "user", "content": "Hey, who are you ?"}
]
inputs = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_dict=True, return_tensors="pt")
# ... rest of the code ...
警告: 修改chat_template
需要对 Jinja2 模板语言有一定了解。 错误修改可能导致模型无法正常工作。 请仔细阅读 Hugging Face 的文档,并进行充分测试。 另外, 不同的Tokenizer实现, template 可能会有一些细节差异, 需要根据实际情况做调整。
总结
通过上述几种方法, 特别是灵活控制是否传入 tools
, 我们成功解决了 Llama 3 只返回函数调用的问题。记住, 使用大型语言模型,就像和人交流一样,清晰的指令和适当的语境,才能获得最佳的结果.