Guardrails(护栏)是一种机制,用于验证 LLM 的输入和输出,确保其符合您的预期。可以使用护栏来实现以下目的(或其他功能):
- 过滤不当内容
- 验证输出格式
- 限制敏感信息泄露
- 确保回答符合业务规则
实验性功能:Guardrails 是一项实验性功能。其 API 和行为可能在未来版本中发生变化。
护栏仅在使用 AI Services 时可用。 它们是高层抽象,不能直接应用于ChatModel或StreamingChatModel。

设计原则
理想情况下,护栏的实现应遵循单一职责原则(Single Responsibility Principle),即每个护栏类只应验证一件事。然后,将多个护栏串联起来,以防范多种问题。
护栏链中的顺序
护栏在链中的顺序非常重要。链中第一个失败的护栏将触发整体失败。请确保:
- 捕获最多失败的护栏放在链的前面
- 更具体、失败频率较低的护栏放在链的后面
成本考量
请记住,护栏本身可能会调用其他服务,甚至触发其他 LLM 交互。如果这类护栏有执行开销或金钱成本,请务必将其纳入考虑。您可能希望将成本较高的护栏放在链的后面。
这里的”昂贵”可以指执行时间较长,也可以指有金钱成本。
输入护栏(Input Guardrails)
输入护栏是在调用 LLM 之前执行的函数。输入护栏失败将阻止 LLM 被调用。输入护栏是调用 LLM 前的最后一步,它们在所有 RAG 操作完成之后才被调用。
实现方式
输入护栏通过实现 InputGuardrail 接口来创建。该接口提供了两个 validate 方法变体,至少需要实现其中一个:
1 | // 变体 1:仅访问 UserMessage(适用于简单护栏) |
1 | // 变体 2:访问更完整的上下文信息 |
- 第一个变体用于简单护栏,或护栏只需要访问
UserMessage的场景。 - 第二个变体用于更复杂的护栏,需要更多信息,例如聊天记忆/历史、用户消息模板、增强结果或传递给模板的变量。详见
InputGuardrailRequest。
输入护栏可以做的事情(示例)
- 检查用户输入是否包含敏感词或不当内容
- 验证用户是否提供了所有必需的参数
- 检测用户意图是否偏离了允许的范围
- 对用户输入进行格式校验
输入护栏的结果
输入护栏可以返回以下结果,可通过 InputGuardrail 接口上的辅助方法来生成:
| 结果 | 说明 |
|---|---|
| 成功(success) | 验证通过,继续执行链中的下一个护栏或调用 LLM |
| 失败(failure) | 验证失败,返回失败原因,不调用 LLM |
声明输入护栏的方式(按优先级排序)
以下是声明输入护栏的几种方式,按优先级从高到低排列:
- 直接在
AiServicesbuilder 上设置InputGuardrail实现类名或实例(优先级最高) - 通过注解方式声明
方式一:在 Builder 上设置
1 | // 传递 InputGuardrail 实现类,将通过反射动态创建实例 |
1 | // 直接传递 InputGuardrail 实例 |
类的实例化方式可以自定义。例如,使用依赖注入的框架(如 Quarkus 或 Spring)可以利用扩展点来提供实例,而不是每次都通过反射创建新实例。
方式二:通过注解声明
1 | // 仅 chat 方法有护栏 |
1 | // chat 和 doSomethingElse 方法都有护栏 |
单元测试
langchain4j-test 模块中提供了一些基于 AssertJ 的单元测试工具。
添加依赖后,您可以执行以下验证:
1 | // 验证输入护栏是否成功 |
输出护栏(Output Guardrails)
输出护栏是在 LLM 生成输出之后执行的函数。输出护栏失败允许更高级的场景,例如重试(retrying)或重新提示(reprompting),以帮助改进响应。它们在所有其他操作(包括函数/工具调用)完成之后才被调用。
实现方式
与输入护栏类似,输出护栏通过实现 OutputGuardrail 接口来创建。该接口提供了两个 validate 方法变体,至少需要实现其中一个:
1 | // 变体 1:仅访问生成的 AiMessage(适用于简单护栏) |
1 | // 变体 2:访问更完整的上下文信息 |
- 第一个变体用于简单护栏,或护栏只需要访问生成的
AiMessage的场景。 - 第二个变体用于更复杂的护栏,需要更多信息,例如完整的聊天响应、聊天记忆/历史、用户消息模板或传递给模板的变量。详见
OutputGuardrailRequest。
输出护栏可以做的事情(示例)
- 验证输出是否符合预期的格式(如 JSON Schema)
- 检查输出是否包含敏感信息
- 评估输出质量是否达标,不达标则触发重试
- 对输出进行内容安全审核
输出护栏的结果
输出护栏可以返回以下结果,可通过 OutputGuardrail 接口上的辅助方法来生成:
| 结果 | 说明 |
|---|---|
| 成功(success) | 验证通过,将结果返回给用户 |
| 失败 - 重试(failure with retry) | 验证失败,重新调用 LLM 尝试生成更好的响应 |
| 失败 - 重新提示(failure with reprompt) | 验证失败,使用修改后的提示重新调用 LLM |
声明输出护栏的方式(按优先级排序)
以下是声明输出护栏的几种方式,按优先级从高到低排列:
- 直接在
AiServicesbuilder 上设置OutputGuardrail实现类名或实例(优先级最高) - 通过注解方式声明
方式一:在 Builder 上设置
1 | // 传递 OutputGuardrail 实现类,将通过反射动态创建实例 |
1 | // 直接传递 OutputGuardrail 实例 |
无论以何种方式声明,输出护栏始终按照它们在列表中的出现顺序执行。
类的实例化方式可以自定义。例如,使用依赖注入的框架(如 Quarkus 或 Spring)可以利用扩展点来提供实例,而不是每次都通过反射创建新实例。
方式二:通过注解声明
1 | // 仅 chat 方法有护栏 |
1 | // chat 和 doSomethingElse 方法都有护栏 |
额外配置
输出护栏还支持以下额外配置:
- maxRetries:最大重试次数,当护栏失败并选择重试时,最多重试的次数。
流式响应中的输出护栏
输出护栏也可以在流式响应操作中使用:
1 | TokenStream tokenStream = assistant.chat(userMessage); |
在这种场景下,输出护栏将在整个流完成之后才执行,更具体地说,是在 TokenStream.onCompleteResponse 被调用时执行。onPartialResponse 将被缓冲,并在护栏验证成功后重放。
如果链中的重试(retry)或重新提示(reprompt)最终成功,则整个链将同步重新执行。每个护栏将按照原始顺序逐一重新执行。链完成后,结果将传递给 TokenStream.onCompleteResponse。
LangChain4j 内置的输出护栏实现
LangChain4j 为一些常见用例提供了开箱即用的输出护栏实现:
- 输出格式验证护栏
- 内容安全审核护栏
- 自定义业务规则验证护栏
单元测试
langchain4j-test 模块中提供了一些基于 AssertJ 的单元测试工具。
添加依赖后,您可以执行以下验证:
1 | // 验证输出护栏是否成功 |
混合使用输入和输出护栏
您可以按照自己的需求自由组合输入和输出护栏!
1 | // 在 Builder 上设置全局输入护栏和输出护栏配置 |
在上述示例中:
Assistant上的所有方法都有同一个输入护栏AnotherInputGuardrail(因为在AiServicesbuilder 上设置了)。- 此外,所有输出护栏的
maxRetries值都为10(因为配置也在 builder 上设置了)。
如果某个方法上通过注解额外声明了输出护栏:
1 | // chat 方法有输出护栏 SomeOutputGuardrail,maxRetries = 10 |
扩展点
护栏系统以可组合的方式构建,因此可以在其他下游框架(如 Quarkus 或 Spring Boot)中扩展和复用。本节介绍提供的一些扩展点:
- 自定义实例创建:框架可以通过扩展点自定义
InputGuardrail/OutputGuardrail实现类的实例化方式(例如使用依赖注入容器)。 - 自定义护栏链行为:可以扩展护栏的执行逻辑,满足特定框架的集成需求。
- 与框架生命周期集成:可以将护栏与框架自身的配置、监控、日志等机制深度集成。
总结
| 特性 | 输入护栏(Input Guardrail) | 输出护栏(Output Guardrail) |
|---|---|---|
| 执行时机 | 调用 LLM 之前 | LLM 生成输出之后 |
| 失败后果 | 阻止 LLM 调用 | 可触发重试或重新提示 |
| 实现接口 | InputGuardrail |
OutputGuardrail |
| 适用场景 | 输入校验、内容过滤、参数验证 | 输出校验、格式验证、内容审核 |
| 流式支持 | ✅ | ✅(流完成后执行) |
| 声明方式 | Builder / 注解 | Builder / 注解 |