护栏(Guardrails)
护栏是一项实验性功能。其 API 与行为可能在未来版本中发生变化。
护栏是用于校验 LLM 输入与输出、确保其符合预期的机制。借助护栏,你可以完成例如以下事情:
- 验证用户输入是否超出范围
- 在调用 LLM 之前确保输入满足某些条件(例如防范 提示注入攻击)
- 确保输出格式正确(例如是符合正确 schema 的 JSON 文档)
- 确保 LLM 输出与业务规则与约束一致(例如若这是公司 X 的聊天机器人,则响应中不应包含对竞争对手 Y 的任何引用)
- 检测幻觉(hallucinations)
以上只是示例。你还可以用护栏做许多其他事情。
护栏仅在使用 AI Services 时可用。它们是更高层的抽象,不能应用于 ChatModel 或 StreamingChatModel。

;
该实现最初在 Quarkus LangChain4j 扩展 中完成,并回移植到此处。
实现护栏
理想情况下,护栏实现应遵循 单一职责原则,即每个护栏类只校验一件事。然后把护栏串联起来,以同时防范多种问题。
护栏链中的顺序很重要。链中第一个失败的护栏会触发整体失败。应把最容易捕获失败的护栏放在链的前面,而那些更具体、很少失败的护栏放在链的后面。
还需注意,护栏本身可以调用其他服务,甚至发起其他 LLM 交互。如果这类护栏有执行开销或金钱成本,请 把这一点考虑进去。你可能希望把更「昂贵」的护栏放在链的末尾。
术语 昂贵(expensive) 可以指某件事执行需要一定时间,也可以指与之相关的金钱成本。
输入护栏
输入护栏是在调用 LLM 之前执行的函数。输入护栏失败会阻止调用 LLM。输入护栏是调用 LLM 之前的最后一步。它们在任何 RAG 操作完成之后才会被调用。
实现输入护栏
输入护栏通过实现 InputGuardrail 接口来完成。InputGuardrail 接口有两个版本的 validate 方法,至少需要实现其中一个:
InputGuardrailResult validate(UserMessage userMessage);
InputGuardrailResult validate(InputGuardrailRequest params);
第一个变体用于简单护栏,或护栏只需要访问 UserMessage 时。
第二个变体用于更复杂的护栏,需要更多信息,例如聊天记忆/历史、用户消息模板、增强(augmentation)结果,或传递给模板的变量。详见 InputGuardrailRequest。
你可以做的一些示例:
- 检查增强结果中是否有足够的文档
- 确保用户没有多次询问同一个问题
- 缓解潜在的提示注入攻击
- 使用社区 Prompt Repetition 模块重写符合条件的单文本输入
无论操作是同步还是异步/流式,都可以使用输入护栏。
输入护栏结果
输入护栏可以产生以下结果。InputGuardrail 接口上有辅助方法可用于提供这些结果:
| 结果 | InputGuardrail 上的辅助方法 | 说明 |
|---|---|---|
| success | success() | - 输入有效。 - 执行链中的下一个护栏。 - 若最后一个护栏通过,则调用 LLM。 |
| success with alternate result | successWith(String) | 与 success 类似,但在进入下一步(链中下一个护栏或调用 LLM)之前会改写用户消息。 |
| failure | failure(String) 或 failure(String, Throwable) | - 输入无效,但链中后续护栏会继续执行,以便累积所有可能的校验问题。 - 不会调用 LLM。 - 若传入了 Throwable,调用方可以捕获 InputGuardrailException 并检查 cause。它就是此处传入的 Throwable。 |
| fatal | fatal(String) 或 fatal(String, Throwable) | - 输入无效,并以 InputGuardrailException 中止执行。- 不会调用 LLM。 - 若传入了 Throwable,调用方可以捕获 InputGuardrailException 并检查 cause。它就是此处传入的 Throwable。 |
声明输入护栏
有多种方式声明输入护栏,按优先级从高到低列出如下:
- 直接在
AiServices构建器上设置的InputGuardrail实现类名或实例。 - 标注在单个 AI Service 方法上的
@InputGuardrails注解。 - 标注在 AI Service 类上的
@InputGuardrails注解。 无论以何种方式声明,输入护栏始终按列表中出现的顺序执行。
AiServices 构建器
直接在 AiServices 构建器上设置的 InputGuardrail 实现类名或实例具有最高优先级,意味着若以其他方式也声明了护栏,将使用构建器上直接声明的那个。
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();
若你想要一个现成的实验性输入护栏,用提示重复(prompt repetition)重写符合条件的单文本输入,请参见社区 Prompt Repetition 模块。
在第一种场景中,传入的是实现 InputGuardrail 的类。会通过反射动态创建这些类的新实例。
标注在单个 AI Service 方法上
标 注在单个 AI Service 方法上的 @InputGuardrails 注解 具有次高优先级。
public interface Assistant {
@InputGuardrails({ FirstInputGuardrail.class, SecondInputGuardrail.class })
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.create(Assistant.class, chatModel);
在此示例中,只有 chat 方法有护栏。
- 在
chat方法上,首先调用FirstInputGuardrail。 - 仅当它成功时才会调用 LLM。
- 仅当
FirstInputGuardrail未产生 fatal 结果时,才会调用SecondInputGuardrail。 FirstInputGuardrail或SecondInputGuardrail都可以重写用户消息。- 若
FirstInputGuardrail重写了用户消息,则SecondInputGuardrail会收到新的用户消息作为输入。
doSomethingElse 方法没有任何护栏。
标注在 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 方法都有护栏。
- 与上一个示例一样,首先调用
FirstInputGuardrail。 - 仅当它成功时才会调用 LLM。
- 仅当
FirstInputGuardrail未产生 fatal 结果时,才会调用SecondInputGuardrail。 FirstInputGuardrail或SecondInputGuardrail都可以重写用户消息。- 若
FirstInputGuardrail重写了用户消息,则SecondInputGuardrail会收到新的用户消息作为输入。
对输入护栏进行单元测试
langchain4j-test 模块中有一些基于 AssertJ 的单元测试工具。
- Maven
- Gradle (Groovy)
- Gradle (Kotlin)
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-test</artifactId>
<scope>test</scope>
</dependency>
testImplementation 'dev.langchain4j:langchain4j-test'
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);
// These are just some examples of what you can do
assertThat(result)
.isSuccessful()
.hasResult(Result.FATAL)
.hasFailures()
.hasSingleFailureWithMessage("Prompt injection detected")
.assertSingleFailureSatisfied(failure -> assertThat(failure)...)
.withFailures().....
}
}
更多细节请参见 GuardrailAssertions 与 InputGuardrailResultAssert 类。
开箱即用的输入护栏
对于若干常见用例,LangChain4j 提供了输入护栏的实现:
| 护栏类 | 说明 |
|---|---|
MessageModeratorInputGuardrail | 使用 ModerationModel 校验用户消息的输入护栏,用于检测潜在有害、不当或违反策略的内容。- 检查传入消息是否包含仇恨言论、暴力、自残、色情内容,或审核模型定义的其他类别。 - 若消息被标记,校验以 fatal 结果失败,阻止消息被进一步处理。 - 适用于在将用户输入发送给 LLM 之前,确保其符合内容策略。 |
PatternBasedPromptInjectionGuardrail | 基于模式的输入护栏,使用源自 OWASP LLM01 的正则表达式检测提示注入尝试。 - 覆盖指令覆盖、角色劫持、越狱、系统提示泄露、分隔符注入以及编码载荷。 - 零外部依赖且延迟亚毫秒级,适合作为护栏链中第一个(最便宜的)关卡,置于任何基于 LLM 的分类器之前。 - 子类可以添加领域特定模式,并自定义失败消息。 |
输出护栏
输出护栏是在 LLM 产生输出之后执行的函数。输出护栏失败允许更高级的场景,例如重试(retry)或重新提示(reprompt),以帮助改进响应。它们在所有其他操作(包括函数/工具调用)完成之后才会被调用。
实现输出护栏
与输入护栏类似,输出护栏通过实现 OutputGuardrail 接口来完成。OutputGuardrail 接口有两个版本的 validate 方法,至少需要实现其中一个:
OutputGuardrailResult validate(AiMessage responseFromLLM);
OutputGuardrailResult validate(OutputGuardrailRequest params);
第一个变体用于简单护栏,或护栏只需要访问结果 AiMessage 时。
第二个变体用于更复杂的护栏,需要更多信息,例如完整的聊天响应、聊天记忆/历史、用户消息模板,或传递给模板的变量。详见 OutputGuardrailRequest。
你可以做的一些示例:
- 确保输出格式正确(例如是符合正确 schema 的 JSON 文档)
- 检测 LLM 幻觉
- 校验 LLM 响应是否包含某些信息
输出护栏结果
输出护栏可以产生以下结果。OutputGuardrail 接口上有辅助方法可用于提供这些结果:
| 结果 | OutputGuardrail 上的辅助方法 | 说明 |
|---|---|---|
| success | success() | - 输出有效。 - 执行链中的下一个护栏。若最后一个护栏通过,则将输出返回给调用方。 |
| success with rewrite | successWith(String) 或 successWith(String, Object) | - 与 success 类似,但输出在原始形式上无效,已被重写为有效形式。 - 对重写后的输出执行下一个护栏。若最后一个护栏通过,则将输出返回给调用方。 |
| failure | failure(String) 或 failure(String, Throwable) | - 输出无效,但链中后续护栏会继续执行,以便累积所有可能的校验问题。 - 校验失败以 OutputGuardrailException 的形式返回给用户。 |
| fatal | fatal(String) 或 fatal(String, Throwable) | 输出无效,并以抛向调用方的 OutputGuardrailException 中止执行。 |
| fatal with retry | retry(String) 或 retry(String, Throwable) | - 与 fatal 类似,但会使用与原始调用相同的提示与聊天历史再次调用 LLM。 - 若在可配置的重试次数之后失败仍然存在,则以抛向调用方的 OutputGuardrailException 中止执行。- 若护栏在重试后通过,则从开头重新执行整个护栏链。 |
| fatal with reprompt | reprompt(String, String) 或 reprompt(String, Throwable, String) | - 与 fatal with retry 类似,但会使用护栏提供的新提示再次调用 LLM。 - 在此情况下,护栏提供一条附加消息追加到先前的用户消息,然后用新的用户消息与原始聊天历史向 LLM 发送新请求。 - 若在可配置的重试次数之后失败仍然存在,则以抛向调用方的 OutputGuardrailException 中止执行。- 若护栏在重新提示后通过,则从开头重新执行整个护栏链。 |
声明输出护栏
有多种方式声明输出护栏,按优先级从高到低列出如下:
- 直接在
AiServices构建器上设置的OutputGuardrail实现类名或实例。 - 标注在单个 AI Service 方法上的
@OutputGuardrails注解。 - 标注在 AI Service 类上的
@OutputGuardrails注解。
无论以何种方式声明,输出护栏始终按列表中出现的顺序执行。
AiServices 构建器
直接在 AiServices 构建器上设置的 OutputGuardrail 实现类名或实例具有最高优先级,意味着若以其他方式也声明了护栏,将使用构建器上声明的那个。
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 方法有护栏。
- 在
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在重试或重新提示后成功,则FirstOutputGuardrail与SecondOutputGuardrail都会被重新执行。
配置
输出护栏还有以下可提供的附加配置:
| 配置 | 说明 |
|---|---|
maxRetries | - 在执行重试或重新提示时,输出护栏的最大重试次数。 - 默认为 2。- 设为 0 可禁用重试。 |
标注在单个 AI Service 方法上
public interface MethodLevelAssistant {
@OutputGuardrails(
value = { FirstOutputGuardrail.class, SecondOutputGuardrail.class },
maxRetries = 10
)
String chat(String question);
}
var assistant = AiServices.create(MethodLevelAssistant.class, chatModel);
标注在 AI Service 类上
@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
- Gradle (Groovy)
- Gradle (Kotlin)
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-test</artifactId>
<scope>test</scope>
</dependency>
testImplementation 'dev.langchain4j:langchain4j-test'
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);
// These are just some examples of what you can do
assertThat(result)
.isSuccessful()
.hasResult(Result.FATAL)
.hasFailures()
.hasSingleFailureWithMessage("Hallucination detected!")
.hasSingleFailureWithMessageAndReprompt("Hallucination detected!", "Please LLM don't hallucinate!")
.assertSingleFailureSatisfied(failure -> assertThat(failure)...)
.withFailures().....
}
}
更多细节请参见 GuardrailAssertions 与 OutputGuardrailResultAssert 类。
混合使用
你可以按任意方式混合搭配输入护栏与输出护栏!
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(PatternBasedPromptInjectionGuardrail.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();
在此示例中,由于在 AiServices 构建器上设置了输入护栏,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 | - 用于覆盖和/或扩展默认 InputGuardrailsConfigBuilder 的 SPI- 其他框架可能提供带有额外输入护栏配置的自有实现。 - 也允许其他框架通过其他机制(例如属性文件)驱动输入护栏配置。 |
OutputGuardrailsConfigBuilderFactory | - 用于覆盖和/或扩展默认 OutputGuardrailsConfigBuilder 的 SPI- 其他框架可能提供带有额外输出护栏配置的自有实现。 - 也允许其他框架通过其他机制(例如属性文件)驱动输出护栏配置。 |
InputGuardrailExecutorBuilderFactory | - 用于覆盖和/或扩展默认 InputGuardrailExecutorBuilder 的 SPI,该构建器负责构建 InputGuardrailExecutor 实例。 |
OutputGuardrailExecutorBuilderFactory | - 用于覆盖和/或扩展默认 OutputGuardrailExecutorBuilder 的 SPI,该构建器负责构建 OutputGuardrailExecutor 实例。 |