LangChain 本地化应用探索(二):修复输出异常与模板适配策略

94 阅读7分钟

前言

上一次的课程当中,我们尝试的进行了在LangChain中接入ModelScope模型,但是在接入中,我们会发现与模型的对话有些许的问题,包括模型回复时会先把系统提示词和问题带上一起来进行回复。这显然不是我们所希望的,我们希望的就只是单纯的回复,因此今天我就来带大家一起解决一下这个问题,从而避免类似的事情发生

图片

上一节课的代码如下所示:

# 导入必要的库:transformers用于模型操作,langchain用于构建对话链,torch用于GPU计算
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
from langchain_huggingface import HuggingFacePipeline

# 指定本地模型路径,需替换为实际存放模型文件的文件夹
model_path"模型的本地文件夹"

# 加载分词器,trust_remote_code允许执行模型作者提供的自定义代码
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)

# 加载预训练模型
# 原代码未指定torch_dtype和device_map,将默认使用CPU或FP32精度
model = AutoModelForCausalLM.from_pretrained(model_path)

# 创建文本生成管道,设置最大生成长度为512个token
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=512)

# 将transformers管道封装为LangChain可用的大语言模型接口
llm = HuggingFacePipeline(pipeline=pipe)

# 向模型提问:"请简单介绍一下大语言模型"
ai_msg = llm.invoke("请简单介绍一下大语言模型")

# 打印调用模型后的输出
print(ai_msg)

问题缘由

在经过了多次的测试后,我发现问题的来源主要是原因提示词模版的错误,这个提示词模版可不是我们常说的用来和大模型交互的提示词,而是指在与大语言模型(如ChatGPT、Qwen等)交互时,为了引导模型生成更准确或符合预期的输出,而设计的一种结构化提示格式。它通常包含固定的上下文信息(如角色设定、任务说明等)以及可插入变量的位置(如用户输入),帮助模型理解当前的语境并作出更合适的回应。简单来说,它就像是给模型“写剧本”,告诉它该扮演什么角色、做什么任务、接下来要回答什么问题,从而提高模型输出的稳定性和可控性。

比如说qwen大模型的提示词模版格式就是下面这样的:

<|im_start|>system
你是一个乐于助人的中文助手。<|im_end|>
<|im_start|>user
请简单介绍一下大语言模型。<|im_end|>
<|im_start|>assistant

由于大模型在训练的过程中都是利用这一套的模版完成,但是我们使用langchain_huggingface库的时候不知道是没有对此进行适配还是怎么样,其没有成功应用这一套的提示词模版,导致了模型输出的偏颇。所以我们要做的事情就是把这一套提示词模版加进去,使其能够正常的输出。

当然还有一些可能的错误是两个模型没有对齐,就比如说在一些关键的参数上,如top_p ,top_k ,temperature上不一致导致的输出混乱,但是提示词模版是更加关键的部分,直接影响到模型的输出效率,这也是为什么我在这里需要重点强调的内容。

解决方案

那这套提示词模版在哪定义的呢,其实就是在每个开源模型里面都有的Tokenizer里面其实都是设置好的,我们只需要按照格式传给Tokenizer里的apply_chat_template就好了,这样就会自动的帮我们去调整合适的输入格式了。

messages = [
    {"role""system""content""你是一个乐于助人的中文助手。"},
    {"role""user""content""你是谁?"}
]
prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)

这个时候所得到的prompt就是:

<|im_start|>system
你是一个乐于助人的中文助手。<|im_end|>
<|im_start|>user
你是谁?<|im_end|>
<|im_start|>assistant

当我们把这个prompt传给大模型时,我们会惊喜的发现,大模型输出的内容竟然是完整格式的内容!也就是下面这样的:

<|im_start|>system
你是一个乐于助人的中文助手。<|im_end|>
<|im_start|>user
你是谁?<|im_end|>
<|im_start|>assistant
我是来自阿里云的超大规模语言模型,我叫通义千问。

那其实我们需要的只是后面部分的内容,前面这些其实是不需要的。因此我们需要在输出前在设置一个函数来解决这个问题,只让<|im_start|>assistant后面的内容进行展示:

def extract_assistant_reply(output_text: str) -> str:
    marker = "<|im_start|>assistant"
    if marker in output_text:
        # 提取其后的所有内容,并去除前缀的换行
        return output_text.split(marker, 1)[-1].lstrip("\n").strip()
    return output_text.strip()

这个函数其实就是找到<|im_start|>assistant这部分内容,然后只提取后面的内容,并且删除掉换行的内容,只保留最后输出的内容。我们就可以看到设置后模型输出的结果就比较简洁明了:

图片

当然<|im_start|>assistant只是针对于qwen这个模型的,并不是所有模型都是通用适用的,所以假如大家发现自己要用的开源模型还没有适配提示词模版,也可以照着我这样手动去进行设置!

完整的代码如下所示:

# 导入必要的库
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
from langchain_huggingface import HuggingFacePipeline

# 定义函数:从模型输出中提取 assistant 回复的主要内容
def extract_assistant_reply(output_text: str) -> str:
    marker"<|im_start|>assistant"# Qwen 模型中的 assistant 回复起始标记
    if marker in output_text:
        # 如果存在标记,则提取其后的内容,去除前后的换行和空格
        return output_text.split(marker, 1)[-1].lstrip("\n").strip()
    # 如果没有标记,直接返回清理后的文本
    return output_text.strip()

# 指定本地大语言模型的路径
model_path"本地大模型路径"

# 加载分词器(Tokenizer),信任远程代码以支持特定模型格式
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)

# 加载模型本体,设置自动推断数据类型、设备映射(如使用GPU)
model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype="auto", device_map="auto")

# 使用 transformers 的 pipeline 创建文本生成器,设定最大输出长度
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=2048)

# 将 pipeline 封装成 LangChain 可用的 HuggingFacePipeline 实例
llm = HuggingFacePipeline(pipeline=pipe)

# 构造消息列表,使用结构化格式(system + user),符合聊天模板要求
messages = [
    {"role""system""content""你是一个乐于助人的中文助手。"},
    {"role""user""content""你是谁?"}
]

# 使用 tokenizer 的 apply_chat_template 方法,将 messages 转换为模型期望的 prompt 格式
prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)

# 调用 LangChain 封装的模型接口,获取原始模型回复
ai_msg = llm.invoke(prompt)

# 提取模型回复中真正需要展示的 assistant 内容
reply = extract_assistant_reply(ai_msg)

# 打印最终结果,只展示 assistant 的自然语言回答部分
print(reply)

总结

通过本节内容,我们深入分析了在使用LangChain接入本地大模型时出现的“回复内容异常”问题,找到了问题的根源——未正确使用大模型自带的提示词模板。随后,我们以Qwen为例,介绍了如何使用tokenizer.apply_chat_template方法规范构造对话输入,并通过简单的后处理函数提取干净的模型回复内容。这个过程不仅提升了模型输出的准确性与可控性,也为后续构建更复杂、更多轮的对话系统打下了坚实基础。未来在接入其他模型时,大家也可以参考这种方式,灵活适配各类提示词格式,确保模型能够“读懂我们想让它读懂的东西”。

原文地址:https://mp.weixin.qq.com/s/wsXJMOMntiBGC0K-Jbe0UQ