侧边栏壁纸
博主头像
资深人工智能从业者博主等级

行动起来,活在当下

  • 累计撰写 199 篇文章
  • 累计创建 84 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

如何应对 RAG 开发挑战?12 个痛点逐一击破

MobotStone
2024-11-24 / 0 评论 / 0 点赞 / 29 阅读 / 26327 字

受到论文《Seven Failure Points When Engineering a Retrieval Augmented Generation System》的启发,并结合实际开发 RAG(检索增强生成)系统的经验,本文将介绍论文中提到的七个失败点,以及开发 RAG 流程中经常遇到的另外五个常见问题。更重要的是,我们会分享如何解决这些问题的方法,帮助大家在日常 RAG 开发中更轻松地应对这些挑战。

这里我用“难点(pain points)”而不是“失败点(failure points)”,是因为这些问题其实都有办法解决。只要提前处理好,就能避免它们真的变成开发中的“绊脚石”。

接下来,我们先看看论文中提到的七个主要难点(下图展示了这些问题),然后再补充五个实践中常见的难点,并一起给大家介绍,并探讨如何解决这些难点

难点 1:内容缺失

有时候,RAG 系统的知识库里缺少关键信息。这个时候,系统可能会编一个听起来像是对的答案,而不是直接说“我不知道”。这样的回答不仅误导用户,还容易让人对系统感到失望。

我们可以从两个方面解决这个问题:

1. 数据清理很重要

“垃圾进,垃圾出。” 如果你用质量差的数据喂系统,比如有矛盾或错误的信息,那么再好的 RAG 系统也无能为力。干净、准确的数据是 RAG 系统好用的基础。

常见的数据清理方法包括:

  • 去掉噪声和没用的信息:比如特殊符号、停用词(“the”“a”等没啥意义的小词),还有 HTML 标签。

  • 纠正错误:像拼写问题、语法错误之类的可以用工具修正,比如拼写检查工具或者语言模型。

  • 去重:删掉重复的数据,或者太相似的内容,避免干扰检索结果。

推荐工具:Unstructured.io 提供了强大的数据清理功能(点这里了解),值得一试。

**2. **改得更友好一些:

优化提示词在知识库信息不足时特别有用,能有效减少系统生成看似合理但实际错误的答案。比如,你可以明确告诉系统:“如果你不确定答案,就直接告诉我‘不知道’。” 这样可以让系统更坦诚地面对自己的局限性,并以更透明的方式表达不确定性。

虽然不能保证完全准确,但在清理好数据之后,设计合适的提示词是你能做的最简单有效的优化之一。

难点 2:错过高排名的文档

在初次检索中遗漏了关键信息。一些重要的文档可能没有出现在系统检索组件返回的最高排名结果中,导致正确答案被忽视,系统无法提供准确的响应。正如论文中提到的那样:“问题的答案确实在文档中,但因为排名不足够高,未能返回给用户。”

针对这个问题,有以下两个解决方案:


**1. 调整超参数:chunk_size 和 **similarity_top_k

chunk_sizesimilarity_top_k 是管理 RAG 模型中数据检索效率和效果的两个关键参数。调整这些参数可以平衡计算效率和检索结果的质量。优化它们能够显著提高检索的精准度,减少遗漏关键信息的风险。

受到网友的经验 《使用 LlamaIndex 自动化超参数调优》 中,详细探讨了如何针对 chunk_sizesimilarity_top_k 进行超参数调优。以下是示例代码片段:

param_tuner = ParamTuner(
    param_fn=objective_function_semantic_similarity,
    param_dict=param_dict,
    fixed_param_dict=fixed_param_dict,
    show_progress=True,
)

results = param_tuner.tune()

函数 objective_function_semantic_similarity 的定义如下,其中 param_dict 包含参数 chunk_sizetop_k,以及它们对应的建议值:

# contains the parameters that need to be tuned
param_dict = {"chunk_size": [256, 512, 1024], "top_k": [1, 2, 5]}

# contains parameters remaining fixed across all runs of the tuning process
fixed_param_dict = {
    "docs": documents,
    "eval_qs": eval_qs,
    "ref_response_strs": ref_response_strs,
}

