如何实现LLM护栏:输入与输出防护指南
技术背景
LLM具有固有的随机性,在从原型转向生产时,提高可操控性成为关键需求。护栏作为检测性控制,旨在引导应用行为,防止有害内容进入或输出。常见场景包括主题偏离、越狱攻击、提示注入、幻觉、不当内容等。护栏的多样性使其可部署到LLM出错的任何场景,但设计时需权衡准确性、延迟和成本。
核心原理
护栏的核心原理是围绕LLM添加检测性控制,分为输入和输出两类。输入护栏在内容到达LLM前进行过滤,输出护栏在内容到达客户前验证。实现上常使用LLM本身作为护栏,通过提示词判断内容是否允许。异步设计可并行运行护栏和主LLM调用,若护栏触发则取消主调用,否则返回LLM响应。阈值设置需基于评估集和混淆矩阵,平衡误报和漏报。
架构分析
系统架构采用异步并发模式:主LLM调用和输入护栏并行执行,通过asyncio实现。若输入护栏触发,则取消主调用并返回预设响应;否则等待主调用完成,再执行输出护栏。输出护栏使用G-Eval方法,通过领域、标准、步骤和内容构造提示,让LLM评分。整体设计可水平扩展,增加更多护栏而不影响用户体验。
完整实战教程
如何使用护栏
在本笔记本中,我们分享了如何为你的LLM应用实现护栏的示例。护栏是旨在引导你的应用的检测性控制的通用术语。鉴于LLM固有的随机性,提高可操控性是一个常见需求,因此在将LLM从原型推向生产环境时,创建有效的护栏已成为性能优化最常见的领域之一。
护栏具有令人难以置信的多样性,几乎可以部署到你能想象到的LLM出错的任何场景。本笔记本旨在提供简单的示例,这些示例可以扩展以满足你的独特用例,并概述在决定是否实施护栏以及如何实施时需要考虑的权衡。
本笔记本将重点介绍:
- 输入护栏,在内容到达LLM之前标记不当内容
- 输出护栏,在内容到达客户之前验证LLM生成的内容
注意: 本笔记本将护栏视为围绕LLM的检测性控制的通用术语 - 如需提供预构建护栏框架分发的官方库,请查看以下内容:
import openai
GPT_MODEL = 'gpt-4o-mini'
1. 输入护栏
输入护栏旨在首先防止不当内容进入LLM——一些常见用例包括:
- 主题护栏: 识别用户何时提出离题问题,并告知他们LLM可以帮助处理哪些主题。
- 越狱: 检测用户何时试图劫持LLM并覆盖其提示。
- 提示注入: 捕获用户试图隐藏恶意代码的提示注入实例,这些代码将在LLM执行的任何下游功能中运行。
在所有这些情况下,它们都作为预防性控制,在LLM之前或并行运行,并在满足这些条件之一时触发应用程序以不同方式行为。
设计护栏
在设计护栏时,重要的是考虑准确性、延迟和成本之间的权衡,在尽可能减少对底线和用户体验影响的同时实现最大准确性。
我们将从一个简单的主题护栏开始,旨在检测离题问题并在触发时阻止LLM回答。该护栏由一个简单的提示组成,并使用gpt-4o-mini,在保持足够准确性的同时最大化延迟/成本,但如果我们想进一步优化,可以考虑:
- 准确性: 您可以考虑微调
gpt-4o-mini或使用少量示例来提高准确性。如果您有语料库可以帮助确定内容是否允许,RAG也可能有效。 - 延迟/成本: 您可以尝试微调较小的模型,如
babbage-002或开源模型(如Llama),在给定足够训练示例时它们可以表现得相当好。使用开源模型时,您还可以调整用于推理的机器,以最大化成本或延迟的降低。
这个简单的护栏旨在确保LLM只回答预定义的主题集,并对超出范围的问题以预设消息响应。
拥抱异步
最小化延迟的常见设计是将护栏与主LLM调用异步发送。如果护栏被触发,则返回其响应,否则返回LLM响应。
我们将采用这种方法,创建一个execute_chat_with_guardrails函数,该函数将并行运行LLM的get_chat_response和topical_guardrail护栏,并且仅当护栏返回allowed时才返回LLM响应。
局限性
在设计时,您应始终考虑护栏的局限性。需要注意的几个关键点包括:
- 当使用LLM作为护栏时,请注意它们与基础LLM调用本身具有相同的漏洞。例如,提示注入尝试可能成功绕过您的护栏和实际LLM调用。
- 随着对话变长,LLM更容易受到越狱的影响,因为您的指令被额外文本稀释。
- 如果您为了弥补上述问题而使护栏过于严格,可能会损害用户体验。这表现为过度拒绝,即护栏拒绝无害的用户请求,因为它们与提示注入或越狱尝试有相似之处。
缓解措施
如果你能将护栏与基于规则或更传统的机器学习检测模型相结合,这可以缓解其中一些风险。我们还看到客户设置护栏,只考虑最新消息,以减轻模型被长对话混淆的风险。
我们还建议逐步推出,并积极监控对话,以便发现提示注入或越狱的实例,并添加更多护栏来覆盖这些新类型的行为,或将它们作为训练示例纳入现有护栏中。
system_prompt = "You are a helpful assistant."
bad_request = "I want to talk about horses"
good_request = "What are the best breeds of dog for people that like cats?"
import asyncio
async def get_chat_response(user_request):
print("Getting LLM response")
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_request},
]
response = openai.chat.completions.create(
model=GPT_MODEL, messages=messages, temperature=0.5
)
print("Got LLM response")
return response.choices[0].message.content
async def topical_guardrail(user_request):
print("Checking topical guardrail")
messages = [
{
"role": "system",
"content": "Your role is to assess whether the user question is allowed or not. The allowed topics are cats and dogs. If the topic is allowed, say 'allowed' otherwise say 'not_allowed'",
},
{"role": "user", "content": user_request},
]
response = openai.chat.completions.create(
model=GPT_MODEL, messages=messages, temperature=0
)
print("Got guardrail response")
return response.choices[0].message.content
async def execute_chat_with_guardrail(user_request):
topical_guardrail_task = asyncio.create_task(topical_guardrail(user_request))
chat_task = asyncio.create_task(get_chat_response(user_request))
while True:
done, _ = await asyncio.wait(
[topical_guardrail_task, chat_task], return_when=asyncio.FIRST_COMPLETED
)
if topical_guardrail_task in done:
guardrail_response = topical_guardrail_task.result()
if guardrail_response == "not_allowed":
chat_task.cancel()
print("Topical guardrail triggered")
return "I can only talk about cats and dogs, the best animals that ever lived."
elif chat_task in done:
chat_response = chat_task.result()
return chat_response
else:
await asyncio.sleep(0.1) # sleep for a bit before checking the tasks again
# Call the main function with the good request - this should go through
response = await execute_chat_with_guardrail(good_request)
print(response)
# Call the main function with the bad request - this should get blocked
response = await execute_chat_with_guardrail(bad_request)
print(response)
看起来我们的护栏起作用了——第一个问题被放行,但第二个因偏离主题而被拦截。现在我们将扩展这个概念,以同样方式审核从 LLM 获得的响应。
2. 输出护栏
输出护栏管理 LLM 返回的内容。这些护栏可以有多种形式,其中一些最常见的包括:
- 幻觉/事实核查护栏: 使用真实信息语料库或幻觉响应的训练集来阻止幻觉响应。
- 审核护栏: 应用品牌和企业指南来审核 LLM 的结果,并在其违反这些指南时阻止或重写其响应。
- 语法检查: 来自 LLM 的结构化输出可能返回损坏或无法解析的内容——这些护栏会检测到这些问题,并重试或优雅地失败,防止下游应用程序出现故障。
- 这是函数调用中常见的控制措施,确保当 LLM 返回
function_call时,在arguments中返回预期的模式。
审核护栏
在这里,我们实现了一个审核护栏,它使用 G-Eval 评估方法的一个版本来对 LLM 响应中不需要的内容进行评分。该方法在我们的其他 notebooks 中有更详细的演示。
为了实现这一点,我们将构建一个可扩展的内容审核框架,该框架接收一个 domain 并使用一组 steps 将 criteria 应用于一段 content:
- 我们设置一个域名,描述我们将要审核的内容类型。
- 我们提供标准,明确概述内容应该和不应该包含的内容。
- 为 LLM 提供逐步说明来对内容进行评分。
- LLM 返回 1-5 的离散分数。
设置护栏阈值
我们的输出护栏将评估 LLM 的响应,并阻止任何评分为 3 或更高的内容。设置此阈值是一个常见的优化领域——我们建议构建一个评估集,并使用混淆矩阵对结果进行评分,以便为您的护栏设置正确的容差。这里的权衡通常是:
- 更多的误报会导致用户体验不佳,客户感到烦恼,助手似乎不那么有用。
- 更多的漏报可能对您的业务造成持久损害,因为人们会让助手回答不适当的问题,或提示注入/越狱它。
例如,对于越狱,您可能希望设置一个非常低的阈值,因为如果您的 LLM 被劫持并用于生成最终出现在社交媒体上的危险内容,对您的业务风险非常高。然而,对于我们的用例,我们愿意接受一些漏报,因为最坏的情况是有人最终得到一只比熊犬,而他们可能更适合拉布拉多犬,虽然这令人难过,但可能不会对我们的业务造成持久损害(我们希望如此)。
domain = "animal breed recommendation"
animal_advice_criteria = """
Assess the presence of explicit recommendation of cat or dog breeds in the content.
The content should contain only general advice about cats and dogs, not specific breeds to purchase."""
animal_advice_steps = """
1. Read the content and the criteria carefully.
2. Assess how much explicit recommendation of cat or dog breeds is contained in the content.
3. Assign an animal advice score from 1 to 5, with 1 being no explicit cat or dog breed advice, and 5 being multiple named cat or dog breeds.
"""
moderation_system_prompt = """
You are a moderation assistant. Your role is to detect content about {domain} in the text provided, and mark the severity of that content.
## {domain}
### Criteria
{scoring_criteria}
### Instructions
{scoring_steps}
### Content
{content}
### Evaluation (score only!)
"""
async def moderation_guardrail(chat_response):
print("Checking moderation guardrail")
mod_messages = [
{"role": "user", "content": moderation_system_prompt.format(
domain=domain,
scoring_criteria=animal_advice_criteria,
scoring_steps=animal_advice_steps,
content=chat_response
)},
]
response = openai.chat.completions.create(
model=GPT_MODEL, messages=mod_messages, temperature=0
)
print("Got moderation response")
return response.choices[0].message.content
async def execute_all_guardrails(user_request):
topical_guardrail_task = asyncio.create_task(topical_guardrail(user_request))
chat_task = asyncio.create_task(get_chat_response(user_request))
while True:
done, _ = await asyncio.wait(
[topical_guardrail_task, chat_task], return_when=asyncio.FIRST_COMPLETED
)
if topical_guardrail_task in done:
guardrail_response = topical_guardrail_task.result()
if guardrail_response == "not_allowed":
chat_task.cancel()
print("Topical guardrail triggered")
return "I can only talk about cats and dogs, the best animals that ever lived."
elif chat_task in done:
chat_response = chat_task.result()
moderation_response = await moderation_guardrail(chat_response)
if int(moderation_response) >= 3:
print(f"Moderation guardrail flagged with a score of {int(moderation_response)}")
return "Sorry, we're not permitted to give animal breed advice. I can help you with any general queries you might have."
else:
print('Passed moderation')
return chat_response
else:
await asyncio.sleep(0.1) # sleep for a bit before checking the tasks again
# Adding a request that should pass both our topical guardrail and our moderation guardrail
great_request = 'What is some advice you can give to a new dog owner?'
tests = [good_request,bad_request,great_request]
for test in tests:
result = await execute_all_guardrails(test)
print(result)
print('\n\n')
结论
护栏是LLM中一个充满活力且不断演进的话题,我们希望本笔记本能为你有效介绍护栏的核心概念。回顾一下:
- 护栏是检测性控制措施,旨在防止有害内容进入你的应用程序和用户,并为你的LLM在生产环境中增加可操控性。
- 它们可以采取输入护栏的形式,在内容到达LLM之前对其进行处理,以及输出护栏,控制LLM的响应。
- 设计护栏并设置其阈值是在准确性、延迟和成本之间的权衡。你的决策应基于对护栏性能的清晰评估,以及对假阴性和假阳性对你的业务成本的了解。
- 通过采用异步设计原则,你可以水平扩展护栏,以在护栏数量和范围增加时最小化对用户的影响。
我们期待看到你如何推进这一工作,以及随着生态系统的成熟,护栏思维如何演变。
常见问题
常见错误包括:护栏本身易受提示注入和越狱攻击;长对话中指令被稀释导致越狱风险增加;护栏过严导致过度拒绝。缓解措施包括结合规则或传统ML模型、只考虑最新消息、逐步推出并监控对话。排查时需检查护栏响应是否准确,调整阈值,并构建评估集进行测试。
企业应用扩展
生产环境优化:可微调小型模型(如babbage-002)或开源模型(如Llama)以降低延迟和成本;使用RAG增强准确性;设置动态阈值,根据业务风险调整;监控护栏性能,持续收集误报和漏报案例以改进。此外,可集成NeMo Guardrails或Guardrails AI等框架,扩展更多预构建护栏。
总结
本文面向有编程基础的开发者,深入讲解LLM应用中的护栏实现。护栏是检测性控制,用于引导LLM行为,防止不当内容进入或输出。文章涵盖输入护栏(主题、越狱、提示注入)和输出护栏(幻觉、审核、语法检查),通过异步设计最小化延迟。核心是使用LLM作为护栏,并权衡准确性、延迟和成本。提供可运行的代码示例,包括主题护栏和基于G-Eval的审核护栏。同时讨论局限性、缓解措施和阈值设置,强调评估集和混淆矩阵的重要性。
- --
内容来源与验证
- 内容等级:PREMIUM_A
- 质量等级:A
- 难度:中级
- 验证等级:V1
- 原文作者:colin-openai
- 原文日期:2023-12-19
- 来源许可:MIT
- 来源版本:
23ec5e3682ad7d45374005bf94fb848831247537 - 查看原文
站内还有更多完整实战教程
加入会员,解锁全部教程、完整代码与持续更新内容。
加入会员