EasyCore.Agent.RAG 8.3.0

dotnet add package EasyCore.Agent.RAG --version 8.3.0
                    
NuGet\Install-Package EasyCore.Agent.RAG -Version 8.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EasyCore.Agent.RAG" Version="8.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EasyCore.Agent.RAG" Version="8.3.0" />
                    
Directory.Packages.props
<PackageReference Include="EasyCore.Agent.RAG" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EasyCore.Agent.RAG --version 8.3.0
                    
#r "nuget: EasyCore.Agent.RAG, 8.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EasyCore.Agent.RAG@8.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EasyCore.Agent.RAG&version=8.3.0
                    
Install as a Cake Addin
#tool nuget:?package=EasyCore.Agent.RAG&version=8.3.0
                    
Install as a Cake Tool

🚀 EasyCore.Agent.RAG

EasyCore.Agent.RAG 是 EasyCore.Agent 生态中的 RAG(检索增强生成)工具库,提供文档切块、代码切块、Query Rewrite、Multi Query、MMR 去重等检索链路能力,可与任意向量存储后端(Redis、Qdrant、Milvus、PostgreSQL、Elasticsearch)组合使用。
A RAG utility library for .NET with document chunking, code chunking, query rewriting, multi-query generation, and MMR selection.

.NET C# RAG Agent


🌍 Language


📚 目录


1. 项目简介

🎯 解决什么问题?

企业知识库问答(RAG)通常包含多个独立步骤:

  • 长文档与源代码仓库需要切块后再 Embedding;
  • 用户多轮对话中的指代、省略需要 Query Rewrite;
  • 单次检索召回不足时需要 Multi Query 扩展;
  • 向量 Top-K 结果高度重复时需要 MMR 提升多样性。

若在每个业务项目中重复实现上述逻辑,成本高且难以统一调优。

EasyCore.Agent.RAG 将这些能力封装为轻量、无状态的静态工具类,与 EasyCore.Agent(Agent / Embedding)和 EasyCore.Vector.*(向量存储)解耦,可按需组合。

📦 在项目中的位置

EasyCore.Agent(Agent SDK / Embedding / 会话上下文)
    └── EasyCore.Agent.RAG(本文档:切块 / Rewrite / Multi Query / MMR)
            └── EasyCore.Vector.*(向量入库与检索)
                    ├── EasyCore.Vector.Redis
                    ├── EasyCore.Vector.Qdrant
                    ├── EasyCore.Vector.Milvus
                    ├── EasyCore.Vector.PostgreSQL
                    └── EasyCore.Vector.Elasticsearch

本库不绑定具体向量数据库,也不强制 DI 注册;引用 NuGet 包或项目后直接调用静态方法即可。


2. 架构图

2.1 RAG 链路总览

2-1-rag-链路总览

2.2 各模块职责

模块 类型 是否依赖 LLM 说明
DocumentChunker 静态工具 固定窗口 + 重叠切块(文档)
CodeChunker 静态工具 按行分块 + 重叠切块(源代码)
QueryRewrite 静态工具 结合会话历史改写检索 Query
MultiQueryGenerator 静态工具 从一个问题生成多条检索 Query
MmrSelector 静态工具 在相关性与多样性间做 MMR 平衡

2.3 Query Rewrite 时序

2-3-query-rewrite-时序


3. 核心特性

  • 📄 DocumentChunker:按字符窗口切块,支持可配置 chunkSizeoverlapSize,保留 StartIndex / EndIndex 便于溯源。
  • 💻 CodeChunker:按行对源代码分块,优先在空行处切分,支持语言识别、1-based 行号、稳定 SHA256 Id 与结构化 EmbeddingText
  • 🔄 QueryRewrite:利用 AIAgent 将会话中的模糊问题改写为独立、可检索的 Query;自动检测语言并与用户问题保持一致。
  • 🔀 MultiQueryGenerator:从一个用户问题生成 N 条不同角度的检索 Query,提升召回覆盖率。
  • 🎯 MmrSelector:Maximum Marginal Relevance 算法,在保持相关性的同时降低结果重复度。
  • 🧩 Prompt 可扩展QueryRewritePromptBuilderMultiQueryPromptBuilder 暴露 System / User Prompt 构建方法,便于业务定制。
  • 同步 / 异步QueryRewriteMultiQueryGenerator 均提供同步与异步 API。
  • 🔌 零配置接入:无 ServiceCollection 扩展,引用程序集即可使用。