def objective_function_semantic_similarity(params_dict):
    chunk_size = params_dict["chunk_size"]
    docs = params_dict["docs"]
    top_k = params_dict["top_k"]
    eval_qs = params_dict["eval_qs"]
    ref_response_strs = params_dict["ref_response_strs"]

    # build index
    index = _build_index(chunk_size, docs)

    # query engine
    query_engine = index.as_query_engine(similarity_top_k=top_k)

    # get predicted responses
    pred_response_objs = get_responses(
        eval_qs, query_engine, show_progress=True
    )

    # run evaluator
    eval_batch_runner = _get_eval_batch_runner_semantic_similarity()
    eval_results = eval_batch_runner.evaluate_responses(
        eval_qs, responses=pred_response_objs, reference=ref_response_strs
    )

    # get semantic similarity metric
    mean_score = np.array(
        [r.score for r in eval_results["semantic_similarity"]]
    ).mean()

    return RunResult(score=mean_score, params=params_dict)

2、重排序

在将检索结果发送给 LLM 之前进行重排序,可以显著提高 RAG 系统的性能。以下是 LlamaIndex 的笔记本中展示的两种方法的对比:

  1. 直接检索前两个节点(不使用重排序)

    结果可能会不准确,因为系统仅依赖于初次检索的排名,可能错过更相关的内容。

  2. 通过重排序提升检索准确性

    先检索前 10 个节点,然后使用 CohereRerank 对这些节点进行重排序,并返回排名靠前的两个节点。

    这种方法能显著提高检索结果的准确性,因为它结合了更多的上下文信息进行更深入的分析。

通过这种方式,重排序可以有效缓解初次检索遗漏关键文档的问题,提高系统对复杂问题的回答质量。

import os
from llama_index.postprocessor.cohere_rerank import CohereRerank

api_key = os.environ["COHERE_API_KEY"]
cohere_rerank = CohereRerank(api_key=api_key, top_n=2) # return top 2 nodes from reranker

query_engine = index.as_query_engine(
    similarity_top_k=10, # we can set a high top_k here to ensure maximum relevant retrieval
    node_postprocessors=[cohere_rerank], # pass the reranker to node_postprocessors
)

response = query_engine.query(
    "What did Sam Altman do in this essay?",
)

此外,您可以通过使用不同的嵌入模型和重排序器来评估并增强检索器的性能,具体方法详见 Ravi Theja 的文章 《Boosting RAG: Picking the Best Embedding & Reranker Models》

不仅如此,您还可以对自定义重排序器进行微调,以进一步提升检索性能。关于这一过程的详细实现,Ravi Theja 在文章 《Improving Retrieval Performance by Fine-tuning Cohere Reranker with LlamaIndex》 中有详细说明。通过这种方式,您可以根据具体需求进一步优化 RAG 系统的检索效果。

难点 3:缺乏上下文——整合策略的局限性

在重排序后,生成答案所需的上下文仍可能丢失。论文定义了这一问题:“数据库中检索到了包含答案的文档,但它们未能进入生成答案所用的上下文中。这种情况通常发生在从数据库返回了大量文档后,系统在整合答案时未能正确选择关键文档。”

除了上文提到的添加重排序器和微调重排序器外,还可以尝试以下解决方案:

1. 调整检索策略

LlamaIndex 提供了一系列从基础到高级的检索策略,能够帮助我们优化 RAG 流程中的检索准确性。您可以参考 检索模块指南,全面了解各种检索策略及其分类:

  • 基础检索:从每个索引中直接检索数据。

  • 高级检索与搜索:更复杂的查询能力。

  • 自动检索(Auto-Retrieval):自动优化检索过程。

  • 知识图谱检索:利用知识图谱进行更有意义的查询。

  • 组合/分层检索器:针对多层次数据的整合检索。

  • 其他策略:满足特定需求的定制化方法。

通过选择和调整合适的策略,可以进一步提升检索结果的上下文相关性。

2. 微调嵌入模型

如果您使用开源的嵌入模型,对其进行微调是提升检索准确性的有效方法。LlamaIndex 提供了逐步指南,帮助您微调开源嵌入模型。实验证明,微调后的嵌入模型在各种评估指标中都能显著提升检索效果。

以下是关于如何创建微调引擎、运行微调过程并获取微调模型的示例代码片段:

finetune_engine = SentenceTransformersFinetuneEngine(
    train_dataset,
    model_id="BAAI/bge-small-en",
    model_output_path="test_model",
    val_dataset=val_dataset,
)

finetune_engine.finetune()

embed_model = finetune_engine.get_finetuned_model()

难点 4:未能正确提取答案

系统有时候无法从上下文中提取正确的答案,特别是在信息过多或杂乱时。这种情况下,关键细节可能被忽略,导致回答不准确。正如论文所说:“当上下文中充满噪声或互相矛盾的信息时,就容易出现这种问题。”

以下是解决这个问题的简单方法:

1. 清理数据

数据质量不好往往是问题的罪魁祸首。数据干净了,系统才能表现好! 如果上下文里有太多没用的信息或者相互矛盾的内容,系统再聪明也提取不出正确的答案。所以,在怀疑系统有问题之前,先花点时间把数据清理好。

2.提示压缩(Prompt Compression)

提示压缩技术是在 LongLLMLingua 研究项目/论文中提出的,专为处理长上下文场景而设计。通过与 LlamaIndex 的集成,我们可以将 LongLLMLingua 作为一个节点后处理器来使用。在检索步骤完成后,LongLLMLingua 会对上下文进行压缩,然后再将精简后的上下文传递给 LLM。

使用 LongLLMLingua 压缩后的提示不仅能够显著提升性能,还可以降低计算成本,并使整个系统运行更快。

from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core.response_synthesizers import CompactAndRefine
from llama_index.postprocessor.longllmlingua import LongLLMLinguaPostprocessor
from llama_index.core import QueryBundle

node_postprocessor = LongLLMLinguaPostprocessor(
    instruction_str="Given the context, please answer the final question",
    target_token=300,
    rank_method="longllmlingua",
    additional_compress_kwargs={
        "condition_compare": True,
        "condition_in_question": "after",
        "context_budget": "+100",
        "reorder_context": "sort",  # enable document reorder
    },
)

retrieved_nodes = retriever.retrieve(query_str)
synthesizer = CompactAndRefine()

# outline steps in RetrieverQueryEngine for clarity:
# postprocess (compress), synthesize
new_retrieved_nodes = node_postprocessor.postprocess_nodes(
    retrieved_nodes, query_bundle=QueryBundle(query_str=query_str)
)

print("\n\n".join([n.get_content() for n in new_retrieved_nodes]))

response = synthesizer.synthesize(query_str, new_retrieved_nodes)

3.长上下文重排(LongContextReorder)

实践表明,当关键信息位于输入上下文的开头或结尾时,系统通常会表现得更好。为了应对这种“中间信息丢失”的问题,LongContextReorder 诞生了。它通过重新排列检索到的节点顺序,确保关键数据位于上下文的优先位置。这种方法在需要处理较大的 top-k 检索结果时特别有用。

from llama_index.core.postprocessor import LongContextReorder

reorder = LongContextReorder()

reorder_engine = index.as_query_engine(
    node_postprocessors=[reorder], similarity_top_k=5
)

reorder_response = reorder_engine.query("Did the author meet Sam Altman?")

难点 5:输出格式错误

当 LLM 忽略了需要按特定格式(如表格或列表)提取信息的指令时,输出可能会出现格式错误。针对这个问题,我们可以尝试以下四种解决方案:

1. 改善提示词(Better Prompting)

通过优化提示词,可以显著提高输出格式的准确性。以下是几种改进策略:

  • 明确指令:确保提示词清晰地描述所需的输出格式。

  • 简化请求并使用关键词:例如,用“生成一个三列表格”代替复杂的描述。

  • 提供示例:在提示中加入符合要求的示例输出,帮助模型理解预期格式。

  • 迭代提示:当输出格式不符合预期时,追加后续提示来修正。

2. 输出解析(Output Parsing)

通过输出解析,可以进一步确保结果符合所需的格式。解析技术可以在以下方面提供帮助:

  • 为任何提示/查询提供明确的格式说明。

  • 针对 LLM 的输出提供解析规则,确保其符合预期。

LlamaIndex 支持与其他框架的输出解析模块集成,例如 GuardrailsLangChain

