0%

langchain4j Guardrails能力

Guardrails(护栏)是一种机制,用于验证 LLM 的输入和输出,确保其符合您的预期。可以使用护栏来实现以下目的(或其他功能):

  • 过滤不当内容
  • 验证输出格式
  • 限制敏感信息泄露
  • 确保回答符合业务规则

实验性功能:Guardrails 是一项实验性功能。其 API 和行为可能在未来版本中发生变化。
护栏仅在使用 AI Services 时可用。 它们是高层抽象,不能直接应用于 ChatModelStreamingChatModel

langchain4j-guard.png


设计原则

理想情况下,护栏的实现应遵循单一职责原则(Single Responsibility Principle),即每个护栏类只应验证一件事。然后,将多个护栏串联起来,以防范多种问题。

护栏链中的顺序

护栏在链中的顺序非常重要。链中第一个失败的护栏将触发整体失败。请确保:

  • 捕获最多失败的护栏放在链的前面
  • 更具体、失败频率较低的护栏放在链的后面

成本考量

请记住,护栏本身可能会调用其他服务,甚至触发其他 LLM 交互。如果这类护栏有执行开销或金钱成本,请务必将其纳入考虑。您可能希望将成本较高的护栏放在链的后面。

这里的”昂贵”可以指执行时间较长,也可以指有金钱成本。


输入护栏(Input Guardrails)

输入护栏是在调用 LLM 之前执行的函数。输入护栏失败将阻止 LLM 被调用。输入护栏是调用 LLM 前的最后一步,它们在所有 RAG 操作完成之后才被调用。

实现方式

输入护栏通过实现 InputGuardrail 接口来创建。该接口提供了两个 validate 方法变体,至少需要实现其中一个:

1
2
// 变体 1:仅访问 UserMessage(适用于简单护栏)
InputGuardrailResult validate(UserMessage userMessage)
1
2
// 变体 2:访问更完整的上下文信息
InputGuardrailResult validate(InputGuardrailRequest request)
  • 第一个变体用于简单护栏,或护栏只需要访问 UserMessage 的场景。
  • 第二个变体用于更复杂的护栏,需要更多信息,例如聊天记忆/历史、用户消息模板、增强结果或传递给模板的变量。详见 InputGuardrailRequest

输入护栏可以做的事情(示例)

  • 检查用户输入是否包含敏感词或不当内容
  • 验证用户是否提供了所有必需的参数
  • 检测用户意图是否偏离了允许的范围
  • 对用户输入进行格式校验

输入护栏的结果

输入护栏可以返回以下结果,可通过 InputGuardrail 接口上的辅助方法来生成:

结果 说明
成功(success) 验证通过,继续执行链中的下一个护栏或调用 LLM
失败(failure) 验证失败,返回失败原因,不调用 LLM

声明输入护栏的方式(按优先级排序)

以下是声明输入护栏的几种方式,按优先级从高到低排列

  1. 直接在 AiServices builder 上设置 InputGuardrail 实现类名或实例(优先级最高)
  2. 通过注解方式声明

方式一:在 Builder 上设置

1
2
3
4
// 传递 InputGuardrail 实现类,将通过反射动态创建实例
AiServices.builder(...)
.inputGuardrails(MyInputGuardrail.class)
.build();
1
2
3
4
// 直接传递 InputGuardrail 实例
AiServices.builder(...)
.inputGuardrails(new MyInputGuardrail())
.build();

类的实例化方式可以自定义。例如,使用依赖注入的框架(如 Quarkus 或 Spring)可以利用扩展点来提供实例,而不是每次都通过反射创建新实例。

方式二:通过注解声明

1
2
3
4
5
6
// 仅 chat 方法有护栏
@InputGuardrails({MyInputGuardrail.class})
String chat(String userMessage);

// doSomethingElse 方法没有任何护栏
String doSomethingElse(String input);
1
2
3
4
5
6
// chat 和 doSomethingElse 方法都有护栏
@InputGuardrails({MyInputGuardrail.class})
String chat(String userMessage);

@InputGuardrails({MyInputGuardrail.class})
String doSomethingElse(String input);

单元测试

langchain4j-test 模块中提供了一些基于 AssertJ 的单元测试工具。

添加依赖后,您可以执行以下验证:

1
2
3
4
5
6
// 验证输入护栏是否成功
assertThat(inputGuardrailResult).isSuccess();

// 验证输入护栏是否失败及失败原因
assertThat(inputGuardrailResult).isFailure()
.hasFailureReason("具体的失败原因");

输出护栏(Output Guardrails)

输出护栏是在 LLM 生成输出之后执行的函数。输出护栏失败允许更高级的场景,例如重试(retrying)重新提示(reprompting),以帮助改进响应。它们在所有其他操作(包括函数/工具调用)完成之后才被调用。

实现方式

与输入护栏类似,输出护栏通过实现 OutputGuardrail 接口来创建。该接口提供了两个 validate 方法变体,至少需要实现其中一个:

1
2
// 变体 1:仅访问生成的 AiMessage(适用于简单护栏)
OutputGuardrailResult validate(AiMessage aiMessage)
1
2
// 变体 2:访问更完整的上下文信息
OutputGuardrailResult validate(OutputGuardrailRequest request)
  • 第一个变体用于简单护栏,或护栏只需要访问生成的 AiMessage 的场景。
  • 第二个变体用于更复杂的护栏,需要更多信息,例如完整的聊天响应、聊天记忆/历史、用户消息模板或传递给模板的变量。详见 OutputGuardrailRequest

输出护栏可以做的事情(示例)

  • 验证输出是否符合预期的格式(如 JSON Schema)
  • 检查输出是否包含敏感信息
  • 评估输出质量是否达标,不达标则触发重试
  • 对输出进行内容安全审核

