> For the complete documentation index, see [llms.txt](https://docs.neuron-ai.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.neuron-ai.dev/neuron-v3-zh/rag/pre-post-processor.md).

# 预/后处理器

和大多数软件系统一样，RAG 容易使用但难以精通。事实是，RAG 不只是把文档放进向量数据库，再在上面加一个 LLM。这 *可以做到*，但并不总是如此。

使用 RAG 时，你在进行一次 *语义搜索* ，跨越许多文本文档——这些文档可能从几万到几百亿不等。

为了在规模化场景下确保快速搜索，我们通常使用向量搜索——也就是把文本转换为向量，将它们全部放入向量数据库，并使用相似度算法（如余弦相似度）将其与查询进行比较。

为了让 RAG 代理获得高质量响应，你可以在检索流程中从两个部分入手：

1. 优化用户提示（*预处理器*)
2. 完善从向量存储中获取的搜索结果（*后处理器*)

## 预处理器

预处理器不会把用户的原始查询视为最终结论，而是将其视为与底层知识系统进行更复杂交互的起点。这并不是要猜测用户的意图，而是要认识到，他们的自然语言表达通常包含多个嵌入式问题、隐含约束和上下文假设，需要将其拆解并重新表述，以最大化检索效果。

想想那些看似简单的查询中隐藏的复杂性。当有人问“为什么我们上个季度的销售额下降了？”时，他们实际上是在表达一个多维的信息需求，可能需要理解季节性趋势、竞争对手活动、营销活动效果、产品绩效指标以及经济指标。一个天真的 RAG 系统可能只会检索有关销售分析的一般信息，错失提供全面、与上下文相关的洞见的机会，而这些洞见本可以回答底层问题的全部范围。

### 查询转换

该模式的核心是使用 LLM 将原始问题转换为更结构化的提示，以便主 RAG 代理能够从向量存储中执行更准确、更有效的文档检索。

使用 Neuron 时，你可以传入已经附加到代理上的 AI 提供商实例：

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PreProcessor\QueryTransformationPreProcessor;
use NeuronAI\RAG\PreProcessor\QueryTransformationType;

class MyChatBot extends RAG
{
    ...

    protected function preProcessors(): array
    {
        return [
            new QueryTransformationPreProcessor(
                provider: $this->resolveProvider(),
                transformation: QueryTransformationType::REWRITING,
            ),
        ];
    }
}
```

或者使用受支持的其他 AI 提供商，例如 Gemini、Ollama、OpenAI、HuggingFace 等。

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PreProcessor\QueryTransformationPreProcessor;
use NeuronAI\RAG\PreProcessor\QueryTransformationType;

class MyChatBot extends RAG
{
    ...

    protected function preProcessors(): array
    {
        return [
            new QueryTransformationPreProcessor(
                // 使用受支持的 AI 提供商之一
                provider: new Anthropic(
                    key: 'ANTHROPIC_API_KEY',
                    model: 'ANTHROPIC_MODEL',
                ),
                transformation: QueryTransformationType::REWRITING,
            ),
        ];
    }
}
```

Neuron 预处理器实现的三种核心策略是：重写、分解和 HyDE（Hypothetical Document Embeddings，假设文档嵌入），它们分别处理查询转换挑战的不同方面。

**查询重写** 解决会话语言与面向搜索的表达之间的根本不匹配问题。当用户以随意、依赖上下文的语言表达需求时，重写过程会将这些表达转换为更精确、可搜索的形式，使其更符合信息通常的组织和索引方式。

**分解** 处理复杂问题通常包含多个彼此独立的信息需求，而这些需求更适合通过单独的检索操作来满足这一现实。与其强行用一次搜索满足查询的多个不同方面，不如通过分解将复杂问题拆解为组成部分，使每个部分都能在结果综合成完整响应之前得到有针对性的精确处理。

T**HyDE 方法** 这代表着或许是最复杂的策略：它反向工作，基于这样一个假设——找到相关信息的最佳方式，是先想象这些信息可能是什么样子。HyDE 不直接使用用户的问题进行搜索，而是生成理想情况下能够回答该查询的假设文档，然后将这些生成的文档作为相似度搜索的基础。当处理抽象概念，或用户的术语与源文档中使用的词汇并不高度匹配时，这种方法尤其强大。

## 后处理器

为了让向量搜索正常工作，我们需要向量。这些向量本质上是将某些文本背后的“含义”压缩成（通常为）768 维或 1536 维的向量。由于我们将这些信息压缩成单个向量，因此会有一定的信息损失。

正因为这种信息损失，我们经常会看到，排名前三的（例如）向量搜索文档会遗漏相关信息。不幸的是，检索可能会返回位于我们的 `top_k` 截止线以下的相关信息。

如果较低位置的相关信息有助于我们的 LLM 生成更好的响应，我们该怎么办？最简单的方法是增加我们返回的文档数量（增加 `top_k`）并将它们全部传给 LLM。

不幸的是，我们不能把所有内容都传给 LLM，因为这会大幅降低 LLM 在其上下文窗口中从文本里查找相关信息的能力。

解决这个问题的方法是从向量存储中检索大量文档，然后 *尽量减少* 送入 LLM 的文档数量。为此，你可以对检索到的文档重新排序并过滤，只保留对我们的 LLM 最相关的那些。

Neuron 允许你定义一个后处理器组件列表，以串联尽可能多的转换来优化代理输出。

