LiteRT-LM 中 Qwen 2.5 的 Chat Template 与模型元数据配置深度解析
2026/9/17 22:37:21 网站建设 项目流程

LiteRT-LM 中 Qwen 2.5 的 Chat Template 与模型元数据配置深度解析

【免费下载链接】LiteRT-LMLiteRT-LM is Google's production-ready, high-performance, open-source inference framework for deploying Large Language Models on edge devices.项目地址: https://gitcode.com/GitHub_Trending/li/LiteRT-LM

LiteRT-LM 在 models/qwen2_5/ 目录下为 Qwen 2.5 模型家族提供了官方(canonical)的提示词模板(Chat Template)与模型元数据(LlmMetadata)配置。本文以该目录的 README.md 为骨架,结合 chat_template.jinja、LlmMetadataProto.pbtext 及配套测试用例,完整讲解这套模板的渲染规则、工具调用(Tool Calling)标准化格式、元数据字段含义,以及如何通过 Bazel 测试验证模板与 golden 输出的一致性,帮助你在端侧推理场景中正确使用并扩展 Qwen 2.5 的对话能力。

一、目录结构与职责定位

按照 LiteRT-LM 的模型配置规范,每个受支持模型家族在 models/ 下拥有独立目录,Qwen 2.5 目录内包含三类核心资产:

文件职责
chat_template.jinja官方提示词模板,基于 Jinja2 语法(运行时由 Minijinja 密闭渲染),负责将结构化对话轮次与工具声明转换成模型特定的提示词字符串
LlmMetadataProto.pbtext模型元数据(text-format protobuf),描述起始/停止 token、采样参数、模型类型、是否支持函数调用,并内嵌一份与chat_template.jinja内容一致的 Jinja 模板字符串
BUILDBazel 构建配置,通过chat_template_test宏把模板与元数据接入自动化测试

这三者被统一约束为"一致性"关系:LlmMetadataProto.pbtext中的jinja_prompt_template字段必须与chat_template.jinja逐字节一致,否则测试会失败(详见下文"测试与验证"章节)。

二、Chat Template 全流程逐段解析

chat_template.jinja 共 68 行,处理五类职责:内容格式化宏、System/工具声明块、普通对话轮次、助手工具调用、工具响应合并。下面逐段说明。

2.1format_content宏:多模态内容归一化

模板开头定义一个内容格式化宏,用于把消息的content列表转成纯文本:

{%- macro format_content(content) -%} {%- for item in content -%} {%- if item['type'] == 'text' -%} {{- item['text'] -}} {%- elif item['type'] == 'tool_response' -%} {%- if item['response'] is mapping or item['response'] is sequence -%} {{- item['response'] | tojson -}} {%- else -%} {{- item['response'] | string -}} {%- endif -%} {%- endif -%} {%- endfor -%} {%- endmacro -%}

关键点:

  • 仅处理texttool_response两种 content part;tool_response中的response若是 JSON 对象/数组则用内置tojson过滤器序列化,若是标量则转字符串。这与 models/README.md 中"content 是严格的多模态 part 列表"的约定一致——纯文本也要包装为{"type": "text", "text": "..."}对象。
  • 模板对imageaudiovideo等 part 不输出文本(Qwen 2.5 纯文本家族场景下通常不会出现),因此这一实现是安全的。

2.2 System 与工具声明块