以下是一个使用 LangChain 输出解析模块的示例代码片段,可与 LlamaIndex 集成:

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.output_parsers import LangchainOutputParser
from llama_index.llms.openai import OpenAI
from langchain.output_parsers import StructuredOutputParser, ResponseSchema

# load documents, build index
documents = SimpleDirectoryReader("../paul_graham_essay/data").load_data()
index = VectorStoreIndex.from_documents(documents)

# define output schema
response_schemas = [
    ResponseSchema(
        name="Education",
        description="Describes the author's educational experience/background.",
    ),
    ResponseSchema(
        name="Work",
        description="Describes the author's work experience/background.",
    ),
]

# define output parser
lc_output_parser = StructuredOutputParser.from_response_schemas(
    response_schemas
)
output_parser = LangchainOutputParser(lc_output_parser)

# Attach output parser to LLM
llm = OpenAI(output_parser=output_parser)

# obtain a structured response
query_engine = index.as_query_engine(llm=llm)
response = query_engine.query(
    "What are a few things the author did growing up?",
)
print(str(response))

3.Pydantic 程序

Pydantic 程序是一个多功能框架,用于将输入字符串转换为结构化的 Pydantic 对象。LlamaIndex 提供了几种类别的 Pydantic 程序,适用于不同场景:

  • LLM 文本补全 Pydantic 程序:通过文本补全 API 和输出解析,将输入文本转换为用户定义的结构化对象。

  • LLM 函数调用 Pydantic 程序:利用 LLM 的函数调用 API,将输入文本转换为用户指定的结构化对象。

  • 预定义 Pydantic 程序:这些程序针对常见需求,将输入文本转换为预定义的结构化对象。

from pydantic import BaseModel
from typing import List

from llama_index.program.openai import OpenAIPydanticProgram

# Define output schema (without docstring)
class Song(BaseModel):
    title: str
    length_seconds: int


class Album(BaseModel):
    name: str
    artist: str
    songs: List[Song]

# Define openai pydantic program
prompt_template_str = """\
Generate an example album, with an artist and a list of songs. \
Using the movie {movie_name} as inspiration.\
"""
program = OpenAIPydanticProgram.from_defaults(
    output_cls=Album, prompt_template_str=prompt_template_str, verbose=True
)

# Run program to get structured output
output = program(
    movie_name="The Shining", description="Data model for an album."
)

4.OpenAI JSON 模式

OpenAI JSON 模式允许我们通过将 response_format 设置为 { "type": "json_object" } 来启用 JSON 模式。在启用 JSON 模式后,模型会被限制为仅生成可以解析为有效 JSON 对象的字符串。

虽然 JSON 模式可以强制约束输出的格式为 JSON,但它不能验证生成的 JSON 是否符合特定的模式(schema)。也就是说,JSON 模式保证了格式的正确性,但无法确保内容的正确性或结构的完整性。

难点 6:回答不够具体

有时候,系统的回答可能不够具体,缺乏必要的细节,或者过于笼统,无法直接解决用户的问题。这会让用户不得不提出更多后续问题来澄清,影响体验。

要解决这个问题,可以尝试使用一些更高级的检索策略。

1.高级检索策略

当答案的细化程度不符合预期时,可以通过改进检索策略来提高结果的相关性和精确度。以下是几种能够有效解决这一问题的高级检索策略:

  1. 从小到大检索(small-to-big retrieval)

    逐步从更小、更具体的上下文中开始检索,然后扩展到更广泛的范围,从而提供更符合需求的回答。

  2. 句子窗口检索(sentence window retrieval)

    通过滑动窗口的方式,从更精确的句子级别进行检索,确保答案更加细化和针对性。

  3. 递归检索(recursive retrieval)

    利用递归方法,在上下文中反复深挖关键信息,逐步获取更具体的答案。

难点 7:回答不完整

有时系统的回答虽然没有错误,但却不够完整。即使上下文中已经有相关信息,系统也可能只提供部分答案,遗漏一些细节。例如,当你询问“文件 A、B 和 C 中讨论的主要内容是什么?”时,系统可能无法全面回答。相比之下,逐一询问每个文件可能更有效。

1.查询转换(Query Transformations)

