Files
notes/resource/ai/Jinja 模板引擎.md
T
Docker7530 6b50219f55 1776654103
2026-04-20 11:01:47 +08:00

5.6 KiB
Raw Blame History

一种"带插件的文本生成器"。它允许你创建一个包含变量和逻辑的"模板"文件,然后在渲染时填入具体数据,最终生成想要的文本格式(如 HTML、JSON、Markdown 等)。

它的语法简单直观,主要有三种核心元素:

  • {{ … }}:用于输出变量值,例如 {{ user_name }} 会在渲染时被具体的用户名替换。
  • {% … %}:用于执行逻辑,例如循环 {% for item in items %} 或判断 {% if user_logged_in %}
  • {# … #}:用于添加注释,不会出现在最终输出中。

提示词(Prompt)工程

主要用于动态、精确地构建和管理提示词(Prompt)。其核心价值在于将"提示词模板"与"可变数据"分离。

1. 构建动态、结构化的提示词

在实际应用中,很少会发送固定的文本给 AI。你需要将用户问题、聊天历史、检索到的知识等动态拼接到提示词中。Jinja 让这件事变得优雅和可控。

示例:一个智能客服的提示词模板

你是一名专业的客服助手,名叫"小智"。
请基于以下提供的资料,回答用户的问题。
如果资料中找不到答案,请直接说"不知道",不要编造。

### 参考资料:
{% for doc in search_results %}
- {{ doc.title }}: {{ doc.content }}
{% endfor %}

### 对话历史:
{% for msg in chat_history %}
{{ msg.role }}: {{ msg.content }}
{% endfor %}

### 用户当前的问题是:
{{ current_question }}

请给出你的回答:

在这个模板中,search_resultschat_historycurrent_question 都是变量。渲染时,Jinja 会循环填入所有搜索结果和对话记录,生成一个结构清晰、信息完整的提示词。

2. 统一聊天模型的提示词格式(Chat Template

这是 Jinja 在 AI 领域最"杀手级"的应用。每个大模型(如 Llama、Mistral、ChatGLM)对聊天对话的输入格式要求都不同。Chat Template 就是用 Jinja 语法写的一段脚本,定义了如何将 messages 数组(包含 role 和 content 的对话)转换成模型能理解的单一字符串。

一个简化的 Chat Template 示例:

{% for message in messages %}
    {% if message['role'] == 'user' %}
        {{ '[INST] ' + message['content'] + ' [/INST]' }}
    {% elif message['role'] == 'assistant' %}
        {{ ' ' + message['content'] + ' </s>' }}
    {% endif %}
{% endfor %}
  • 输入(Messages: [{"role": "user", "content": "你好"}, {"role": "assistant", "content": "你好!有什么可以帮你的?"}]
  • 输出(Prompt String: [INST] 你好 [/INST] 你好!有什么可以帮你的? </s>

AI 框架如 Hugging Face Transformers 已经内置了对 Jinja Chat Template 的支持。你只需调用 tokenizer.apply_chat_template(messages),它就会自动使用模型对应的 Jinja 模板来生成正确的输入,极大地简化了开发流程。

3. 在 AI 工作流和数据转换中作为"胶水"

在复杂的 AI 应用(如 RAG 检索增强生成、Agent)和工作流平台(如阿里云的 AI Studio、Azure 的 Prompt Flow)中,Jinja 常被用作轻量级的数据处理和格式化工具

例如,可以将检索到的多个相关文档片段,通过一个 Jinja 模板快速格式化为一个结构清晰的 Markdown 列表,再输入给大模型。

{% if not add_generation_prompt is defined %}{% set add_generation_prompt = false %}{% endif %}{% set ns = namespace(is_first=false, is_tool=false, is_output_first=true, system_prompt='') %}{%- for message in messages %}{%- if message['role'] == 'system' %}{% set ns.system_prompt = message['content'] %}{%- endif %}{%- endfor %}{{bos_token}}{{ns.system_prompt}}{%- for message in messages %}{%- if message['role'] == 'user' %}{%- set ns.is_tool = false -%}{{'<User>' + message['content']}}{%- endif %}{%- if message['role'] == 'assistant' and message['content'] is none %}{%- set ns.is_tool = false -%}{%- for tool in message['tool_calls']%}{%- if not ns.is_first %}{{'<Assistant><tool▁calls▁begin><tool▁call▁begin>' + tool['type'] + '<tool▁sep>' + tool['function']['name'] + '\\n' + '```json' + '\\n' + tool['function']['arguments'] + '\\n' + '```' + '<tool▁call▁end>'}}{%- set ns.is_first = true -%}{%- else %}{{'\\n' + '<tool▁call▁begin>' + tool['type'] + '<tool▁sep>' + tool['function']['name'] + '\\n' + '```json' + '\\n' + tool['function']['arguments'] + '\\n' + '```' + '<tool▁call▁end>'}}{{'<tool▁calls▁end><end▁of▁sentence>'}}{%- endif %}{%- endfor %}{%- endif %}{%- if message['role'] == 'assistant' and message['content'] is not none %}{%- if ns.is_tool %}{{'<tool▁outputs▁end>' + message['content'] + '<end▁of▁sentence>'}}{%- set ns.is_tool = false -%}{%- else %}{% set content = message['content'] %}{% if '</think>' in content %}{% set content = message['content'].replace('</think>', '').split('<think>')[-1] %}{% endif %}{{'<Assistant>' + content + '<end▁of▁sentence>'}}{%- endif %}{%- endif %}{%- if message['role'] == 'tool' %}{%- set ns.is_tool = true -%}{%- if ns.is_output_first %}{{'<tool▁outputs▁begin><tool▁output▁begin>' + message['content'] + '<tool▁output▁end>'}}{%- set ns.is_output_first = false %}{%- else %}{{'\\n<tool▁output▁begin>' + message['content'] + '<tool▁output▁end>'}}{%- endif %}{%- endif %}{%- endfor -%}{% if ns.is_tool %}{{'<tool▁outputs▁end>'}}{% endif %}{% if add_generation_prompt and not ns.is_tool %}{{'<Assistant>'}}{% endif %}