{%- set loop_messages = messages[1:] if messages[0]['role'] == 'system' else messages -%} {#- Handle System/Tool Definitions Block -#} {%- if tools or messages[0]['role'] == 'system' -%} {{- '<|im_start|>system\n' -}} {%- if messages[0]['role'] == 'system' -%} {{- format_content(messages[0]['content']) -}} {%- endif -%} {%- if tools -%} {%- if messages[0]['role'] == 'system' -%} {{- '\n\n' -}} {%- endif -%} {{- "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" -}} {%- for tool in tools -%} {{- '\n' + tool | tojson -}} {%- endfor -%} {{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call>" -}} {%- endif -%} {{- '<|im_end|>\n' -}} {%- endif -%}

逻辑要点:

  1. 系统消息提前剥离:若首条消息是system,先取出其内容单独渲染成system角色开头的块,并从后续循环中剔除(messages[1:]),保证系统提示只出现一次。
  2. 工具签名注入:当存在tools列表时,每个工具声明通过tojson输出为单行 JSON,整体包裹在<tools>...</tools>XML 标签内,并附上一段固定的指令文本,要求模型以<tool_call>内 JSON 对象的形式返回函数名与参数。这是 README 中"Features & Standardization"第 1 点的落地实现。

2.3 普通消息循环:user / system / assistant

{%- for message in loop_messages -%} {%- set content = format_content(message['content']) -%} {%- if message['role'] == 'user' or message['role'] == 'system' -%} {{- '<|im_start|>' + message['role'] + '\n' + content + '<|im_end|>\n' -}} {%- elif message['role'] == 'assistant' -%} ... {%- elif message['role'] == 'tool' -%} ... {%- endif -%} {%- endfor -%}

Qwen 2.5 使用 ChatML 风格的分隔符:每条消息以<|im_start|>{role}\n开头、<|im_end|>\n结尾。这一约定同时在元数据的prompt_templates中登记(见第三节)。

2.4 助手工具调用(tool_calls)渲染

{{- '<|im_start|>' + message['role'] + '\n' + content -}} {%- if message['tool_calls'] -%} {%- for tool_call in message['tool_calls'] -%} {%- if content or not loop.first -%} {{- '\n' -}} {%- endif -%} {%- set function = tool_call['function'] if 'function' in tool_call else tool_call -%} {%- set args = function['arguments'] if function['arguments'] is string else function['arguments'] | tojson -%} {{- '<tool_call>\n{"name": "' + function['name'] + '", "arguments": ' + args + '}\n</tool_call>' -}} {%- endfor -%} {%- endif -%} {{- '<|im_end|>\n' -}}

要点:

  • 兼容两种tool_calls结构:标准 OpenAI 风格(含function子对象)或扁平结构(直接含name/arguments)。
  • arguments若已是 JSON 字符串则原样输出,否则tojson序列化。
  • 每条工具调用渲染为一个<tool_call>...</tool_call>块;多条调用之间用换行分隔,支持并行工具调用场景。

2.5 工具响应合并到 user 角色

{%- elif message['role'] == 'tool' -%} {%- if loop.first or loop_messages[loop.index0 - 1]['role'] != 'tool' -%} {{- '<|im_start|>user' -}} {%- endif -%} {{- '\n<tool_response>\n' + content + '\n</tool_response>' -}} {%- if loop.last or loop_messages[loop.index0 + 1]['role'] != 'tool' -%} {{- '<|im_end|>\n' -}} {%- endif -%} {%- endif -%}

这是 README 中"Multi-step tool responses are wrapped within<tool_response>tags under thetoolrole"的具体实现,有两个巧妙设计:

  1. 连续tool消息合并:相邻的多条tool响应会被折叠进同一个user块(只有第一条前面输出<|im_start|>user,最后一条后面才输出<|im_end|>),避免碎片化的消息结构。
  2. 角色映射:最终渲染时tool响应统一以user角色承载,内容外层包<tool_response>...</tool_response>,符合 Qwen 2.5 训练数据中"工具结果由 user 转述"的格式习惯。

2.6 生成提示(Generation Prompt)

{%- if add_generation_prompt -%} {{- '<|im_start|>assistant\n' -}} {%- endif -%}

add_generation_prompt为真时,在末尾追加空的assistant起始块,引导模型开始续写。该变量在 models/README.md 中登记为可选参数,默认值为true

三、LlmMetadataProto.pbtext:模型元数据逐字段详解

LlmMetadataProto.pbtext 是对应 runtime/proto/llm_metadata.proto 中LlmMetadataProto消息的 text-format 实例,头部注释明确标注了 proto 文件与消息类型。各字段含义如下:

字段说明
start_token<\|endoftext\|>序列起始 token
stop_tokens<\|im_end\|>停止生成 token,即 ChatML 消息结束符
prompt_templates.userprefix<\|im_start\|>user\n,suffix<\|im_end\|>\n非 Jinja 路径下的 user 消息包装格式
prompt_templates.modelprefix<\|im_start\|>assistant\n,suffix<\|im_end\|>\n非 Jinja 路径下的 assistant 消息包装格式
sampler_paramsTOP_P类型,k=40p=0.95temperature=1.0默认采样策略:top-p 采样,p 值 0.95
max_num_tokens4096单次推理最大生成长度(默认值)
llm_model_typeqwen2p5 {}模型家族标记,驱动引擎选择对应的计算/图执行路径
supports_function_callingtrue声明该模型支持函数调用,是启用工具链路的前提
jinja_prompt_template(整段模板字符串)内嵌的 Jinja 模板,与chat_template.jinja内容必须一致
min_runtime_version"0.18.0"运行该模型配置所需的最低 LiteRT-LM 运行时版本

两个值得注意的工程细节:

  • 双轨模板机制:元数据中既有结构化的prompt_templates(简单 user/model 包装),又有完整的jinja_prompt_template。运行时优先使用 Jinja 渲染复杂对话(多轮、工具调用),结构化模板则作为轻量兜底。为保证二者不会分叉,测试会强制校验"pbtext 内嵌模板 == 磁盘上的.jinja文件"。
  • supports_function_calling与模型类型qwen2p5类型 +true声明共同构成工具调用能力的声明式契约;引擎侧只有在元数据明确声明时才允许注入工具相关处理逻辑。

四、Tool Calling 标准化格式速览

结合 README 与 chat_template.jinja 的实现,Qwen 2.5 在 LiteRT-LM 中的工具调用统一遵循以下三组 XML 标签约定:

  1. 工具签名声明:放入 system 提示中的<tools>...</tools>标签内,每个工具以单行 JSON(OpenAPI 风格)表示,如:

    {"type": "function", "function": {"name": "get_weather", "description": "Get current weather for a location.", "parameters": {"type": "object", "properties": {"location": {"type": "string", "description": "City name"}}, "required": ["location"]}}}
  2. 助手函数调用:以<tool_call>...</tool_call>包裹 JSON 对象,格式固定为{"name": <function-name>, "arguments": <args-json-object>},如:

    <tool_call> {"name": "get_weather", "arguments": {"location": "London"}} </tool_call>
  3. 工具响应:多步工具结果包裹在<tool_response>标签下,最终承载于user角色消息中:

    <tool_response> {"location": "London", "temperature": "18C"} </tool_response>

这套格式是模板硬编码的标准化指令,模型在 system 提示中读到<tools>后,会被引导输出严格匹配<tool_call>格式的 JSON,从而被下游解析器稳定提取。

五、输入消息结构(渲染入口契约)

模板的输入由 LiteRT-LM 运行时构造,顶层字段在 models/README.md 中给出正式定义:

字段类型说明
messagesarray(必填)对话历史,元素为消息对象
toolsarray(可选)可用的工具(函数声明)列表
enable_thinkingboolean(可选)是否让模型先输出推理过程
add_generation_promptboolean(可选)是否追加模型轮次前缀,默认true
bos_tokenstring(可选)序列起始 token,如<bos><s>

消息对象的核心角色有四种:system(系统指令、人格与工具 schema)、user(用户提问)、assistant(模型回复,可含tool_calls)、tool(工具执行结果,content 中为tool_responsepart)。arguments既可以是 JSON 对象也可以是序列化 JSON 字符串;当存在tool_callscontent允许为空。

六、测试与验证:如何证明模板正确

6.1 BUILD 中的测试接线

models/qwen2_5/BUILD 通过chat_template_test宏生成测试目标:

load("//models:chat_template_test.bzl", "chat_template_test") exports_files([ "chat_template.jinja", "LlmMetadataProto.pbtext", ]) chat_template_test( name = "chat_template_test", chat_template = "chat_template.jinja", pbtext_files = ["LlmMetadataProto.pbtext"], )

pbtext_files的传入意味着测试还会校验元数据内嵌模板与磁盘模板的一致性(见 chat_template_test.bzl 的参数说明)。

6.2 测试用例与 golden 输出

Qwen 2.5 提供了三组测试输入(位于 testdata/input/):

  • system_instruction.json:带系统指令的普通多轮对话,验证<|im_start|>system块的渲染;
  • tools.json:完整工具链路(声明工具 → 两次工具调用 → 两次工具响应 → 最终文本回复),验证并行/多步工具调用;
  • tool_response_ending.json:以工具响应结尾的对话,验证"相邻 tool 消息合并进单个 user 块"的边界行为。

每组输入在 testdata/golden/ 下有两个期望输出:*.txt(普通模式)与*-thinking.txt(thinking 模式)。以 tools.txt 为例,其渲染结果完整展示了# Tools声明块、<tools>签名、<tool_call>调用、<tool_response>结果合并以及末尾的<|im_start|>assistant生成提示。

6.3 测试运行器的工作原理

models/chat_template_test_runner.cc 是测试的可执行主体,其关键流程:

  1. 读取--chat_template--input_dir--golden_dir等命令行参数(另支持--update_golden更新 golden、--pbtext_files校验元数据、--mirror_chat_templates校验镜像模板);
  2. runtime/components/prompt_template.h提供的PromptTemplate构造模板对象并调用Apply()渲染每个输入 JSON;
  3. 每个输入同时以enable_thinking=false/true两种模式渲染,若存在对应 thinking golden 文件则一并比对;
  4. 逐字符比对渲染结果与 golden 文件(EXPECT_EQ),不匹配即报告输入/输出/golden 三路信息;
  5. 最后解析各pbtext文件中的jinja_prompt_template字段,与磁盘模板逐字节比对。

运行全部模型家族的模板测试只需:

bazel test //models/...

新增测试用例时,只需在testdata/input/放入输入 JSON、在testdata/golden/放入期望文本(或先以--update_golden生成),无需改动 C++ 代码。

七、在推理流程中的位置与扩展建议

从源码结构看,这套模板与元数据在 LiteRT-LM 推理链路中的定位可以概括为:运行时将多轮会话(含工具声明与执行结果)整理为messages/tools结构,经PromptTemplate渲染为模型输入文本;引擎依据LlmMetadataProto中的停止 token、采样参数与模型类型执行生成,并通过supports_function_calling决定是否启用工具解析与执行回路。

若你要为其他 Qwen 2.5 变体(如更长上下文、带思考模式的版本)复用此配置,建议保持chat_template.jinjajinja_prompt_template的镜像关系不变,仅调整max_num_tokenssampler_paramsstop_tokens等生成相关字段,并为本目录补齐对应的 testdata 用例,确保任何改动都经过 golden 回归验证。

【免费下载链接】LiteRT-LMLiteRT-LM is Google's production-ready, high-performance, open-source inference framework for deploying Large Language Models on edge devices.项目地址: https://gitcode.com/GitHub_Trending/li/LiteRT-LM

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询