4. 环境要求

4.1 .NET 版本

  • .NET 8.0 及以上

4.2 NuGet 依赖

用途
Microsoft.Agents.AI AIAgentChatMessage 等 Agent 运行时
Microsoft.Agents.AI.OpenAI OpenAI 兼容模型接入(通过 EasyCore.Agent 间接使用)

4.3 配合使用的组件

组件 用途
EasyCore.Agent 创建 AIAgent、Embedding、会话上下文
EasyCore.Vector.* 向量入库与相似度检索

5. 快速开始

5.1 安装包

dotnet add package EasyCore.Agent.RAG

5.2 文档切块

using EasyCore.Agent.RAG;

var content = File.ReadAllText("manual.md");

var chunks = DocumentChunker.Chunk(
    content: content,
    documentId: "manual-001",
    chunkSize: 800,
    overlapSize: 100);

foreach (var chunk in chunks)
{
    Console.WriteLine($"[{chunk.Index}] {chunk.StartIndex}-{chunk.EndIndex}: {chunk.Content[..Math.Min(50, chunk.Content.Length)]}...");
}

5.3 代码切块

using EasyCore.Agent.RAG;

var sourceCode = await File.ReadAllTextAsync("Services/AuthService.cs");

var chunks = CodeChunker.Chunk(
    content: sourceCode,
    documentId: "repo-auth",
    filePath: "Services/AuthService.cs",
    chunkSize: 3000,
    overlapSize: 300);

foreach (var chunk in chunks)
{
    Console.WriteLine($"[{chunk.Index}] {chunk.Language} L{chunk.StartLine}-{chunk.EndLine}");
    Console.WriteLine(chunk.EmbeddingText);
}

5.4 Query Rewrite

using EasyCore.Agent.RAG;
using Microsoft.Extensions.AI;

// 假设已通过 EasyCore.Agent 创建 agent,并有多轮会话 history
var history = agentClient.GetChatContext(sessionId);

var rewritten = await QueryRewrite.RewriteAsync(
    query: "它支持哪些功能?",
    agent: agent,
    history: history);

// 可能输出:"EasyCore.Agent 支持哪些功能?"

5.5 Multi Query

var queries = await MultiQueryGenerator.GenerateAsync(
    query: "如何申请年假?",
    agent: agent,
    count: 3);

// 可能输出:
// - 如何申请年假?
// - 年假申请流程是什么?
// - 员工休假制度有哪些规定?

5.6 MMR 去重

var candidates = searchResults.Select(x => new MmrCandidate
{
    Id = x.Record.Id,
    Content = x.Record.Content,
    Score = x.Score,
    Vector = x.Record.GetVector("contentVector")
}).ToList();

var diversified = MmrSelector.Select(
    candidates: candidates,
    topK: 3,
    lambda: 0.7);

6. 模块说明

6.1 DocumentChunker

成员 说明
Chunk(content, documentId, chunkSize, overlapSize) 将文本切分为 List<DocumentChunk>

参数约束:

参数 默认值 约束
chunkSize 800 必须 > 0
overlapSize 100 必须 ≥ 0 且 < chunkSize

行为说明:

  • 自动归一化换行符(\r\n\n)并 Trim;
  • 空内容返回空列表;
  • 每个 chunk 自动生成唯一 Id(GUID N 格式);
  • 空白 chunk 会被跳过。

6.2 DocumentChunk

属性 类型 说明
Id string Chunk 唯一标识
DocumentId string 来源文档 ID
Index int 在文档中的序号(从 0 开始)
Content string 切块文本
StartIndex int 在原文中的起始字符位置
EndIndex int 在原文中的结束字符位置

6.3 CodeChunker

成员 说明
Chunk(content, documentId, filePath, language, chunkSize, overlapSize) 将源代码切分为 List<CodeChunk>

参数约束:

参数 默认值 约束
chunkSize 3000 必须 > 0
overlapSize 300 必须 ≥ 0 且 < chunkSize
filePath null 可选,用于语言识别与元数据
language null 可选,显式指定语言时覆盖扩展名推断