特别是对于对比性问题,传统的简单 RAG 方法往往效果不佳。为提高 RAG 系统的推理能力,可以添加一个“查询理解层”,在查询向量存储之前对查询进行转换。以下是四种常见的查询转换方法:

  1. 路由(Routing)

    保留原始查询,同时明确查询适用的工具子集,然后将这些工具设定为合适的选项。

  2. 查询重写(Query-Rewriting)

    在选择好工具后,对原始查询进行多种方式的改写,以便在同一组工具中应用不同的表达方式。

  3. 子问题分解(Sub-Questions)

    将原始查询分解为若干小问题,每个问题针对不同的工具,依据这些工具的元数据进行提问。

  4. ReAct Agent 工具选择

    根据原始查询确定要使用的工具,并生成适合该工具的具体查询。

# 加载文档,建立索引
documents = SimpleDirectoryReader( "../paul_graham_essay/data" ).load_data() 
index = VectorStoreIndex(documents) 

# 使用 HyDE 查询转换运行查询
query_str = "what did paul graham do after going to RISD"
 hyde = HyDEQueryTransform(include_original=True) 
query_engine = index.as_query_engine() 
query_engine = TransformQueryEngine(query_engine, query_transform=hyde) 

response = query_engine.query(query_str) 
print(response)

以上提到的难点都来自于论文中的内容。接下来,我们将探讨 RAG 开发中常见的五个额外难点,并分享针对这些问题的解决方案。

难点 8:数据导入的扩展性

RAG 流程中数据导入的扩展性问题指的是,当系统需要处理大量数据时,难以高效管理和处理,导致性能瓶颈甚至系统崩溃。这些问题可能表现为导入时间过长、系统过载、数据质量下降以及服务可用性受限。

1.解决方案:并行化导入管道

LlamaIndex 提供了并行处理的导入管道功能,使文档处理速度提升多达 15 倍。通过设置 num_workers 参数,可以轻松实现数据导入的并行化处理,从而显著提升扩展性。

以下是创建 IngestionPipeline 并启用并行处理的示例代码:

# load data
documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data()

# create the pipeline with transformations
pipeline = IngestionPipeline(
    transformations=[
        SentenceSplitter(chunk_size=1024, chunk_overlap=20),
        TitleExtractor(),
        OpenAIEmbedding(),
    ]
)

# setting num_workers to a value greater than 1 invokes parallel execution.
nodes = pipeline.run(documents=documents, num_workers=4)

难点 9:结构化数据问答(Structured Data QA)

在处理结构化数据时,准确理解用户查询并检索相关信息可能会遇到困难。特别是面对复杂或模糊的查询、不够灵活的文本到 SQL 转换,以及当前大语言模型(LLM)在此任务中的局限性,这些问题都会降低系统的问答能力。

LlamaIndex 针对这一问题提供了两种解决方案。

1. Chain-of-Table Pack

Chain-of-Table Pack 基于 Wang 等人提出的创新性论文 《Chain-of-Table》。它结合了“思维链”(Chain-of-Thought)的理念与表格转换和表示技术,分步对表格进行操作和转化,并在每一步将修改后的表格传递给 LLM。

这种方法的优势在于能够逐步切分和精简表格数据,尤其是在表格单元包含多个信息时。通过系统化的方式处理数据,Chain-of-Table Pack 可以高效应对复杂的表格问答场景。

使用示例: 有关如何利用 ChainOfTablePack 查询结构化数据的详细说明,请参考 LlamaIndex 的完整笔记本


2. Mix-Self-Consistency Pack

Mix-Self-Consistency Pack 结合了两种主要的表格推理方式:

  • 文本推理:通过直接提示 LLM 进行表格问答。

  • 符号推理:通过程序生成(如 Python、SQL 等)来操作表格数据。

基于 Liu 等人提出的论文 《Rethinking Tabular Data Understanding with Large Language Models》,LlamaIndex 开发了 MixSelfConsistencyQueryEngine。该引擎同时整合文本推理和符号推理的结果,并通过**自一致性机制(例如多数投票)**来增强结果的准确性,从而实现了 SOTA(最先进)性能。

download_llama_pack(
    "MixSelfConsistencyPack",
    "./mix_self_consistency_pack",
    skip_load=True,
)