输出护栏的结果

输出护栏可以返回以下结果,可通过 OutputGuardrail 接口上的辅助方法来生成:

结果 说明
成功(success) 验证通过,将结果返回给用户
失败 - 重试(failure with retry) 验证失败,重新调用 LLM 尝试生成更好的响应
失败 - 重新提示(failure with reprompt) 验证失败,使用修改后的提示重新调用 LLM

声明输出护栏的方式(按优先级排序)

以下是声明输出护栏的几种方式,按优先级从高到低排列

  1. 直接在 AiServices builder 上设置 OutputGuardrail 实现类名或实例(优先级最高)
  2. 通过注解方式声明

方式一:在 Builder 上设置

1
2
3
4
// 传递 OutputGuardrail 实现类,将通过反射动态创建实例
AiServices.builder(...)
.outputGuardrails(MyOutputGuardrail.class)
.build();
1
2
3
4
// 直接传递 OutputGuardrail 实例
AiServices.builder(...)
.outputGuardrails(new MyOutputGuardrail())
.build();

无论以何种方式声明,输出护栏始终按照它们在列表中的出现顺序执行。

类的实例化方式可以自定义。例如,使用依赖注入的框架(如 Quarkus 或 Spring)可以利用扩展点来提供实例,而不是每次都通过反射创建新实例。

方式二:通过注解声明

1
2
3
4
5
6
// 仅 chat 方法有护栏
@OutputGuardrails({MyOutputGuardrail.class})
String chat(String userMessage);

// doSomethingElse 方法没有任何护栏
String doSomethingElse(String input);
1
2
3
4
5
6
// chat 和 doSomethingElse 方法都有护栏
@OutputGuardrails({MyOutputGuardrail.class})
String chat(String userMessage);

@OutputGuardrails({MyOutputGuardrail.class})
String doSomethingElse(String input);

额外配置

输出护栏还支持以下额外配置:

  • maxRetries:最大重试次数,当护栏失败并选择重试时,最多重试的次数。

流式响应中的输出护栏

输出护栏也可以在流式响应操作中使用:

1
2
3
4
TokenStream tokenStream = assistant.chat(userMessage);
tokenStream.onCompleteResponse(response -> {
// 处理完整响应
});

在这种场景下,输出护栏将在整个流完成之后才执行,更具体地说,是在 TokenStream.onCompleteResponse 被调用时执行。onPartialResponse 将被缓冲,并在护栏验证成功后重放。

如果链中的重试(retry)重新提示(reprompt)最终成功,则整个链将同步重新执行。每个护栏将按照原始顺序逐一重新执行。链完成后,结果将传递给 TokenStream.onCompleteResponse

LangChain4j 内置的输出护栏实现

LangChain4j 为一些常见用例提供了开箱即用的输出护栏实现:

  • 输出格式验证护栏
  • 内容安全审核护栏
  • 自定义业务规则验证护栏

单元测试

langchain4j-test 模块中提供了一些基于 AssertJ 的单元测试工具。

添加依赖后,您可以执行以下验证:

1
2
3
4
5
6
7
8
9
10
// 验证输出护栏是否成功
assertThat(outputGuardrailResult).isSuccess();

// 验证输出护栏是否失败及失败原因
assertThat(outputGuardrailResult).isFailure()
.hasFailureReason("具体的失败原因");

// 验证是否触发了重试
assertThat(outputGuardrailResult).isFailure()
.isRetry();

混合使用输入和输出护栏

您可以按照自己的需求自由组合输入和输出护栏!

1
2
3
4
5
6
7
// 在 Builder 上设置全局输入护栏和输出护栏配置
AiServices<Assistant> aiServices = AiServices.builder(Assistant.class)
.inputGuardrails(AnotherInputGuardrail.class) // 所有方法共享的输入护栏
.outputGuardrailsConfig(OutputGuardrailsConfig.builder()
.maxRetries(10) // 所有输出护栏的最大重试次数
.build())
.build();

在上述示例中:

  • Assistant 上的所有方法都有同一个输入护栏 AnotherInputGuardrail(因为在 AiServices builder 上设置了)。
  • 此外,所有输出护栏的 maxRetries 值都为 10(因为配置也在 builder 上设置了)。

如果某个方法上通过注解额外声明了输出护栏:

1
2
3
4
5
6
7
// chat 方法有输出护栏 SomeOutputGuardrail,maxRetries = 10
@OutputGuardrails({SomeOutputGuardrail.class})
String chat(String userMessage);

// chatAndReturnJson 方法有输出护栏 MyObjectJsonOutputGuardrail,maxRetries = 10
@OutputGuardrails({MyObjectJsonOutputGuardrail.class})
MyObject chatAndReturnJson(String userMessage);

扩展点

护栏系统以可组合的方式构建,因此可以在其他下游框架(如 Quarkus 或 Spring Boot)中扩展和复用。本节介绍提供的一些扩展点:

  • 自定义实例创建:框架可以通过扩展点自定义 InputGuardrail / OutputGuardrail 实现类的实例化方式(例如使用依赖注入容器)。
  • 自定义护栏链行为:可以扩展护栏的执行逻辑,满足特定框架的集成需求。
  • 与框架生命周期集成:可以将护栏与框架自身的配置、监控、日志等机制深度集成。

总结

特性 输入护栏(Input Guardrail) 输出护栏(Output Guardrail)
执行时机 调用 LLM 之前 LLM 生成输出之后
失败后果 阻止 LLM 调用 可触发重试或重新提示
实现接口 InputGuardrail OutputGuardrail
适用场景 输入校验、内容过滤、参数验证 输出校验、格式验证、内容审核
流式支持 ✅(流完成后执行)
声明方式 Builder / 注解 Builder / 注解