LangChain4j Guardrails:给你的 AI Service 装上输入输出双层卡口
在做企业知识库问答或者客服 Agent 的时候,有一个问题早晚都会碰到:用户输入的内容你没法完全信任,模型输出的内容也没法完全信任。用户可能提交包含 PII 的文本,可能尝试绕过系统提示的限制,模型可能返回格式错误的 JSON,也可能在回答里顺手提到了竞品名字。
处理这些问题的传统做法是在业务层手写 if 判断,或者包一层 try-catch 加重试逻辑。LangChain4j 提供了一套更有结构的方案:Guardrails,一个专门绑定在 AI Service 上的输入输出校验机制。
Guardrails 是什么
Guardrails 是 LangChain4j 里专门针对 AI Service 的校验机制,分为 Input Guardrails 和 Output Guardrails 两层。Input Guardrails 在用户消息发给模型之前执行,可以拦截 Prompt 注入、PII 泄漏或不合规的内容。Output Guardrails 在模型返回响应之后执行,校验输出格式是否正确、内容是否符合业务规则,失败时可以触发重试或补充提示词后重发。
重要的一点是,Guardrails 只能在 AI Services 上使用,它是一个高层抽象,无法直接绑定到ChatModel或StreamingChatModel上。如果你的项目里直接操作底层ChatModel的接口,这套机制用不上,需要先迁移到AiServices的声明式风格。
先跑起来一个最简场景
假设你在做一个客服 Agent,需要拦截用户输入里的敏感话题,同时保证模型输出的内容一定是有效 JSON。
Maven 依赖(版本以实际发布为准,请确认 Maven Central 最新稳定版本):
<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>1.1.0</version> </dependency>定义 AI Service 接口:
public interface CustomerSupportAgent { @SystemMessage("你是一名客服助手,只回答与订单和退款相关的问题,回答必须是 JSON 格式。") String handleQuery(@UserMessage String query); }实现一个 Input Guardrail,拦截包含竞品名称的输入:
public class CompetitorMentionGuardrail implements InputGuardrail { private static final List<String> BLOCKED_KEYWORDS = List.of("竞品A", "竞品B"); @Override public InputGuardrailResult validate(UserMessage userMessage) { String text = userMessage.singleText(); boolean hasBlockedWord = BLOCKED_KEYWORDS.stream() .anyMatch(text::contains); if (hasBlockedWord) { return fatal("您的问题包含不支持讨论的内容,请重新描述您的问题。"); } return success(); } }实现一个 Output Guardrail,校验模型输出是否是合法 JSON:
public class JsonFormatGuardrail implements OutputGuardrail { @Override public OutputGuardrailResult validate(AiMessage aiMessage) { String content = aiMessage.text(); if (content == null || !content.trim().startsWith("{")) { return reprompt( "输出格式不合法", "请严格以 JSON 格式返回,以 '{' 开头,不要包含 Markdown 标记。" ); } return success(); } }把两个 Guardrail 挂到AiServices上:
CustomerSupportAgent agent = AiServices.builder(CustomerSupportAgent.class) .chatLanguageModel(model) .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) .inputGuardrails(new CompetitorMentionGuardrail()) .outputGuardrails(new JsonFormatGuardrail()) .build();三个细节值得注意
第一,Input Guardrail 的fatal()和 Output Guardrail 的reprompt()语义不同。Input Guardrails 不支持重试或重新提示,一旦fatal()触发,整个请求就终止了,这也意味着它能在发出 API 调用之前就把成本省下来。Output Guardrails 则可以通过reprompt()向原有上下文追加提示信息,然后让模型重试,直到达到最大重试次数上限。
第二,多个 Guardrail 可以叠加,执行顺序按注册顺序走。可以在AiServicesbuilder 上直接注册实例或类名,也可以通过@InputGuardrails和@OutputGuardrails注解放在接口方法或接口类上,builder 上注册的优先级最高。这个设计很适合把"通用 Guardrail"放在 builder 上,把"针对某个方法的特殊校验"放在注解上,职责分层清晰。
第三,Output Guardrail 校验失败时会把失败原因和reprompt内容追加进对话历史,触发下一次请求。这个机制在 JSON 格式校验、业务规则校验这类场景里很实用,但要注意设置合理的maxRetries,避免因为模型反复输出格式错误导致无限循环。实践中可以通过配置限制最大重试次数,3 次是一个相对合理的起点。
用更复杂的输入校验
对于 PII 检测、Prompt 注入这类场景,简单的关键词匹配往往不够用。LangChain4j 的文档里提到一种思路:在 Input Guardrail 内部再建一个轻量的AiServices实例,专门用于内容分类,用一个更小、更快的模型来判断输入是否合规,只有通过分类的请求才会继续往下走。这样做的好处是:主流程用的模型不变,分类任务可以选成本低得多的轻量模型,两者互不影响
// 在 Guardrail 内部使用独立的 AiService 做分类判断(伪代码,具体实现参考官方文档) public class PiiDetectionGuardrail implements InputGuardrail { private final TextClassifier classifier; // 内部轻量分类 AiService public PiiDetectionGuardrail(ChatLanguageModel fastModel) { this.classifier = AiServices.create(TextClassifier.class, fastModel); } @Override public InputGuardrailResult validate(UserMessage userMessage) { String verdict = classifier.classify(userMessage.singleText()); if ("UNSAFE".equalsIgnoreCase(verdict.trim())) { return fatal("输入包含敏感信息,请重新描述。"); } return success(); } }这个模式在企业场景里比较常见,主模型做业务回答,分类模型专注安全判断,两条链路并行,成本可控。
工程化落地要想清楚的事
Guardrails 机制本身并不复杂,但在项目里真正用好它,有几件事需要提前想清楚。
Guardrail 的粒度要合理。不要把所有校验逻辑堆在一个 Guardrail 类里,每个 Guardrail 专注一个职责,叠加使用。这和 Filter 链、拦截器链的设计原则是一样的,职责拆分清楚后,测试也更容易写。
Output Guardrail 的reprompt会消耗额外 Token。每一次重试都会把补充提示词追加进上下文再发一次请求,在生产环境里要关注这部分的 Token 消耗,必要时记录日志,评估哪些输出校验频繁触发重试。
对于流式响应场景,Guardrails 的支持需要单独确认你当前使用的版本,具体 API 行为可能和非流式场景有所不同,实际接入前以官方文档和 Release Notes 为准。
LangChain4j 的 Guardrails 把一类过去散落在业务层、各自为政的防御逻辑,收拢成了框架层面有结构的能力。它不是万能的安全方案,但它让这部分逻辑有了统一的接入点,也有了可测试、可复用的结构。对于正在做企业 AI 应用的 Java 团队,这是值得尽早了解的一个机制。