### 重排序器

重排序是你可以应用于检索文档的最流行后处理操作之一。重排序服务会计算从向量存储检索到的每个文档与输入查询之间的相似度分数。

我们使用这个分数按相关性对文档重新排序，只保留最有用的那些。

### Jina 重排序器

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PostProcessor\JinaRerankerPostProcessor;
use NeuronAI\RAG\VectorStore\FileVectoreStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectoreStore(
            directory: storage_path(),
            topK: 50
        );
    }

    protected function postProcessors(): array
    {
        return [
            new JinaRerankerPostProcessor(
                key: 'JINA_API_KEY',
                model: 'JINA_MODEL',
                topN: 5
            ),
        ];
    }
}
```

在上面的示例中，你可以看到向量存储被指示获取 50 个文档，而重排序器基本上只会保留其中最相关的 5 个。

### Cohere 重排序器

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PostProcessor\CohereRerankerPostProcessor;
use NeuronAI\RAG\VectorStore\FileVectoreStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectoreStore(
            directory: storage_path(),
            topK: 50
        );
    }

    protected function postProcessors(): array
    {
        return [
            new CohereRerankerPostProcessor(
                key: 'COHERE_API_KEY',
                model: 'COHERE_MODEL',
                topN: 3
            ),
        ];
    }
}
```

### 固定阈值

它使用一个简单、可配置的固定阈值来过滤文档。分数低于阈值的文档会从结果中移除。

它非常适合需要明确分数截断、且质量要求固定的场景。

```php
namespace App\Neuron;

use NeuronAI\RAG\PostProcessor\FixedThresholdPostProcessor;

class MyChatBot extends RAG
{
    ...

    protected function postProcessors(): array
    {
        return [
            new FixedThresholdPostProcessor(
                threshold: 0.5
            ),
        ];
    }
}
```

### 自适应阈值

它实现了一种使用中位数和 MAD（中位数绝对偏差）的动态阈值算法。它会自动适应分数分布，因此对异常值具有鲁棒性。

你可以配置一个乘数参数来控制过滤的严格程度。

推荐的乘数值：

* \[0.2 到 0.4] 高精度模式。适用于更有针对性的结果，文档更少但相关性更高。
* \[0.5 到 0.7] 平衡模式。推荐用于一般用例。
* \[0.8 到 1.0] 高召回模式。适用于更包容的结果，优先保证覆盖面。
* \>1.0 不推荐，因为它往往会包含几乎所有文档。

此组件非常适合通过动态过滤来清理 RAG 结果，使其适应当前结果集的分数分布。

```php
namespace App\Neuron;

use NeuronAI\RAG\PostProcessor\AdaptiveThresholdPostProcessor;

class MyChatBot extends RAG
{
    ...

    protected function postProcessors(): array
    {
        return [
            new AdaptiveThresholdPostProcessor(
                multiplier: 0.6
            ),
        ];
    }
}
```

### LocalAI 重排序器

[LocalAI](https://localai.io/) 是一个一体化的完整 AI 技术栈。你可以在本地硬件上运行大型语言模型。它为 LLM 提供与 OpenAI 兼容的 API，因此你可以将其与 [OpenAILike](/neuron-v3-zh/ti-gong-fang/ai-provider.md#openailike) 提供商一起使用。

```php
namespace App\Neuron;

use NeuronAI\RAG\PostProcessor\LocalAIPostProcessor;

class MyChatBot extends RAG
{
    ...

    protected function postProcessors(): array
    {
        return [
            new LocalAIPostProcessor(
                key: 'LOCALAI_KEY',
                model: 'LOCALAI_MODEL',
                topN: 3,
                host: 'LOCALAI_HOST' // 默认值为 "https://localhost:8080"
            ),
        ];
    }
}
```

## 监控

Neuron 内置的可观测性功能会自动追踪每个后处理器的执行，因此你将能够监控与你账户中的外部服务交互。 [Inspector](https://inspector.dev/) 了解更多，请参阅 [监控部分](/neuron-v3-zh/agent/observability.md).

<figure><img src="/files/34245f217b808d66cb4d57bf22aa2191517b0048" alt=""><figcaption></figcaption></figure>

## 扩展框架

借助 Neuron，你只需通过扩展 `\NeuronAI\PostProcessor\PostProcessorInterface`:

```php
namespace NeuronAI\RAG\PostProcessor;

use NeuronAI\Chat\Messages\Message;
use NeuronAI\RAG\Document;

interface PostProcessorInterface
{
    /**
     * 处理一个文档数组并返回处理后的文档。
     *
     * @param Message $question 处理文档所依据的问题。
     * @param array<Document> $documents 需要处理的文档。
     * @return array<Document> 处理后的文档。
     */
    public function process(Message $question, array $documents): array;
}
```

实现 `process` 方法后，你可以对文档列表执行操作并返回新的列表。Neuron 将按它们在 `postProcessors()` 方法时。

下面是一个实际示例：

```php
namespace App\Neuron\PostProcessors;

use NeuronAI\Chat\Messages\Message;
use NeuronAI\RAG\PostProcessor\PostProcessorInterface;

// 实现你的自定义组件
class CutOffPostProcessor implements PostProcessorInterface
{
    public function __constructor(protected int $level) {}

    public function process(Message $question, array $documents): array
    {
        /*
         * 对向量存储返回的分数应用截断
         */
         
        return $documents;
    }
}
```
