Dify 中的 Prompt 是如何管理的?

很多人第一次接触 Dify 时,会把 Prompt 理解成一个文本框:写几句话,保存,然后交给模型。

从 Dify 1.14.2 的源码看,Prompt 更像一条流水线。它有两种来源,经过不同的保存与调用路径,最后在模板解析和 Prompt Transform 层汇合,才变成模型真正收到的消息。

这条链路可以回答几个常见问题:

  • Prompt IDE 里的内容保存在哪里?
  • {``{#context#}}{``{#query#}} 这些变量是谁注入的?
  • Simple 和 Advanced 到底差在哪里?
  • 对话标题、代码生成、知识库摘要使用的 Prompt,是否和应用 Prompt 是同一套配置?

答案是:它们共享解析能力,但不共享生命周期。

一、Dify 里有两个 Prompt 世界

Dify 的 Prompt 管理可以分成两条主线。

类型 主要使用者 来源与存储 作用
用户自定义 Prompt 应用开发者 Prompt IDE;应用配置保存到数据库,工作流节点保存到 workflow.graph 描述业务角色、任务规则、上下文处理方式
系统内置 Prompt Dify 平台自身 api/core/llm_generator/prompts.py 中的代码常量 驱动对话命名、代码生成、规则配置、摘要等平台功能

用户自定义 Prompt 服务于某个应用。开发者在 Prompt IDE 中写下角色设定、任务说明和变量,最终用户发起请求时,系统再把这些内容渲染成模型消息。

系统内置 Prompt 服务于 Dify 自己。比如,Dify 要为一轮对话生成标题,要根据任务描述生成 Prompt 模板,或要为知识库段落生成摘要,都需要调用内置 Prompt。应用用户通常看不到这些文本。

两条路径最后都会用到 PromptTemplateParser,但入口不同:用户 Prompt 由 SimplePromptTransformAdvancedPromptTransform 处理;内置 Prompt 由 LLMGenerator 的各个方法调用。

二、用户 Prompt 的完整生命周期

1. 在 Prompt IDE 中编辑

用户保存应用模型配置时,前端会向以下接口提交数据:

text 复制代码
POST /console/api/apps/{app_id}/model-config

Simple 模式的核心字段是 pre_prompt。Advanced 模式则提交完整的消息列表,例如:

json 复制代码
{
  "prompt_type": "advanced",
  "chat_prompt_config": {
    "prompt": [
      {"role": "system", "text": "你是一个专业助手。"},
      {"role": "user", "text": "{{#query#}}"}
    ]
  }
}

工作流中的 LLM 节点走另一条路。它不使用 AppModelConfig,而是把 Prompt 配置放进工作流图的 JSON 数据中,并随工作流版本管理。

2. API 层验证并补齐默认值

配置进入后端后,PromptTemplateConfigManager.validate_and_set_defaults() 会先检查结构:

  • prompt_type 只能是 simpleadvanced
  • Simple 模式的 pre_prompt 必须是字符串;
  • Advanced Chat 模式的消息条数不超过 10 条;
  • Completion 模式缺少角色前缀时,会补上 HumanAssistant

这一步把前端传来的 JSON 变成后续代码能够稳定处理的配置。它不是模型调用前的 Prompt 渲染,而是配置保存前的边界检查。

3. 保存为应用配置快照

应用配置保存到 PostgreSQL 的 app_model_configs 表。Simple 和 Advanced 的内容分别落在不同字段中:

  • prompt_type:模式;
  • pre_prompt:Simple 模式的核心 Prompt;
  • chat_prompt_config:Advanced Chat 配置;
  • completion_prompt_config:Advanced Completion 配置。

发布新版本时,系统会创建新的 AppModelConfig 记录,再让应用通过 app_model_config_id 指向当前版本。这样,Prompt 不是一份不断覆盖的文本,而是应用版本的一部分,历史配置也有机会回溯。

4. 运行时转换为 Prompt 实体

应用运行时,配置会经过 PromptTemplateConfigManager.convert(),转换成内存中的 PromptTemplateEntity。它包含模式、Simple 模板或 Advanced 模板等信息。

每次请求都会从配置构建这类实体。源码注释的设计意图是让请求拥有独立的运行时对象,而不是把带有请求变量的结果长期缓存下来。

5. 注入变量并渲染

系统会把两类变量交给模板解析器:

  1. 开发者在 user_input_form 中定义的业务变量,例如 {``{language}}{``{content}}
  2. Dify 在运行时注入的特殊变量,例如知识库上下文、当前问题和对话历史。

6. 生成消息并调用模型

经过 Transform 后,Prompt 不再只是模板字符串,而会变成 PromptMessage 列表,随后交给 ModelInstance.invoke_llm()

简化后的链路是:

text 复制代码
Prompt IDE / 工作流节点
        ↓
配置验证与版本保存
        ↓
PromptTemplateEntity
        ↓
SimplePromptTransform 或 AdvancedPromptTransform
        ↓
PromptTemplateParser 渲染变量
        ↓
PromptMessage 列表
        ↓
ModelInstance.invoke_llm()

三、模板引擎只负责替换,不负责写逻辑

PromptTemplateParser 是两类 Prompt 共用的底层能力。它做两件事:提取变量名,替换变量值。

普通变量与特殊变量

普通变量使用双花括号:

text 复制代码
请把以下{{language}}内容翻译成英文:{{content}}

传入:

json 复制代码
{
  "language": "中文",
  "content": "你好,世界"
}

得到:

text 复制代码
请把以下中文内容翻译成英文:你好,世界

Dify 还定义了系统特殊变量:

变量 运行时内容
{``{#context#}} 知识库检索结果
{``{#query#}} 用户当前输入
{``{#histories#}} Completion 模式使用的对话历史文本

普通变量要求以字母或下划线开头,只能包含字母、数字和下划线,长度不超过 16 个字符。特殊变量使用带 # 的语法,由对应的渲染流程负责注入。

它不是 Jinja2

虽然也使用 {``{}},但 Dify 的 Prompt 模板不是 Jinja2。它没有条件语句、循环和过滤器:

text 复制代码
没有 {% if %}
没有 {% for %}
没有 {{ value | filter }}

判断上下文是否存在、历史消息放在哪里、查询没有占位符时如何追加,这些逻辑都写在 Python 的 Transform 和 Runner 中。模板负责表达位置,代码负责表达行为。

未匹配变量的处理

format() 默认会把没有匹配到的 {``{var}} 处理成 {var},避免用户输入中的模板语法触发二次渲染。例如,用户输入本身包含 {``{secret_var}} 时,系统不会把它当成下一轮模板继续执行。

内置 Prompt 的多步调用有时会把 remove_template_variables 设为 False,让未替换变量保留到后续步骤。这解释了一个容易混淆的现象:用户 Prompt 偏向一次渲染,平台内置 Prompt 则可能刻意保留中间模板。

四、Simple 与 Advanced:两种不同的控制权

Simple:只写核心指令,系统负责搭框架

Simple 模式中,开发者主要填写 pre_prompt。Dify 会根据模型类型和应用类型读取系统框架模板,再把多个片段拼起来。

常见的组装顺序是:

text 复制代码
知识库上下文(有检索结果时)
        +
用户编写的 pre_prompt
        +
对话历史(有历史时)
        +
当前问题

对于 Chat 模型,系统会把规则和上下文组织成 system 消息,把当前问题作为 user 消息。对于 Completion 模型,系统会把内容拼成一段文本,用 HumanAssistant 前缀区分角色,并使用停止词防止模型越过当前轮次继续生成。

Simple 模式的优点是上手快。开发者不必处理每一种上下文为空、历史为空或查询追加的位置。代价是控制粒度有限:RAG 片段、历史和查询的位置由框架模板决定。

Advanced:自己定义消息结构

Advanced 模式允许开发者按消息列表编排 systemuserassistant 内容。每条消息的文本都可以包含变量。

运行时,一条消息大致会经过以下处理:

  1. 渲染普通业务变量;
  2. 替换 {``{#context#}}
  3. 处理用户消息中的文件或图片内容;
  4. 根据配置插入历史消息;
  5. 把当前 query 注入最后一个 user 消息,或在缺少位置时追加。

Advanced 模式适合需要 few-shot 示例、多轮角色编排、指定上下文位置,或需要让历史信息出现在特定消息之间的场景。它把更多责任交给开发者:占位符放错、角色顺序不合理、上下文重复注入,都会直接影响模型输入。

两种模式怎么选

维度 Simple Advanced
控制粒度 只写核心指令 控制完整消息列表
上下文位置 系统框架决定 开发者指定
对话历史 系统按框架处理 可控制插入位置
Few-shot 示例 不适合复杂示例 适合预置多轮示例
上手成本 较高
适用场景 通用问答、快速搭建 复杂角色、精细编排、实验性 Prompt

一个实用判断是:先用 Simple 验证业务效果;当问题变成"上下文必须放在这里""历史需要插入某个角色之后""必须保留几轮示例"时,再切换到 Advanced。

五、系统内置 Prompt:Dify 的平台能力层

用户写的 Prompt 决定应用如何回答问题;系统内置 Prompt 则帮助 Dify 完成平台功能。prompts.py 中定义了 16 个内置 Prompt 常量,由 LLMGenerator 负责调用。

它们可以按用途分为几组:

类别 代表功能
对话管理 自动生成对话标题、生成回答后的建议问题
代码生成 生成 Python 或 JavaScript Code 节点代码
规则配置生成 根据任务描述生成 Prompt、提取变量、生成欢迎语
结构化输出 生成 JSON Schema,指导模型按 Schema 输出
知识库索引 从文档生成问答对、为段落生成摘要
Prompt 优化 根据上次运行结果修改 Prompt 或 Code,生成修复指令

这些 Prompt 的一个共同特点是:它们不一定由用户在 Prompt IDE 中编辑。部分默认值允许业务配置覆盖,例如建议问题指令和知识库摘要 Prompt;其余内容仍然由平台代码固定控制。

最复杂的例子:规则配置生成

当用户根据任务描述自动生成应用 Prompt 时,Dify 不只发起一次模型调用,而是串起三步:

text 复制代码
任务描述
   ↓
生成带变量的 Prompt 模板
   ↓
从模板中提取变量列表
   ↓
生成聊天机器人的欢迎语
   ↓
prompt + variables + opening_statement

如果是工作流的"无变量"模式,系统只生成 Prompt 模板,跳过变量提取和欢迎语生成。这个设计说明,内置 Prompt 不只是一些散落的提示词,它们也参与平台配置对象的生成。

六、把一次模型请求拆开看

假设开发者在 Simple 模式中写了:

text 复制代码
你是一名专业的产品顾问。请根据知识库内容回答用户问题。

用户输入是:

text 复制代码
如何为新产品设计试用流程?

系统又检索到了一段知识库内容,并且已有历史对话。运行时不会把这几块内容无序地拼在一起,而是按当前模型对应的框架模板组装:

text 复制代码
context_prompt
  知识库检索结果

pre_prompt
  你是一名专业的产品顾问......

histories_prompt
  历史对话

query_prompt
  用户当前问题

Chat 模型最终收到的可能是一组带角色的消息;Completion 模型则更接近一段带前缀的完整文本。两者都来源于同一份应用配置,但最终形态不同。

换成 Advanced 模式后,开发者可以把同样的信息安排成:

text 复制代码
system:你是一名专业的产品顾问。
user:以下是知识库内容......
assistant:我会结合资料回答。
user:{{#query#}}

这里的关键变化不是"Prompt 写得更长",而是消息结构的所有权发生了变化:Simple 把结构交给系统模板,Advanced 把结构交给应用开发者。

七、从源码结构得到的几个实践建议

1. 调试时先判断问题属于哪条链路

应用回答不符合预期时,可以按以下顺序定位:

  • 保存失败:检查 Prompt 配置结构、模式和消息条数;
  • 变量没有替换:检查变量名、请求输入和特殊变量注入位置;
  • 上下文位置不对:检查 Simple 框架模板或 Advanced 消息列表;
  • 历史消息异常:检查模型类型和历史注入逻辑;
  • 平台功能生成结果异常:检查 LLMGenerator 对应的内置 Prompt,而不是只看应用的 pre_prompt

2. 把变量名当成接口契约

变量名连接了前端输入表单、后端解析器和最终 Prompt。建议保持命名稳定,避免用一套变量名承载多种含义。{``{language}} 是语言,{``{content}} 是正文,职责越清楚,调试成本越低。

3. 不要把双花括号当成模板编程语言

Dify 的模板层不负责流程判断。需要条件分支、数据清洗或复杂拼装时,应把逻辑放到工作流节点、代码节点或上游数据处理中,再把结果交给 Prompt。

4. 复杂结构要谨慎切换 Advanced

Advanced 给了更大的控制权,也带来了更多输入错误的可能。切换前先明确三件事:上下文放在哪条消息、历史放在哪个位置、当前 query 没有占位符时如何进入 Prompt。

5. 把 Prompt 当成版本资产

应用发布会生成新的配置记录,Prompt 应与模型、变量表、知识库策略一起做版本管理。只保存一份"最新文本",很难解释一次发布后为什么结果发生变化。

结语

Dify 的 Prompt 管理不是一个文本框,而是一条从配置到运行时消息的转换链:

text 复制代码
编辑配置 → 验证保存 → 版本快照 → 变量注入 → 消息组装 → 模型调用

用户自定义 Prompt 决定应用的业务行为,系统内置 Prompt 支撑 Dify 的平台功能;模板解析器负责替换变量,Prompt Transform 负责决定消息如何成形。

理解这几层之后,Simple 与 Advanced 不再只是两个 UI 选项,{``{#context#}} 也不再像一个神秘占位符。你看到的每一句 Prompt,背后都有明确的来源、生命周期和渲染位置。


源码依据

本文根据 Dify 1.14.2 目录 07-others/prompt-management 下的 5 篇源码分析稿整理:

  • 00-overview.md
  • 01-builtin-prompts.md
  • 02-user-prompt-lifecycle.md
  • 03-template-engine.md
  • 04-prompt-transform.md

关键源码入口包括:api/core/llm_generator/prompts.pyapi/core/llm_generator/llm_generator.pyapi/core/prompt/utils/prompt_template_parser.pyapi/core/prompt/simple_prompt_transform.pyapi/core/prompt/advanced_prompt_transform.py


想继续深入 Dify 1.14.2?

私信获取详情。

相关推荐
“AI国潮设计-小江”4 小时前
【Python实战】SDXL精准控制“普宁英歌舞×星空蛋糕”IP落地,附核心Prompt与商用授权思路
开发语言·人工智能·python·prompt·aigc
科技苑5 小时前
日常用的 prompt词汇集指南
人工智能·prompt
“AI国潮设计-小江”21 小时前
《Python实战 | SDXL大模型批量生成“英歌舞海浪”蛋糕IP,附核心Prompt控制代码与IP授权变现思路》
人工智能·python·prompt·aigc
liulilittle1 天前
为什么需要回程闲置保护?
ai·llm·prompt·agent·tools·subagent·opencode
智码看视界1 天前
Day68-结构化Prompt设计:XML标签法/Markdown法/JSON Schema
prompt·markdown·json schema·spring ai·结构化prompt·xml标签
LayZhangStrive1 天前
Prompt - 如何生成贴合我们业务需求的有效prompt
ai·prompt·agent·提示词
小白羊丨1 天前
为什么微调而不是大模型 + Prompt?正确率如何得到,是否过拟合?
大数据·人工智能·prompt
hasty2 天前
从 Prompt Injection 到越界写文件:Theia Agent Mode 的信任边界为何失效
安全·prompt
“AI国潮设计-小江”2 天前
Python实战 | SDXL精准控制“普宁英歌舞×星空蛋糕”IP落地,附核心Prompt与商用授权思路
开发语言·人工智能·python·prompt·aigc