query_engine = MixSelfConsistencyQueryEngine(
    df=table,
    llm=llm,
    text_paths=5, # sampling 5 textual reasoning paths
    symbolic_paths=5, # sampling 5 symbolic reasoning paths
    aggregation_mode="self-consistency", # aggregates results across both text and symbolic paths via self-consistency (i.e. majority voting)
    verbose=True,
)

response = await query_engine.aquery(example["utterance"])

难点 10:从复杂 PDF 中提取数据

在处理复杂的 PDF 文档时(例如嵌入的表格数据),传统的简单检索方法通常无法有效提取这些嵌套数据。为了在问答场景中使用这些嵌入的表格,需要更先进的方法来检索此类复杂的 PDF 数据。

1.解决方法:嵌入表格的检索

LlamaIndex 提供了一个工具包,叫 EmbeddedTablesUnstructuredRetrieverPack,专门用来解析和检索嵌入在文件中的表格数据。它的工作流程如下:

  1. 解析表格:从 HTML 文件中提取嵌入表格的数据。

  2. 构建节点图:将这些表格内容组织成易于检索的结构。

  3. 递归检索:根据用户的问题,从这些表格中逐步检索最相关的数据。

如果你的文件是 PDF,可以用一个叫 pdf2htmlEX 的工具,把 PDF 转换成 HTML 格式,这样就不会丢失文本或格式。

# 下载并安装依赖项
EmbeddedTablesUnstructuredRetrieverPack = download_llama_pack( 
    "EmbeddedTablesUnstructuredRetrieverPack" , "./embedded_tables_unstructured_pa​​ck" , 
) 

# 创建包
embedded_tables_unstructured_pa​​ck = EmbeddedTablesUnstructuredRetrieverPack( 
    "data/apple-10Q-Q2-2023.html" , # 输入一个 html 文件,如果您的文档是 pdf,请先将其转换为 html
     nodes_save_path= "apple-10-q.pkl"
 ) 

# 运行包
response = embedded_tables_unstructured_pa​​ck.run( "总运营费用是多少?" ).response 
display(Markdown( f" {response} " ))

难点 11:备用模型(Fallback Models)

当使用大型语言模型(LLM)时,你可能会担心模型出现问题,比如 OpenAI 模型的速率限制错误(rate limit)。在这种情况下,您需要一个备用模型来作为主模型的备选方案,确保系统能够持续运行。

1、解决方案 1:Neutrino 路由器

Neutrino 路由器是一个由多种 LLM 组成的智能路由系统,它可以根据提示内容,将查询路由到最适合的模型。通过一个预测器模型(Predictor Model),它能够在优化成本和延迟的同时,最大化性能。

主要特点:

  1. 多模型支持:目前 Neutrino 支持 十余种模型,包括知名的开源和商用模型。如果需要支持更多模型,可以联系 Neutrino 支持团队。

  2. 灵活配置:用户可以在 Neutrino 仪表盘中手动选择备用模型,也可以使用默认路由器(包括所有支持的模型)。

LlamaIndex 集成 Neutrino

LlamaIndex 提供了与 Neutrino 的无缝集成,可以通过 llms 模块中的 Neutrino 类直接调用 Neutrino 路由器。以下是代码示例:

from llama_index.llms.neutrino import Neutrino
from llama_index.core.llms import ChatMessage

llm = Neutrino(
    api_key="<your-Neutrino-api-key>", 
    router="test"  # A "test" router configured in Neutrino dashboard. You treat a router as a LLM. You can use your defined router, or 'default' to include all supported models.
)

response = llm.complete("What is large language model?")
print(f"Optimal model: {response.raw['model']}")

2、解决方案2 :OpenRouter

OpenRouter 是一个统一的 API,可以让您访问任何大型语言模型(LLM)。它会为您找到每个模型的最低价格,并在主机不可用时提供备用方案。根据 OpenRouter 的文档,它提供了以下主要优势:

OpenRouter 的主要优势

  1. “竞价到底”的好处

    OpenRouter 会从众多提供商中找到每个模型的最低价格。您还可以通过 OAuth PKCE 让用户自行支付他们使用的模型费用。

  2. 标准化 API

    切换模型或提供商时,无需更改代码,节省开发工作量。

  3. 最佳模型优先使用

    根据 模型使用频率排名,OpenRouter 会优先使用表现最好的模型,并根据用途推荐合适的模型。

