Dify Agent Prompt Layer 详解:如何为一次 Agent Run 组装系统提示词与用户输入
【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify
Dify Agent 采用“层(Layer)”架构组织每次运行(run)的能力单元,其中 Prompt Layer(type id 为plain.prompt)负责为当前 run 提供系统提示词片段(prefix/suffix)与用户输入片段(user)。本文以 Prompt Layer 用户手册 为核心,结合 PromptLayer 源码实现、运行器校验逻辑 和 compositor 聚合逻辑,讲清楚它的配置字段、组装时序、空 prompt 拒绝规则以及它与 history layer 的协作边界,帮助你在构建 create-run 请求时正确编排 prompt 片段。
Prompt Layer 的定位与适用场景
在 Dify Agent 的请求体中,Prompt Layer 是一个普通的RunLayerSpec,type id 为plain.prompt。它承载三类内容:
- 本次 run 应发送的系统指令(
prefix,以及可选的suffix); - 当前用户输入(
user)。
这是 run API 提交用户输入的唯一入口——API 不接受顶层user_prompt字段。这一点可以从运行器代码得到印证:runner.py 在进入 run 后直接读取run.user_prompts(由所有层聚合而来),并以此作为 pydantic-ai 的 run 输入,请求体中并不存在独立的用户输入字段。
Prompt Layer 与其他层(如模型层、工具层)平级参与构图,源码注释说明了它的定位:它是构建在agenton.layers.types之上的小型具体实现,刻意保持与 compositor 图构建无关,以便在配置、示例和更高层的动态层中复用(见 basic.py)。
配置字段说明
PromptLayerConfig的完整字段定义在 basic.py:
| 字段 | 类型 | 含义 |
|---|---|---|
prefix | str或list[str] | 收集在其他 prompt 内容之前的系统提示词片段。 |
user | str或list[str] | 当前 run 的用户消息片段。 |
suffix | str或list[str] | 收集在 prefix 内容之后的系统提示词片段。 |
三个字段默认值均为空列表。另外需要注意两个源码层面的约束:
- 配置类使用了
model_config = ConfigDict(extra="forbid"),即请求中传入未知字段会被 Pydantic 校验直接拒绝,这是一个常见的报错来源; - 字符串与列表两种写法都合法。
PromptLayer提供了prefix_prompts/suffix_prompts/user_prompts三个属性,将字符串归一化为单元素列表后再参与聚合(见 basic.py),因此调用方无论传str还是list[str],运行时的处理路径是一致的。
测试用例 test_basic.py 也验证了PLAIN_PROMPT_LAYER_TYPE_ID == "plain.prompt"以及PromptLayer.type_id与该常量一致,确认了 type id 的稳定性。
系统提示词的组装顺序
当图中存在多个会贡献 prompt 的层时,聚合顺序由 compositor 决定。从 run.py 的prompts属性 可以看到:
- 按 slot 顺序收集所有层的prefix片段;
- 以倒序收集所有层的suffix片段;
- 每个片段经过所在层的
wrap_prompt包装(允许层对片段做二次加工),最后统一交给prompt_transformer处理。
这意味着prefix位于系统提示词的前部,suffix位于尾部;倒序收集 suffix 的设计使得后声明的层的 suffix 更靠近 prefix 内容、先声明的层的 suffix 排在最末尾。如果你的 run 中只有 prompt layer 一个系统提示词来源,这套细节不会产生影响;只有在多层同时贡献系统片段(例如 Dify 内置的知识检索层也会贡献user_prompts,见 knowledge 层实现)时才需要关注顺序。
基本用法
手册给出的最小示例:用一个RunLayerSpec声明名为prompt的层,config 中分别传入一条系统指令和一条用户输入:
from agenton_collections.layers.plain import PLAIN_PROMPT_LAYER_TYPE_ID, PromptLayerConfig from dify_agent.protocol import RunLayerSpec prompt_layer = RunLayerSpec( name="prompt", type=PLAIN_PROMPT_LAYER_TYPE_ID, config=PromptLayerConfig( prefix="You are a concise assistant.", user="Summarize the incident in one paragraph.", ), )这里name="prompt"是约定俗成的名字而非保留字:运行时不对层名做保留,但官方示例(如 run_server_sync_client.py)与调度器测试(test_run_scheduler.py)都使用prompt作为该层的名称,建议保持一致以便他人阅读。type则必须是PLAIN_PROMPT_LAYER_TYPE_ID(即字符串"plain.prompt"),运行时按 type id 反查层工厂来实例化,写错 type 会导致构图失败。
多片段写法:列表形式的 prefix / user / suffix
当调用方希望把若干提示词片段保持独立(而仍然只发送一个 run)时,使用列表:
prompt_layer = RunLayerSpec( name="prompt", type=PLAIN_PROMPT_LAYER_TYPE_ID, config=PromptLayerConfig( prefix=[ "You are an incident response assistant.", "Prefer concrete mitigation steps.", ], user=[ "Database latency is elevated.", "Return the likely severity and next actions.", ], suffix="Do not invent metrics that are not provided.", ), )列表写法在多片段场景下的实际收益体现在两处:
- 系统侧:
prefix列表中的每个片段会按顺序拼接进系统提示词流,suffix同理放在尾部。像“角色设定 + 回答风格偏好”这类天然分段的指令,用列表比手动用换行拼接更清晰,也便于后续按来源增删片段。 - 用户侧:
user列表中的多个片段最终会一起作为 run 输入。从 agent_factory.py 的normalize_user_input可以看到运行时的归一化规则:只有当聚合结果恰好是“单个字符串”时才作为str传入 pydantic-ai,否则整体以列表形式传入,从而保留多部分(multi-part)提示词的语义。
空用户输入的拒绝机制
手册指出:当有效用户 prompt 为空或仅含空白字符时,Dify Agent 会拒绝 create-run 请求。这条规则的实现在 user_prompt_validation.py:
EMPTY_USER_PROMPTS_ERROR = "run.user_prompts must not be empty" def has_non_blank_user_prompt(user_prompts: Sequence[UserContent]) -> bool: for prompt in user_prompts: if isinstance(prompt, str): if prompt.strip(): return True else: return True return False关键细节有三点:
- 校验发生在 run 入口内部,即 compositor 构建完成、层实例化并经过 transformer 转换之后。源码注释明确说明这样做是为了“让运行时执行使用与实际 pydantic-ai 输入相同的转换后 prompt”,避免校验的是原始配置、执行的是转换后内容的偏差;
- 字符串片段按
strip()判定:""和" "都不算有效输入;非字符串片段(富媒体/消息部件)直接视为有效内容,因为富内容没有统一的空白表示; - 校验失败时,runner.py 抛出
AgentRunValidationError("run.user_prompts must not be empty")。注意存在一个豁免条件:如果本次 run 携带的是延迟工具结果(deferred tool results),则跳过该校验——因为工具结果回传场景下用户 prompt 本来就可以为空。
Prompt 如何进入模型:run 级 instructions 与记忆隔离
理解了 prompt 的“入口”,还要知道它的“出口”。运行器在调用agent.run时做了明确的分工(见 runner.py):
- 用户输入:
normalize_user_input(user_prompts)作为 run 的输入传入; - 系统提示词:
instructions=run.prompts or None,即聚合后的 prefix/suffix 系统片段,以pydantic-ai 的 run 级 instructions形式传入,而不是被硬编码进模型层的系统提示。
agent_factory的模块 docstring 也点明了这一设计:“The runner passes Dify system prompts as run-level instructions”。这种“系统提示按 run 注入”的方式带来一个重要的行为边界:
当前 run 的系统提示词是瞬态(transient)的,不会写入会话记忆。
这与手册 Notes 中最后一条对应:当 history layer 存在时,当前系统 prompt 以 run 级 instructions 传入,不会被保存到 memory。history.py 的 docstring 明确写道:“Current system instructions belong to each run and are never (persisted)”,其持久化实现replace(message, instructions=None)在落盘前会把消息上的瞬态 instructions 清除。因此:
- 你想在每轮对话中稳定生效的规则,写进
prefix/suffix即可,不用担心污染历史; - 但反过来说,历史轮次中曾用过的系统指令也不会自动出现在当前 run 的上下文中——每轮的 prefix/suffix 只来自当前请求的层配置。
实践建议与注意事项
- 用户输入一律走 prompt layer。不要假设存在
user_prompt顶层字段;只填了prefix没填user的请求(且无延迟工具结果)会以run.user_prompts must not be empty失败。 - 层名用
prompt,type 用plain.prompt。名字不保留但建议遵守约定;type id 是反查工厂的依据,必须精确。 - 多来源系统指令时注意 suffix 倒序聚合:多个层同时提供 suffix 时,先声明的层其 suffix 排在最后,可按此设计指令的优先级。
- 配合 history layer 使用时,系统指令按 run 生效、不落盘;跨轮上下文只通过
history层持久化的消息历史传递。 - 配置字段是封闭的:
extra="forbid"意味着拼写错误(如sys_prompt)会直接触发校验失败,而不是被静默忽略。
参考实现与测试
| 内容 | 路径 |
|---|---|
| 用户手册(本文主体) | dify-agent/docs/dify-agent/user-manual/prompt-layer/index.md |
PromptLayerConfig/PromptLayer实现 | dify-agent/src/agenton_collections/layers/plain/basic.py |
| 空 prompt 校验 | dify-agent/src/dify_agent/runtime/user_prompt_validation.py |
| run 执行与校验触发点 | dify-agent/src/dify_agent/runtime/runner.py |
| 系统/用户 prompt 聚合 | dify-agent/src/agenton/compositor/run.py |
| 记忆持久化清除瞬态指令 | dify-agent/src/dify_agent/runtime/history.py |
| 层 type id 单测 | dify-agent/tests/local/agenton_collections/layers/plain/test_basic.py |
| 协议层 schema 测试 | dify-agent/tests/local/dify_agent/protocol/test_protocol_schemas.py |
| 调度器中 prompt layer 用例 | dify-agent/tests/local/dify_agent/runtime/test_run_scheduler.py |
| 客户端示例 | dify-agent/examples/dify_agent/dify_agent_examples/run_server_sync_client.py |
综上,Prompt Layer 是 Dify Agent 中“提示词即配置”的核心载体:prefix/suffix系统片段按 compositor 的顺序规则聚合为 run 级 instructions,user片段构成经过空值校验的 run 输入,且系统指令与记忆持久化严格隔离。掌握这三条链路,就能在任何包含 Dify Agent 模型层、history layer 等组件的组合中正确编排提示词。
【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考