当前位置: 首页 > news >正文

vllm0.8.5:自定义聊天模板qwen_nonthinking.jinja,从根本上避免模型输出<think>标签

一、环境

vllm:0.8.5

大语言模型:DeepSeek-R1-Distill-Qwen-1.5B

内存:128G

GPU:无

二、qwen_nonthinking.jinja内容如下:

2.1、内容解读

第一部分:如果存在可用工具(tools

{%- if tools %}

判断是否存在外部提供的工具(函数),如果有,则进入工具模式。

显示系统头 + 工具说明

    {{- '<|System|>\n' }}    {%- if messages[0].role == 'system' %}

        {{- messages[0].content + '\n\n' }}

    {%- endif %}

  • 输出 <|System|> 标记。
  • 如果第一条消息是 system 角色,输出其内容,并加两个换行。
输出工具列表说明

    {{- "# Tools\n\nYou may call one or more functions..." }}    <tools>

    {%- for tool in tools %}

        {{- "\n" }}

        {{- tool | tojson }}

    {%- endfor %}

    </tools>

  • 向模型说明可以使用的工具。
  • 使用 <tools>...</tools> 包裹每个工具的 JSON 定义(如函数名、参数等)。
  • tool | tojson 将 Python/JSON 对象转为标准 JSON 字符串。
指导模型如何调用函数

    {{- "\n</tools>\n\nFor each function call... within <tool_call><tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": ...}\n<tool_call>\n" }}

告诉模型:当你想调用函数时,请使用如下格式:

<tool_call>{"name": "get_weather", "arguments": {"city": "Beijing"}}</tool_call>

注意:这里的 <tool_call> 是一个自定义 XML-like 标签,用来清晰地标记函数调用的开始和结束,避免与普通文本混淆。

第二部分:如果没有工具

{%- else %}    {%- if messages[0].role == 'system' %}

        {{- '<|System|>\n' + messages[0].content + '\n<|End|>\n' }}

    {%- endif %}

{%- endif %}

  • 不显示工具相关说明。
  • 仅输出第一条 system 消息(如果有),并用 <|End|> 结束。


第三部分:设置命名空间变量(用于判断上下文)

{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}

创建一个命名空间 ns,用于在循环中跨迭代共享变量:

  • multi_step_tool: 判断是否处于多步工具调用流程。
  • last_query_index: 记录最后一次“用户主动提问”的位置。
向前遍历消息,判断是否为多步工具调用

{%- for message in messages[::-1] %}    {%- set index = (messages|length - 1) - loop.index0 %}

    {%- if ns.multi_step_tool and message.role == "user" and message.content is string and not(message.content.startswith('<tool_call>') and message.content.endswith('<tool_call>')) %}

        {%- set ns.multi_step_tool = false %}

        {%- set ns.last_query_index = index %}

    {%- endif %}

{%- endfor %}

  • 倒序遍历 messages(从最新到最旧)。
  • 找到第一个 role == "user" 且内容不是以 <tool_call> 开头/结尾的消息(即不是工具调用指令)。
  • 把这个消息的索引记为 last_query_index,表示这是用户最后一次“真正提问”的位置。
  • 用于后续判断哪些回复需要加上 <|End|>

作用:防止在工具调用链中错误地终止对话流。


第四部分:主循环 —— 遍历每条消息并格式化输出

{%- for message in messages %}

对每条消息进行处理。

设置内容变量

    {%- if message.content is string %}        {%- set content = message.content %}

    {%- else %}

        {%- set content = '' %}

    {%- endif %}

安全获取 content,避免非字符串类型出错。


情况一:用户消息(User)

    {%- if message.role == "user" %}        {{- '<|User|>\n' + content + '\n<|End|>\n' }}

格式:

<|User|>[用户内容]

<|End|>


情况二:助手消息(Assistant)

    {%- elif message.role == "assistant" %}

处理 <think>...</think> 推理块(可选)

        {%- set reasoning_content = '' %}        {%- if '</think>' in content %}

            {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}

            {%- set content = content.split('</think>')[-1].lstrip('\n') %}

        {%- endif %}

  • 如果内容包含 <think>推理内容</think>,则提取推理部分(虽然这里没使用),并把剩余部分作为正式回复。
  • 这是为了支持“先思考,再回答”的格式。

根据是否是“最后一问之后的回复”,决定是否加 <|End|>

        {%- if loop.index0 > ns.last_query_index %}            {{- '<|Assistant|>\n' + content.lstrip('\n') + '\n<|End|>\n' }}

        {%- else %}

            {{- '<|Assistant|>\n' + content + '\n<|End|>\n' }}

        {%- endif %}

  • 如果该回复是在用户最后一次提问之前,不加 <|End|>(可能是中间步骤)。
  • 如果是在之后,加上 <|End|> 表示回应完成。

处理工具调用(Tool Calls)

        {%- if message.tool_calls %}            {%- for tool_call in message.tool_calls %}

                {%- if (loop.first and content) or (not loop.first) %}

                    {{- '\n' }}

                {%- endif %}

                {%- if tool_call.function %}

                    {%- set tool_call = tool_call.function %}

                {%- endif %}

                {{- '<tool_call>\n{"name": "' }}

                {{- tool_call.name }}

                {{- '", "arguments": ' }}

                {%- if tool_call.arguments is string %}

                    {{- tool_call.arguments }}

                {%- else %}

                    {{- tool_call.arguments | tojson }}

                {%- endif %}

                {{- '}\n</tool_call>' }}

            {%- endfor %}

        {%- endif %}

  • 如果助手要调用函数,生成如下格式:

<tool_call>{"name": "search_web", "arguments": {"query": "天气预报"}}</tool_call><tool_call>{"name": "get_time", "arguments": {}}</tool_call>

  • 支持多个函数调用。
  • arguments 可以是字符串或对象,自动处理。


情况三:工具执行结果(Tool)

    {%- elif message.role == "tool" %}        {%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}

            {{- '<|User|>' }}

        {%- endif %}

        {{- '\n<tool_call>\n' }}

        {{- content }}

        {{- '\n</tool_call>' }}

        {%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}

            {{- '\n<|End|>\n' }}

        {%- endif %}

  • 工具返回的结果被视为“用户侧输入”,所以用 <|User|> 标记。
  • 多个连续的 tool 消息会被合并到同一个 <|User|> 块中。
  • 每个工具结果用 <tool_call>\ntool_result\n</tool_call> 包裹,与函数调用请求对称。
  • 最后一个工具结果后加 <|End|>

示例输出:

<|User|><tool_call>

{"result": "北京今天晴,气温25°C"}

</tool_call>

<tool_call>

{"result": "当前时间:2025-08-03 18:30"}

</tool_call>

<|End|>


 第五部分:生成提示(用于触发模型回复)

{%- if add_generation_prompt %}    {{- '<|Assistant|>\n<think>\n\n</think>\n\n' }}

{%- endif %}

  • 如果设置了 add_generation_prompt=True,在最后加上:

<|Assistant|><think>

</think>

  • 表示“现在轮到模型输出了”,并且鼓励模型先进行内部推理(<think>)。
  • 这个标记不会被模型重复输出,而是作为生成起点。

总结:这个模板的作用

功能

实现方式

✅ 支持函数调用

用 <tool_call>...<tool_call> 包裹 JSON 格式的调用请求

✅ 接收工具结果

工具返回也用 <tool_call>...</tool_call> 标记,归为用户输入

✅ 支持多步工具调用

通过 ns.last_query_index 判断上下文

✅ 支持内部推理

使用 <think>...</think> 分隔思考与回答

 

http://www.dtcms.com/a/313600.html

相关文章:

  • Docker环境离线安装指南
  • C++与Go的匿名函数编程区别对比
  • SPI入门(基于ESP-IDF-v5.4.1)
  • accept4系统调用及示例
  • ELECTRICAL靶场
  • 检索召回率优化探究三:基于LangChain0.3集成Milvu2.5向量数据库构建的智能问答系统
  • 思途JSP学习 0802(项目完整流程)
  • Fay数字人如何使用GPT-SOVITS进行TTS转换以及遇到的一些问题
  • 写作路上的迷茫与突破
  • 推荐系统学习笔记(八)其他召回通道
  • ssh服务器端口和本地端口映射
  • 基于Python 批量导入实体与关系到 Neo4j 数据库的完整实践
  • jconsole与jvisualvm监控
  • 数据结构基础 - 平衡二叉树
  • async/await和Promise之间的关系是什么?(补充)
  • NSA稀疏注意力深度解析:DeepSeek如何将Transformer复杂度从O(N²)降至线性,实现9倍训练加速
  • 能表示旋转的矩阵是一个流形吗?
  • 【大模型篇】:GPT-Llama-Qwen-Deepseek
  • 数据结构重点内容
  • Go语言实战案例:多协程并发下载网页内容
  • 《 ThreadLocal 工作机制深度解析:高并发场景的利与弊》
  • Mysql深入学习:InnoDB执行引擎篇
  • C++ : 反向迭代器的模拟实现
  • 【图像处理基石】如何使用deepseek进行图像质量的分析?
  • vllm0.8.5:思维链(Chain-of-Thought, CoT)微调模型的输出结果包括</think>,提供一种关闭思考过程的方法
  • MCP协议:CAD地图应用的AI智能化解决方案(唯杰地图MCP)
  • 【数据结构与算法】数据结构初阶:排序内容加餐(二)——文件归并排序思路详解(附代码实现)
  • 【C++】面向对象编程
  • C语言(长期更新)第8讲 函数递归
  • 网络通信与Socket套接字详解