ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

如何读懂 Fara1.5-9B-OptiQ-4bit 的 Chat Template:Jinja 模板如何实现工具调用与思考模式

如何读懂 Fara1.5-9B-OptiQ-4bit 的 Chat Template:Jinja 模板如何实现工具调用与思考模式
  • 人工智能
  • 大模型
  • 基础模型
  • 本地部署
  • 多模态
  • 计算机视觉
  • AI Agent

【免费下载链接】Fara1.5-9B-OptiQ-4bit

项目地址:https://ai.gitcode.com/hf_mirrors/mlx-community/Fara1.5-9B-OptiQ-4bit
点击查看免费下载

Fara1.5-9B-OptiQ-4bit是微软开源的计算机使用(computer-use)视觉智能体模型 Fara1.5-9B 的 MLX 混合精度量化版本,专为 Apple Silicon 本地推理打造。很多人下载模型后都会问一个问题:模型的"说话格式"到底是怎么定的?答案就在仓库根目录的 chat_template.jinja 里。这份 Jinja 模板同时负责两件事:工具调用(Function Calling)的消息拼装与思考模式(Thinking Mode)的开关控制,是理解这个模型行为的关键钥匙。

Chat Template 是什么:模型格式的"翻译官"

大模型本身只认识 token 序列。你在 API 里发的messages数组(system / user / assistant / tool 多轮对话),必须经过一层"翻译"才能变成模型训练时见过的文本格式——这层翻译就是Chat Template。

在 Fara1.5-9B-OptiQ-4bit 仓库中,相关文件的分工如下:

文件作用
chat_template.jinjaJinja 模板,定义消息拼装规则(本文主角)
tokenizer_config.json特殊 token、分词器参数(Qwen2Tokenizer、工具解析器类型等)
config.json模型架构与 4/8-bit 混合量化配置
generation_config.json生成参数(如eos_token_id: 248044)
optiq_metadata.jsonOptiQ 量化元数据(目标 5.0 bpw,实测 5.21 bpw)

几个值得注意的模板变量:

  • messages:对话消息列表,缺失时模板会直接抛出No messages provided.异常;
  • tools:可选的工具(函数)Schema 列表,决定是否注入工具系统提示;
  • add_generation_prompt:是否在末尾追加assistant生成提示;
  • enable_thinking:思考模式总开关,取值true/false。

视觉占位符:一张图片如何变成文字

Fara1.5 是视觉语言模型,用户消息里可以混排文本和图片。模板顶部的render_content宏(chat_template.jinja)负责把多模态 content 渲染成纯文本:

  • 遇到图片项 → 输出特殊 tokenimage,可选前缀Picture N:(由add_vision_id控制);
  • 遇到视频项 → 输出video特殊 token;
  • system 消息里出现图片/视频会被直接拒绝(System message cannot contain images.),这是防注入的硬约束;
  • 图片和视频各有独立计数器(image_count/video_count),多张图时编号不混乱。

对应的 token 定义在 tokenizer_config.json 中:image_token为image,video_token为video,视觉边界 token 为vision_bos_token/vision_eos_token。处理器Qwen3VLProcessor则负责把真实图片切块并与这些占位符对齐(像素范围由min_pixels: 3136与max_pixels: 12845056约束)。

💡 换句话说:图片在模型眼里就是一串image占位符,真正的像素信息走视觉塔(vision tower),而它在本仓库中以 bf16 精度单独存放在 optiq/optiq_vision.safetensors——这也是"同一个仓库既能纯文本跑、又能图文跑"的原因。

工具调用第一步:tools 如何注入 system 消息

当请求里带tools参数时,模板会在最前面生成一个特殊的 system 块(chat_template.jinja):

  1. 以# Tools开头,声明"你有以下函数可用";
  2. 用<tools>标签包裹全部工具,每个工具用tojson序列化为一行 JSON(即标准 OpenAI Function Calling 的 Schema 格式);
  3. 附上一段"格式铁律":调用函数只能输出function_call包裹的function=...嵌套结构,必填参数必须给出,推理说明要放在调用之前而非之后;
  4. 如果消息列表第一条本来就是 system 消息,用户自写的 system 内容会被追加到这段工具说明之后(用空行分隔),而不是被覆盖。

不带tools时走 else 分支(chat_template.jinja):只输出system块(如果存在),干净利落。

多步工具链的"哨兵":last_query_index

这是整份模板里最精妙的机制。看一段核心逻辑(chat_template.jinja):

ns = namespace(multi_step_tool=true, last_query_index=消息总数 - 1) 从最后一条消息向前扫描: 若遇到 user 消息,且其内容【不是】tool_response 包裹 → multi_step_tool = false,记录 last_query_index