行为说明:

  • \n 拆行分块,优先在空行处切分,单行超长时硬切;
  • 自动归一化换行符(\r\n\n)并 Trim;
  • 根据文件扩展名或 language 识别编程语言(如 .csC#);
  • 每个 chunk 生成稳定 SHA256 Id(非 GUID);
  • 自动生成 EmbeddingText(含 Language、File、SymbolType、行号与代码正文);
  • SymbolType 固定为 CodeBlockSummary / Keywords 预留为空;
  • 不依赖 LLM、Roslyn 或 Tree-sitter。

6.4 CodeChunk

属性 类型 说明
Id string 稳定唯一标识(SHA256)
DocumentId string 来源文档 ID
Index int 在文件中的序号(从 0 开始)
Content string 代码块原文
StartIndex int 在归一化文本中的起始字符位置
EndIndex int 在归一化文本中的结束字符位置
StartLine int 1-based 起始行号
EndLine int 1-based 结束行号
Language string 编程语言
FilePath string 文件路径
SymbolType string 固定为 CodeBlock
Summary string 摘要(预留,当前为空)
Keywords string 关键词(预留,当前为空)
EmbeddingText string 用于向量化的结构化文本

6.5 QueryRewrite

方法 说明
RewriteAsync(query, agent, history, cancellationToken) 异步改写
Rewrite(query, agent, history) 同步改写

降级策略: 若 LLM 返回空文本,则原样返回用户 query

Prompt 规则(摘要):

  1. 检测用户最新问题的语言;
  2. 改写为独立、清晰、适合检索的 Query;
  3. 保持与原问题相同语言;
  4. 不回答问题、不解释、不臆造历史中不存在的信息;
  5. 若问题已足够清晰则原样返回;
  6. 仅输出纯文本 Query。

6.6 MultiQueryGenerator

方法 说明
GenerateAsync(query, agent, count, cancellationToken) 异步生成多条 Query
Generate(query, agent, count) 同步生成

输出解析:

  • 按行拆分 LLM 输出;
  • 自动去除 1. 1、- 等序号前缀;
  • 去重(大小写不敏感);
  • 若结果中不包含原问题,则将其插入首位;
  • 最终返回不超过 count 条。

6.7 MmrSelector

方法 说明
Select(candidates, topK, lambda) MMR 选取 Top-K

算法:

MMR = λ × relevanceScore − (1 − λ) × maxSimilarity(selected)
  • relevanceScore:向量检索原始 Score;
  • maxSimilarity:候选与已选集合的最大余弦相似度;
  • lambda:默认 0.7,越大越偏向相关性,越小越偏向多样性。

过滤规则: 无向量(Vector.Length == 0)的候选会被排除。

6.8 MmrCandidate

属性 类型 说明
Id string 候选 ID
Content string 文本内容
Score float 原始相关性分数
Vector float[] 用于多样性计算的向量

7. API 使用示例

7.1 入库:切块 + Embedding + 向量写入

using EasyCore.Agent.RAG;
using EasyCore.Vector.Redis;

const string collectionName = "knowledge_base";
const string vectorField = "contentVector";

var chunks = DocumentChunker.Chunk(documentText, documentId, 800, 100);

foreach (var chunk in chunks)
{
    var embedding = await agentClient.EmbedAsync(chunk.Content);

    var record = new RedisTextVector
    {
        Id = chunk.Id,
        DocumentId = chunk.DocumentId,
        Index = chunk.Index,
        StartIndex = chunk.StartIndex,
        EndIndex = chunk.EndIndex,
        Content = chunk.Content
    };

    record.SetVector(vectorField, embedding);
    await vectorStore.UpsertAsync(collectionName, record);
}

7.2 代码入库:切块 + Embedding + 向量写入

using EasyCore.Agent.RAG;
using EasyCore.Vector.Redis;

const string collectionName = "code_base";
const string vectorField = "contentVector";

var sourceCode = await File.ReadAllTextAsync("Services/AuthService.cs");
var chunks = CodeChunker.Chunk(sourceCode, "repo-auth", "Services/AuthService.cs", chunkSize: 3000, overlapSize: 300);

foreach (var chunk in chunks)
{
    var embedding = await agentClient.EmbedAsync(chunk.EmbeddingText);

    var record = new RedisTextVector
    {
        Id = chunk.Id,
        DocumentId = chunk.DocumentId,
        Index = chunk.Index,
        StartIndex = chunk.StartIndex,
        EndIndex = chunk.EndIndex,
        Content = chunk.Content
    };

    record.SetVector(vectorField, embedding);
    await vectorStore.UpsertAsync(collectionName, record);
}
var history = deepSeekAgent.GetChatContext(sessionId);
var standaloneQuery = await QueryRewrite.RewriteAsync(userMessage, agent, history);

var queryVector = await agentClient.EmbedAsync(standaloneQuery);

var results = await vectorStore.VectorSearchAsync<RedisTextVector>(
    collectionName,
    vectorField,
    queryVector,
    new RedisVectorSearchOptions
    {
        Limit = 10,
        ScoreThreshold = 0.75f
    });

7.4 Multi Query 多路检索

var queries = await MultiQueryGenerator.GenerateAsync(userMessage, agent, count: 5);

var merged = new Dictionary<string, RedisVectorSearchResult<RedisTextVector>>();

foreach (var q in queries)
{
    var vector = await agentClient.EmbedAsync(q);
    var hits = await vectorStore.VectorSearchAsync<RedisTextVector>(
        collectionName, vectorField, vector,
        new RedisVectorSearchOptions { Limit = 5 });

    foreach (var hit in hits)
    {
        if (!merged.ContainsKey(hit.Record.Id) || merged[hit.Record.Id].Score < hit.Score)
            merged[hit.Record.Id] = hit;
    }
}

var topResults = merged.Values.OrderByDescending(x => x.Score).Take(10).ToList();

7.5 MMR + Agent 回答

var mmrCandidates = topResults.Select(x => new MmrCandidate
{
    Id = x.Record.Id,
    Content = x.Record.Content,
    Score = x.Score,
    Vector = x.Record.GetVector(vectorField)
}).ToList();

var contextChunks = MmrSelector.Select(mmrCandidates, topK: 3, lambda: 0.7);

var context = string.Join("\n\n", contextChunks.Select(c => c.Content));

var answer = await agentClient.ChatRunAsync(
    sessionId,
    agent,
    $"参考以下资料回答问题:\n\n{context}\n\n问题:{userMessage}");

7.6 自定义 Prompt(QueryRewrite)

// 直接使用 PromptBuilder 构建消息,再自行调用 Agent
var messages = QueryRewritePromptBuilder.Build(query, history);

// 或替换 System Prompt
var customSystem = QueryRewritePromptBuilder.GetSystemPrompt();
// 基于 customSystem 自行组装 messages...

7.7 自定义 Prompt(MultiQuery)

var messages = MultiQueryPromptBuilder.Build(query, count: 5);

var systemPrompt = MultiQueryPromptBuilder.BuildSystemPrompt(count: 5);
var userPrompt = MultiQueryPromptBuilder.BuildUserPrompt(query, count: 5);

8. 完整 RAG 流水线

8-完整-rag-流水线

推荐组合:

场景 建议启用的模块
单轮 FAQ DocumentChunker + VectorSearch
代码库问答 CodeChunker + VectorSearch
多轮对话知识库 + QueryRewrite
召回率不足 + MultiQueryGenerator
结果重复度高 + MmrSelector
高精度要求 + 外部 Reranker(业务自行接入)

9. 最佳实践

  • chunkSize 与 Embedding 模型匹配:中文建议 500~1000 字符,英文可按 token 估算;overlapSize 通常取 chunkSize 的 10%~20%。
  • 代码入库优先使用 EmbeddingTextCodeChunker 已内置 Language、FilePath、行号等元数据,Embedding 时比裸 Content 更利于检索。
  • Rewrite 前先积累会话历史:通过 EasyCore.AgentGetChatContext(sessionId) 获取完整 ChatMessage 列表。
  • Multi Query 后做结果合并去重:按 Record.Id 保留最高分,避免重复 chunk 进入上下文。
  • MMR 需要向量数据:检索时设置 IncludeVector = true,或将向量一并映射到 MmrCandidate.Vector
  • lambda 调参:知识库重复内容多时可降至 0.5~0.6;追求精确匹配时可提高至 0.8~0.9
  • ScoreThreshold 与 MMR 配合:先用向量库阈值过滤低分结果,再 MMR 精选。
  • ⚠️ QueryRewrite / MultiQuery 依赖 LLM:注意 API 成本与延迟,可对简单问题跳过 Rewrite。
  • ⚠️ DocumentChunker 为字符级切块:不感知 Markdown 标题或段落边界,长文档可考虑先按段落预分割。
  • ⚠️ CodeChunker 为通用行级切块:不按语法树或 class/method 分块;如需符号级粒度请在上层自行扩展。

10. FAQ

❓ Q1:本库是否包含向量存储?

不包含。向量入库与检索请使用 EasyCore.Vector.RedisEasyCore.Vector.Qdrant 等配套包。

❓ Q2:是否必须注册 DI?

不需要。所有 API 均为静态方法,引用程序集后直接调用。

❓ Q3:QueryRewrite 需要什么类型的 Agent?

需要支持 RunAsync(IEnumerable<ChatMessage>)AIAgent,通常由 EasyCore.AgentCreateAgent(...) 创建。

❓ Q4:Rewrite 返回空或异常怎么办?

RewriteAsync 在 LLM 返回空时会降级为原始 query;建议在业务层对异常做 try/catch 并同样降级。

❓ Q5:MMR 选不出足够条数?

若候选本身不足 topK,或大量候选缺少有效向量,返回数量会少于 topK。请确保检索阶段返回足够候选且 IncludeVector = true

❓ Q6:是否支持 Reranker?

当前版本未内置 Cross-Encoder Reranker。可在 MmrSelector 之后自行接入第三方 Rerank 服务。

❓ Q7:Multi Query 生成语言不对?

Prompt 已要求「与用户问题同语言」。若模型仍偏离,可修改 MultiQueryPromptBuilder.BuildSystemPrompt 或在业务层过滤。


11. EasyCore.Agent.RAG 详细介绍

11.1 设计目标

EasyCore.Agent.RAG 聚焦 RAG 检索链路中的可复用算法与 Prompt 封装,而非重复实现 Agent 或向量库能力。设计原则:

  1. 轻量无状态:静态工具类,无全局配置,便于测试与组合;
  2. 与存储解耦:不引用任何 EasyCore.Vector.* 程序集;
  3. 与 Agent 协作:Rewrite / MultiQuery 通过标准 AIAgent 接口调用 LLM;
  4. 企业可扩展:Prompt Builder 公开,允许业务覆盖 System Prompt。

11.2 类型一览

EasyCore.Agent.RAG
├── DocumentChunker/
│   ├── DocumentChunker          # 文档切块
│   └── DocumentChunk            # 文档切块模型
├── CodeChunker/
│   ├── CodeChunker              # 代码切块
│   └── CodeChunk                # 代码切块模型
├── QueryRewrite/
│   ├── QueryRewrite             # Query 改写
│   └── QueryRewritePromptBuilder
├── MultiQueryGenerator/
│   ├── MultiQueryGenerator      # 多 Query 生成
│   └── MultiQueryPromptBuilder
└── MmrSelector/
    ├── MmrSelector              # MMR 选取
    └── MmrCandidate             # MMR 候选模型

11.3 典型落地步骤

  1. 引用 EasyCore.Agent.RAG 与目标 EasyCore.Vector.*
  2. 注册 EasyCore.Agent 与向量库 DI;
  3. 入库:DocumentChunker / CodeChunkerEmbedAsyncUpsertAsync
  4. 检索:QueryRewrite(可选)→ MultiQueryGenerator(可选)→ VectorSearchAsync
  5. 后处理:MmrSelector.Select → 拼接上下文 → ChatRunAsync 生成答案。

12. Demo 运行

AspCoreAgentDemo.EasyCore.Agent.RAG Demo 提供了 RAG 相关 API 示例。

12.1 启动 Demo

dotnet run --project demo/AspCoreAgent/AspCoreAgent.csproj
dotnet run --project demo/Demo.EasyCore.Agent.RAG/Demo.EasyCore.Agent.RAG.csproj

12.2 RAG 相关端点

端点 说明
GET /api/Embedding/RagDocumentChunker 文档切块示例(AspCoreAgent)
GET /api/Rag/chunk?chunkSize=120&overlap=30 文档切块示例(Demo.EasyCore.Agent.RAG)
GET /api/Rag/code-chunk?chunkSize=400&overlap=80 代码切块示例(Demo.EasyCore.Agent.RAG)
GET /api/Embedding/RagQueryRewrite?message=...&sessionId=... Query Rewrite(含多轮上下文)
GET /api/Embedding/RagMultiQueryRetrieval?message=... Multi Query 生成

各向量库 Controller(Redis / Qdrant / Milvus 等)中的 *MmrSelector 端点演示了 向量检索 + MMR 的组合用法。


📄 License

MIT(与 EasyCore.Agent 主仓库保持一致)

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
8.3.0 95 7/21/2026
8.0.4 114 6/20/2026 8.0.4 is deprecated because it is no longer maintained and has critical bugs.