Guardrails(护栏机制)
Guardrails 是一种机制,可以让你验证 LLM(大语言模型)的输入和输出,确保其符合预期。通过 Guardrails,你可以完成以下操作:
- 验证用户输入是否不在允许范围内
- 确保输入在调用 LLM 前满足特定条件(例如防御 提示注入攻击)
- 确保输出格式正确(例如是符合正确模式的 JSON 文档)
- 确保 LLM 输出符合业务规则和约束(例如,如果这是 X 公司的聊天机器人,回答中不能包含对竞争对手 Y 的引用)
- 检测幻觉(hallucinations)
以上只是示例,你可以用 Guardrails 做更多的事情。

该功能最初在 Quarkus LangChain4j 扩展中实现,并被回移到这里。
实现 Guardrails
理想情况下,Guardrail 的实现应遵循单一职责原则,即每个 Guardrail 类只验证一件事情。然后将多个 Guardrail 串联起来,以防护多个方面。
Guardrail 链中的顺序很重要。第一个失败的 Guardrail 会触发整体失败。应确保最容易捕获错误的 Guardrail 排在链的前面,而那些仅在极少情况下才会失败的 Guardrail 放在链的后面。
另外请记住,Guardrail 本身可以调用其他服务,甚至触发其他 LLM 交互。如果这些 Guardrail 执行有延迟或会带来额外的成本,请务必考虑这个因素。对于更“昂贵”的 Guardrail,可以将其放在链的末尾。
输入 Guardrails
输入 Guardrails 是在调用 LLM 之前执行的函数。如果输入 Guardrail 失败,将阻止 LLM 被调用。输入 Guardrails 是调用 LLM 之前的最后一步,且在任何 RAG 操作完成后执行。
实现输入 Guardrails
实现输入 Guardrails 需要实现 InputGuardrail
接口。该接口提供两种 validate
方法的变体,必须至少实现其中一种:
InputGuardrailResult validate(UserMessage userMessage);
InputGuardrailResult validate(InputGuardrailRequest params);
第一种适用于简单的 Guardrail,或只需要访问 UserMessage
的场景。
第二种适用于需要更多信息的复杂 Guardrail,例如聊天记录、用户消息模板、增强结果或传递给模板的变量。详细信息请参见 InputGuardrailRequest
。
一些可以做的示例:
- 检查增强结果中是否有足够的文档
- 确保用户不是重复提问
- 缓解潜在的提示注入攻击
输入 Guardrails 无论是同步操作还是异步/流式操作都可以使用。
输入 Guardrail 的结果
输入 Guardrail 的结果类型如下,并且 InputGuardrail
接口中提供了对应的辅助方法:
结果 | InputGuardrail 接口的辅助方法 | 描述 |
---|---|---|
success | success() | - 输入有效 - 执行链中的下一个 Guardrail - 如果最后一个 Guardrail 通过,则调用 LLM |
success with alternate result | successWith(String) | 类似 success,但用户消息会在继续下一步(下一个 Guardrail 或 LLM 调用)前被修改 |
failure | failure(String) 或 failure(String, Throwable) | - 输入无效,但仍继续执行链中的其他 Guardrails 以收集所有验证问题 - LLM 不会被调用 |
fatal | fatal(String) 或 fatal(String, Throwable) | - 输入无效,立即停止执行并抛出 InputGuardrailException - LLM 不会被调用 |
声明输入 Guardrails
声明输入 Guardrails 的方式有以下几种,按优先级排序:
- 在
AiServices
构建器上直接设置InputGuardrail
实现类或实例 - 在单个 AI Service 方法上使用
@InputGuardrails
注解 - 在 AI Service 类上使用
@InputGuardrails
注解
无论如何声明,输入 Guardrails 都会按列表顺序依次执行。
使用 AiServices
构建器
在 AiServices
构建器中直接设置的 Guardrails 拥有最高优先级。如果在其他位置也声明了 Guardrails,将以构建器上的配置为准。
public interface Assistant {
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.inputGuardrailClasses(FirstInputGuardrail.class, SecondInputGuardrail.class)
.build();
或者
public interface Assistant {
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.inputGuardrails(new FirstInputGuardrail(), new SecondInputGuardrail())
.build();
第一种方式是传递实现 InputGuardrail
的类,框架会通过反射动态创建这些类的实例。
在单个 AI Service 方法上使用注解
@InputGuardrails
注解可以放在单个 AI Service 方法上,优先级次于构建器配置。
public interface Assistant {
@InputGuardrails({ FirstInputGuardrail.class, SecondInputGuardrail.class })
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.create(Assistant.class, chatModel);
在此示例中,只有 chat
方法有 Guardrails:
FirstInputGuardrail
会先执行- 只有它成功,LLM 才会被调用
- 如果
FirstInputGuardrail
没有返回 fatal,SecondInputGuardrail
才会执行 - 两个 Guardrail 都可以重写用户消息
- 如果第一个 Guardrail 重写了用户消息,第二个 Guardrail 将收到修改后的消息
doSomethingElse
方法没有 Guardrails。
在 AI Service 类上使用注解
如果在 AI Service 类上添加 @InputGuardrails
注解,优先级最低。
@InputGuardrails({ FirstInputGuardrail.class, SecondInputGuardrail.class })
public interface Assistant {
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.create(Assistant.class, chatModel);
在此示例中,chat
和 doSomethingElse
方法都会应用 Guardrails:
FirstInputGuardrail
先执行- 只有它成功,才会调用 LLM
SecondInputGuardrail
仅在第一个没有返回 fatal 时执行- 两个 Guardrail 都可能重写用户消息
- 如果第一个 Guardrail 修改了消息,第二个会接收修改后的消息
输入 Guardrails 的单元测试
langchain4j-test
模块中提供了一些基于 AssertJ 的单元测试工具。
Maven
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-test</artifactId>
<scope>test</scope>
</dependency>
Gradle (Groovy)
testImplementation 'dev.langchain4j:langchain4j-test'
Gradle (Kotlin)
testImplementation("dev.langchain4j:langchain4j-test")
添加依赖后,可以进行如下验证:
import static dev.langchain4j.test.guardrail.GuardrailAssertions.assertThat;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.guardrail.GuardrailResult.Result;
class Tests {
MyInputGuardrail inputGuardrail = new MyInputGuardrail();
@Test
void test() {
var userMessage = UserMessage.from("Some user message");
var result = inputGuardrail.validate(userMessage);
// 以下是一些示例断言
assertThat(result)
.isSuccessful()
.hasResult(Result.FATAL)
.hasFailures()
.hasSingleFailureWithMessage("Prompt injection detected")
.assertSingleFailureSatisfied(failure -> assertThat(failure)...)
.withFailures().....
}
}
输出 Guardrails
输出 Guardrails 是在 LLM 生成结果后执行的函数。如果输出 Guardrail 失败,可以支持更高级的操作,例如 重试 或 重新提示,以改善响应。它们在所有其他操作(包括函数/工具调用)之后执行。
实现输出 Guardrails
实现输出 Guardrails 需要实现 OutputGuardrail
接口。该接口提供两种 validate
方法,必须至少实现其中一种:
OutputGuardrailResult validate(AiMessage responseFromLLM);
OutputGuardrailResult validate(OutputGuardrailRequest params);
第一种适用于简单的 Guardrail,或只需访问生成的 AiMessage
。
第二种适用于需要更多上下文信息的复杂 Guardrail,例如完整的聊天响应、聊天记录、用户消息模板或传递给模板的变量。详细信息请参见 OutputGuardrailRequest
。
可以做的示例:
- 验证输出格式是否正确(如符合指定 JSON 模式)
- 检测 LLM 幻觉
- 验证 LLM 响应中包含必要信息
输出 Guardrail 的结果
输出 Guardrail 的结果类型如下,OutputGuardrail
接口中提供了对应的辅助方法:
结果 | OutputGuardrail 接口的辅助方法 | 描述 |
---|---|---|
success | success() | - 输出有效 - 执行链中的下一个 Guardrail,如果最后一个通过,则将输出返回给调用方 |
success with rewrite | successWith(String) 或 successWith(String, Object) | - 类似 success,但输出在原始形式下无效,需要被重写后再进行下一步 - 下一个 Guardrail 将基于重写后的输出执行。如果最后一个 Guardrail 通过,则返回修改后的输出 |
failure | failure(String) 或 failure(String, Throwable) | - 输出无效,但继续执行链中的其他 Guardrail 以收集所有问题 - 最终会抛出 OutputGuardrailException |
fatal | fatal(String) 或 fatal(String, Throwable) | - 输出无效,立即停止执行并抛出 OutputGuardrailException |
fatal with retry | retry(String) 或 retry(String, Throwable) | - 类似 fatal,但会用相同的提示和聊天历史重新调用 LLM - 如果在可配置的重试次数后仍失败,则抛出 OutputGuardrailException - 如果重试后通过,Guardrail 链会从头重新执行 |
fatal with reprompt | reprompt(String, String) 或 reprompt(String, Throwable, String) | - 类似 fatal with retry,但会用 Guardrail 提供的新提示重新调用 LLM - 新请求将附加额外的消息并保持原始聊天历史 - 如果在可配置的重试次数后仍失败,则抛出 OutputGuardrailException - 如果通过,Guardrail 链会从头重新执行 |
声明输出 Guardrails
声明方式与输入 Guardrails 类似,优先级顺序如下:
- 在
AiServices
构建器上直接设置OutputGuardrail
实现类或实例 - 在单个 AI Service 方法上使用
@OutputGuardrails
注解 - 在 AI Service 类上使用
@OutputGuardrails
注解
无论如何声明,输出 Guardrails 都会按列表顺序依次执行。
使用 AiServices
构建器
在 AiServices
构建器中直接设置的 Guardrails 优先级最高。
public interface Assistant {
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.outputGuardrailClasses(FirstOutputGuardrail.class, SecondOutputGuardrail.class)
.build();
或者
public interface Assistant {
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.outputGuardrails(new FirstOutputGuardrail(), new SecondOutputGuardrail())
.build();
第一种方式是传递实现 OutputGuardrail
的类,框架会通过反射动态创建实例。
对单个 AI Service 方法的注解
在单个 AI Service 方法上使用的 @OutputGuardrails
注解 具有次高优先级。
public interface Assistant {
@OutputGuardrails({ FirstOutputGuardrail.class, SecondOutputGuardrail.class })
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.create(Assistant.class, chatModel);
在这个示例中,只有 chat
方法应用了输出护栏(guardrails)。
- 对于
chat
方法,FirstOutputGuardrail
首先执行。 - 仅当其验证成功时,结果才会返回给调用方。如果
FirstOutputGuardrail
的结果不是 fatal、fatal with retry 或 fatal with reprompt,才会执行SecondOutputGuardrail
。 SecondOutputGuardrail
将接收FirstOutputGuardrail
的输出。- 如果
SecondOutputGuardrail
在重试或重新提示后成功,则FirstOutputGuardrail
和SecondOutputGuardrail
都会重新执行。
doSomethingElse
方法没有任何输出护栏。
注解在 AI Service 类上
在 AI Service 类上使用 @OutputGuardrails
注解 具有最低优先级。
@OutputGuardrails({ FirstOutputGuardrail.class, SecondOutputGuardrail.class })
public interface Assistant {
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.create(Assistant.class, chatModel);
在这个示例中,chat
和 doSomethingElse
方法都应用了输出护栏。
- 与上一个示例类似,
FirstOutputGuardrail
首先执行。 - 只有在验证成功后,结果才返回给调用方。如果
FirstOutputGuardrail
结果不是 fatal、fatal with retry 或 fatal with reprompt,才会执行SecondOutputGuardrail
。 SecondOutputGuardrail
将接收FirstOutputGuardrail
的输出。- 如果
SecondOutputGuardrail
在重试或重新提示后成功,则两个护栏都会重新执行。
配置
输出护栏可以提供以下附加配置:
配置项 | 描述 |
---|---|
maxRetries | - 输出护栏在执行重试或重新提示时的最大重试次数。 - 默认值为 2 。- 设置为 0 可禁用重试。 |
方法级别注解
public interface MethodLevelAssistant {
@OutputGuardrails(
value = { FirstOutputGuardrail.class, SecondOutputGuardrail.class },
maxRetries = 10
)
String chat(String question);
}
var assistant = AiServices.create(MethodLevelAssistant.class, chatModel);
类级别注解
@OutputGuardrails(
value = { FirstOutputGuardrail.class, SecondOutputGuardrail.class },
maxRetries = 10
)
public interface ClassLevelAssistant {
String chat(String question);
}
var assistant = AiServices.create(ClassLevelAssistant.class, chatModel);
AiServices
构建器
public interface Assistant {
String chat(String message);
}
var outputGuardrailsConfig = OutputGuardrailsConfig.builder()
.maxRetries(10)
.build();
var assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.outputGuardrailsConfig(outputGuardrailsConfig)
.outputGuardrailClasss(FirstOutputGuardrail.class, SecondOutputGuardrail.class)
.build();
流式响应中的输出护栏
输出护栏也可用于具有流式响应的操作:
public interface StreamingAssistant {
@OutputGuardrails({ FirstOutputGuardrail.class, SecondOutputGuardrail.class })
TokenStream streamingChat(String message);
}
在这种情况下,输出护栏会在整个流完成后执行,更准确地说是在调用 TokenStream.onCompleteResponse
时。onPartialResponse
会被缓冲,并在护栏验证成功后重放。
如果在链中的 retry 或 reprompt 最终成功,则整个链会 同步 重新执行。每个护栏会按原始顺序逐一重新执行。完成后,结果会传递给 TokenStream.onCompleteResponse
。
内置的输出护栏
LangChain4j 提供了一些常见用例的输出护栏实现:
护栏类 | 描述 |
---|---|
JsonExtractorOutputGuardrail | 一个输出护栏,用于检查响应是否能成功从 JSON 反序列化为特定类型对象。 - 使用 Jackson ObjectMapper 尝试反序列化对象。 - 如果响应无法反序列化为预期对象类型,则会重新提示 LLM。 - 可直接使用,也可通过扩展和自定义(有多个 protected 方法可重写以定制行为)。 |
输出护栏的单元测试
langchain4j-test
模块提供了基于 AssertJ 的一些单元测试工具。
Maven
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-test</artifactId>
<scope>test</scope>
</dependency>
Gradle (Groovy)
testImplementation 'dev.langchain4j:langchain4j-test'
Gradle (Kotlin)
testImplementation("dev.langchain4j:langchain4j-test")
添加依赖后,可以执行如下验证:
import static dev.langchain4j.test.guardrail.GuardrailAssertions.assertThat;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.guardrail.GuardrailResult.Result;
class Tests {
MyOutputGuardrail outputGuardrail = new MyOutputGuardrail();
@Test
void test() {
var aiMessage = AiMessage.from("Some output");
var result = outputGuardrail.validate(aiMessage);
// 以下是一些示例
assertThat(result)
.isSuccessful()
.hasResult(Result.FATAL)
.hasFailures()
.hasSingleFailureWithMessage("Hallucination detected!")
.hasSingleFailureWithMessageAndReprompt("Hallucination detected!", "Please LLM don't hallucinate!")
.assertSingleFailureSatisfied(failure -> assertThat(failure)...)
.withFailures().....
}
}
混合使用
你可以随意混合使用输入护栏和输出护栏!
public class MyObjectJsonOutputGuardrail extends JsonExtractorOutputGuardrail<MyObject> {
public MyObjectJsonOutputGuardrail() {
super(MyObject.class);
}
}
@InputGuardrails({ FirstInputGuardrail.class, SecondInputGuardrail.class })
@OutputGuardrails(value = SomeOutputGuardrail.class, maxRetries = 5)
public interface Assistant {
String chat(String message);
@InputGuardrails(PromptInjectionGuardrail.class)
@OutputGuardrails(MyObjectJsonOutputGuardrail.class)
MyObject chatAndReturnJson(String message);
}
var outputGuardrailsConfig = OutputGuardrailsConfig.builder()
.maxRetries(10)
.build();
var assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.inputGuardrails(new AnotherInputGuardrail())
.outputGuardrailsConfig(outputGuardrailsConfig)
.build();
在此示例中:
- 所有
Assistant
方法都有一个输入护栏AnotherInputGuardrail
,因为它在AiServices
构建器上设置。 - 所有输出护栏的
maxRetries
都是10
,因为该配置也在构建器中设置。
chat
方法有一个输出护栏 SomeOutputGuardrail
,maxRetries
为 10
。
chatAndReturnJson
方法有一个输出护栏 MyObjectJsonOutputGuardrail
,maxRetries
也为 10
。
扩展点
护栏系统采用可组合的方式构建,因此可以在其他下游框架(如 Quarkus 或 Spring Boot)中扩展和复用。本节描述了一些提供的扩展点或“钩子”。
所有这些扩展点都使用 Java Service Provider Interface (Java SPI)。
扩展点接口 | 作用 |
---|---|
ClassInstanceFactory | 提供类的实例。 - 用于委托实例创建/获取的方式。 - 如果未提供,则使用反射通过默认构造函数创建实例。 - 其他框架(如 Quarkus 或 Spring)可以使用其自己的 Bean 容器提供类实例。这些框架会提供一个实现。 - Quarkus 实现示例:CDIClassInstanceFactory - Spring 实现示例:ApplicationContextClassInstanceFactory |
ClassMetadataProviderFactory | 提供访问类元数据的能力。 - 用于扫描 AiService 接口的方法,并查找和处理 @InputGuardrails /@OutputGuardrails 注解。- 默认实现是 ReflectionBasedClassMetadataProviderFactory ,它通过反射提供类元数据。 |
GuardrailServiceBuilderFactory | 提供用于构建 GuardrailService 实例的构建器。如果应用或框架需要自定义 GuardrailService 的构建方式,可以实现该接口。 |
InputGuardrailsConfigBuilderFactory | - SPI,用于覆盖或扩展默认的 InputGuardrailsConfigBuilder 。- 其他框架可以提供其自己的实现,并增加额外配置。 - 允许框架通过其他机制驱动输入护栏配置(例如配置文件)。 |
OutputGuardrailsConfigBuilderFactory | - SPI,用于覆盖或扩展默认的 OutputGuardrailsConfigBuilder 。- 其他框架可以提供其自己的实现,并增加额外配置。 - 允许框架通过其他机制驱动输出护栏配置(例如配置文件)。 |
InputGuardrailExecutorBuilderFactory | - SPI,用于覆盖或扩展默认的 InputGuardrailExecutorBuilder ,以构建 InputGuardrailExecutor 实例。 |
OutputGuardrailExecutorBuilderFactory | - SPI,用于覆盖或扩展默认的 OutputGuardrailExecutorBuilder ,以构建 OutputGuardrailExecutor 实例。 |