它的目的:判断当前是否处于"工具返回结果、等待模型继续"的多步调用中间态。

  • 如果最后一条 user 消息只是tool_response回传,说明真正的"人类提问"在更早的某处——这个位置就是last_query_index;
  • 若全程都找不到一条真正的用户提问,模板会抛出No user query found in messages.,防止拼出一段无主的对话;
  • 这个索引后面还会决定一件事:思考块只出现在"最后一轮真实用户提问之后"的 assistant 消息中(见下文)。

三种角色消息如何被拼装

拿到上述铺垫后,模板按顺序遍历messages,对每个角色执行不同规则(chat_template.jinja):

user 消息:最直接的包装

user 这里是用户内容(含 image 占位符)

tool 消息:归并到 user 块

工具结果不独立成块,而是复用 user 角色(chat_template.jinja):

  • 连续的多个 tool 结果会合并进同一个 user 块,每个结果各自包在tool_response标签里;
  • 只有当下一条不是 tool 消息、或已到消息末尾时,才补上闭合标签与换行;
  • 这套"合并 + 延迟闭合"设计,正是多步工具链能保持格式稳定的原因。

assistant 消息:思考块 + 工具调用的主战场

assistant 分支(chat_template.jinja)做三件事:

① 拆分思考内容。如果消息带独立的reasoning_content字段,优先使用它;否则尝试从正文里按</think>拆分出思考部分,避免重复。

② 决定是否渲染思考块。只有当该消息位置晚于last_query_index(即属于最后一轮真实提问之后的回复)时,才渲染完整的think结构:

assistant think (推理过程) (正文)

历史轮次的 assistant 消息则只输出正文,不保留思考块——这让训练时的"只在末轮思考"的约定得以贯彻。

③ 拼装 function_call。如果消息带tool_calls,逐个函数渲染成嵌套结构(chat_template.jinja):

function_call function=get_weather parameter=city 北京 function_call

细节考究之处:参数是字典或列表时用tojson序列化,普通字符串原样输出;第一个调用若前面已有正文,会多补一个空行分隔;多个并行工具调用之间用独立标签串联。

思考模式的开关:enable_thinking 参数

模板的最后一段(chat_template.jinja)决定了"模型接下来往哪里写":

场景末尾追加内容效果
未开add_generation_prompt无纯补全模式
enable_thinking = falsethink空思考块,模型直接进正文
开启或未指定(默认)think模型先输出推理过程,再给答案

这就是 Qwen3 系列标志性的"推理开关":调用方只需在参数里传一个布尔值,模型就会在"边想边答"和"直接作答"两种模式间切换,无需改动提示词。配套地,tokenizer_config.json 中声明了tool_parser_type: "qwen3_coder",即框架侧用同一套 qwen3_coder 解析器把function_call标签反向解析回结构化的tool_calls——模板写、解析器读,形成闭环。

完整输出示例:一条消息经过模板之后

假设带有一个get_weather工具,最后一轮用户提问后请求生成,模板拼出的大致结构是:

system Tools 你有以下函数可用: tools {"type": "function", "function": {"name": "get_weather", ...}} tools (调用格式铁律……) 你是 Fara,一个计算机使用智能体…… system user 帮我查一下北京天气 user assistant think 用户想查北京天气,应调用 get_weather…… function_call function=get_weather parameter=city 北京 function_call

若工具结果返回,则追加一轮归并到 user 块,并以think结尾等待模型续写——整个多步链路由此循环。

写在最后:模板与量化的分工

把视角拉回整个仓库:

  • 模板层(chat_template.jinja):决定模型"怎么说话"——多模态占位、工具协议、思考协议全部在此编码;
  • 权重层:约 89.5 亿参数的 Qwen3_5 架构(config.json 中architectures: Qwen3_5ForConditionalGeneration),经 OptiQ 混合 4/8-bit 量化到 5.21 bits/weight,磁盘约 7.5G,视觉塔保持 bf16;
  • 运行层:一条命令optiq serve --model mlx-community/Fara1.5-9B-OptiQ-4bit即可在 Apple Silicon 上起 OpenAI 兼容端点,model_max_length高达 262144 token,长对话与截图序列都从容应对。

读懂这份 Jinja 模板,你就握住了与 Fara1.5 对话的底层语法:工具调用为何如此规整、思考模式为何能一键开关、多步 Agent 链为何不会乱——答案都在这 154 行里。

📌延伸阅读:README.md 中列出了模型标签(computer-use / web-agent / agent)与支持该仓库的加载器说明,值得一并查阅。

  • 人工智能
  • 大模型
  • 基础模型
  • 本地部署
  • 多模态
  • 计算机视觉
  • AI Agent

【免费下载链接】Fara1.5-9B-OptiQ-4bit

项目地址:https://ai.gitcode.com/hf_mirrors/mlx-community/Fara1.5-9B-OptiQ-4bit
点击查看免费下载

相关推荐

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进