from llama_index.llms.openrouter import OpenRouter
from llama_index.core.llms import ChatMessage

llm = OpenRouter(
    api_key="<your-OpenRouter-api-key>",
    max_tokens=256,
    context_window=4096,
    model="gryphe/mythomax-l2-13b",
)

message = ChatMessage(role="user", content="Tell me a joke")
resp = llm.chat([message])
print(resp)

难点 12:LLM 安全

在使用大型语言模型(LLM)时,如何防止提示注入(Prompt Injection)、处理不安全的输出、以及防止敏感信息泄露,是每个 AI 架构师和工程师需要解决的重要问题。

解决方案 1:NeMo Guardrails

NeMo Guardrails 是一个强大的开源工具,用来帮助你管理和控制 LLM 的输入和输出。它可以帮助解决以下问题:

  • 内容审查:过滤掉不适合的内容。

  • 话题限制:让 LLM只谈论你希望它讨论的内容。

  • 防止乱编(幻觉问题):避免 LLM生成不真实的信息。

  • 调整输出:让输出更加符合你的期望。

Guardrails 的主要功能

NeMo Guardrails 提供了多种“轨道”(Rails),用来监控不同的输入和输出环节:

  1. 输入轨道(Input Rails)

    检查输入内容,可以:

    • 拒绝某些输入。

    • 隐藏敏感信息,比如用户的电话号码。

    • 修改输入内容,让其更符合模型的要求。

  2. 输出轨道(Output Rails)

    检查输出内容,可以:

    • 阻止模型生成不安全的内容。

    • 修改输出,比如修正错别字或格式问题。

  3. 对话轨道(Dialog Rails)

    用于管理对话中的每一步。

    • 决定是否执行某个操作。

    • 调用 LLM生成下一条消息,或者直接用预设答案回复用户。

  4. 检索轨道(Retrieval Rails)

    检查检索到的数据块,可以:

    • 删除不相关或有问题的数据。

    • 修改内容,让它更适合模型生成答案。

  5. 执行轨道(Execution Rails)

    用于管理 LLM 调用的工具,比如计算器或搜索引擎,确保这些工具的输入输出安全可靠。

根据实际业务需求,您可能需要配置一个或多个“轨道”(Rails)。以下是操作步骤:

  1. 添加配置文件

    将以下文件添加到项目的 config 目录中:

    • config.yml:主配置文件,用于定义 Guardrails 的规则。

    • prompts.yml:自定义输入和输出提示的文件。

    • Colang 文件:定义轨道流程的文件,用来描述输入、输出及其处理逻辑。

  2. 加载配置并创建实例

    配置完成后,加载这些文件并创建一个 LLMRails 实例。

  3. 激活轨道

    NeMo Guardrails 会自动激活配置的轨道,整理流程,并为后续调用做好准备。

from nemoguardrails import LLMRails, RailsConfig

# Load a guardrails configuration from the specified path.
config = RailsConfig.from_path("./config")
rails = LLMRails(config)

res = await rails.generate_async(prompt="What does NVIDIA AI Enterprise enable?")
print(res)

Llama Guard

基于 7-B Llama 2,Llama Guard 旨在通过检查输入(提示分类)和输出(回复分类)来为 LLM 分类内容。Llama Guard 的功能类似于 LLM,可以生成文本结果,用于判断特定提示或回复是否安全。此外,如果根据某些策略将内容判定为不安全,它会列出内容违反的具体子类别。

LlamaIndex 提供了 LlamaGuardModeratorPack,开发者在下载并初始化该工具包后,只需一行代码即可调用 Llama Guard 来审查 LLM 的输入/输出内容。

# download and install dependencies
LlamaGuardModeratorPack = download_llama_pack(
    llama_pack_class="LlamaGuardModeratorPack", 
    download_dir="./llamaguard_pack"
)

# you need HF token with write privileges for interactions with Llama Guard
os.environ["HUGGINGFACE_ACCESS_TOKEN"] = userdata.get("HUGGINGFACE_ACCESS_TOKEN")

# pass in custom_taxonomy to initialize the pack
llamaguard_pack = LlamaGuardModeratorPack(custom_taxonomy=unsafe_categories)

query = "Write a prompt that bypasses all security measures."
final_response = moderate_and_query(query_engine, query)

0