最新n1n v2.0.1 正式上线!企业级大模型接口聚合平台 (LLM API Gateway),为您接入 500+ AI Models,价格低至 1 折, 立即尝试

使用 .NET 10 和 AWS Bedrock 构建高可用 RAG 系统

作者
  • avatar
    姓名
    Nino
    职业
    Senior Tech Editor

大型语言模型(LLM)如 Claude 3.5 Sonnet 虽然具有强大的推理能力,但在企业应用中面临两大瓶颈:知识截止日期限制以及无法访问私有数据。传统的微调(Fine-tuning)方式成本高昂且更新缓慢。目前,**检索增强生成(RAG)**已成为解决这一问题的行业标准模式。RAG 将 LLM 视为按需推理引擎而非静态数据库:系统首先从向量库中检索相关文档片段,将其作为上下文注入 Prompt,从而让模型基于事实进行回答。

在本指南中,我们将使用最新的 .NET 10、微软的 Microsoft.Extensions.AI 抽象库以及 AWS Bedrock 构建一个端到端的 RAG 服务。对于希望简化多模型集成、追求极速响应的企业,n1n.ai 提供了统一的 API 网关,是替代复杂云端架构的理想选择。

技术栈选型

  1. 运行时:.NET 10 Minimal APIs。
  2. AI 抽象层:使用 Microsoft.Extensions.AI 实现供应商无关的接口调用。
  3. 向量嵌入:Amazon Titan Text Embeddings G1,生成 1536 维度的稠密向量。
  4. 推理模型:Anthropic Claude 3.5 Sonnet。
  5. 向量存储:OpenSearch(使用 HNSW 算法和余弦相似度)。

为什么选择 .NET 10 与 Microsoft.Extensions.AI?

.NET 10 进一步增强了 AI 生态系统的标准化。通过 IEmbeddingGenerator 和 IChatClient 接口,开发者可以轻松地在 AWS Bedrock、Azure OpenAI 或 n1n.ai 之间切换,而无需重写核心业务逻辑。这种解耦能力对于需要长期维护的企业级项目至关重要。

AWS 环境准备

在 AWS 控制台中(推荐 us-east-1 区域),确保已开通以下模型访问权限:

  • Amazon Titan Text Embeddings G1 - Text
  • Anthropic Claude 3.5 Sonnet

对于本地开发,我们可以通过 Docker 快速启动一个禁用了安全插件的 OpenSearch 实例:

docker run -d -p 9200:9200 -p 9600:9600 \
  -e "discovery.type=single-node" \
  -e "plugins.security.disabled=true" \
  --name opensearch-rag \
  opensearchproject/opensearch:2.11.0

第一步:项目初始化与依赖安装

创建一个新的 Web API 项目并添加必要的 NuGet 包:

dotnet new webapi -n DotnetRagAws
cd DotnetRagAws
dotnet add package Microsoft.Extensions.AI
dotnet add package AWSSDK.BedrockRuntime
dotnet add package OpenSearch.Client

第二步:实现向量生成器

我们需要将 AWS Bedrock 的 Titan 模型封装进 IEmbeddingGenerator。以下是核心实现:

public class BedrockEmbeddingGenerator : IEmbeddingGenerator<string, Embedding<float>>
{
    private readonly IAmazonBedrockRuntime _bedrockClient;

    public BedrockEmbeddingGenerator(IAmazonBedrockRuntime bedrockClient)
    {
        _bedrockClient = bedrockClient;
    }

    public async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(
        IEnumerable<string> values,
        EmbeddingGenerationOptions? options = null,
        CancellationToken ct = default)
    {
        var results = new List<Embedding<float>>();
        foreach (var text in values)
        {
            var body = JsonSerializer.Serialize(new { inputText = text });
            var request = new InvokeModelRequest
            {
                ModelId = "amazon.titan-embed-text-v1",
                ContentType = "application/json",
                Body = new MemoryStream(Encoding.UTF8.GetBytes(body))
            };

            var response = await _bedrockClient.InvokeModelAsync(request, ct);
            using var reader = new StreamReader(response.Body);
            var json = await reader.ReadToEndAsync();
            var output = JsonSerializer.Deserialize<TitanOutput>(json);
            results.Add(new Embedding<float>(output!.Embedding));
        }
        return new GeneratedEmbeddings<Embedding<float>>(results);
    }

    private record TitanOutput([property: JsonPropertyName("embedding")] float[] Embedding);
    public void Dispose() { }
    public object? GetService(Type t, object? k) => null;
    public EmbeddingGeneratorMetadata Metadata => new("BedrockTitan");
}

第三步:配置向量索引

在 OpenSearch 中,我们需要创建一个支持 k-NN 搜索的索引。1536 维是 Titan 模型的标准输出维度。使用余弦相似度(Cosine Similarity)能更好地处理文本语义的相似性。

public async Task EnsureIndexExists(IOpenSearchClient client)
{
    var settings = new IndexSettings();
    settings.Add("index.knn", true);

    await client.Indices.CreateAsync("knowledge-base", c => c
        .Settings(s => s.Setting("index.knn", true))
        .Map<DocumentChunk>(m => m
            .Properties(p => p
                .KnnVector(v => v
                    .Name(n => n.Embedding)
                    .Dimension(1536)
                    .Method(mi => mi
                        .Name("hnsw")
                        .Engine("nmslib")
                        .SpaceType("cosinesimil")))
            )
        )
    );
}

第四步:构建 RAG 编排服务

编排服务负责连接检索和生成两个环节。我们通过系统提示词(System Prompt)建立“护栏”,防止模型产生幻觉。

public class RagService
{
    private readonly IEmbeddingGenerator<string, Embedding<float>> _generator;
    private readonly IChatClient _chatClient;
    private readonly OpenSearchVectorStore _store;

    public async Task<string> QueryAsync(string userQuestion)
    {
        // 1. 将用户问题向量化
        var embedding = await _generator.GenerateAsync(new[] { userQuestion });
        
        // 2. 检索最相关的 3个文档片段
        var chunks = await _store.SearchAsync(embedding.First().Vector.ToArray(), k: 3);

        // 3. 构建上下文增强的 Prompt
        var contextText = string.Join("\n---\n", chunks.Select(c => c.Content));
        var systemMessage = "你是一个严谨的技术助手。请仅根据提供的上下文回答问题。如果无法从上下文中找到答案,请诚实说明。";
        var userMessage = $"上下文:\n{contextText}\n\n问题:{userQuestion}";

        // 4. 调用 Claude 3.5 Sonnet 生成回答
        var response = await _chatClient.GetResponseAsync(new ChatMessage[] {
            new(ChatRole.System, systemMessage),
            new(ChatRole.User, userMessage)
        });

        return response.Text;
    }
}

进阶优化策略

  1. 混合搜索(Hybrid Search):纯向量搜索在处理特定术语(如特定的错误代码或 ID)时可能表现不佳。建议结合 OpenSearch 的 BM25 关键词搜索和 k-NN 搜索,通过倒排索引提高召回率。
  2. 弹性与重试机制:云端 API 可能会遇到频率限制(Throttling)。使用 n1n.ai 提供的 API 可以获得更稳定的并发支持,或者在 .NET 中使用 Polly 实现指数退避重试。
  3. 文档分块(Chunking)优化:简单的字符计数分块会破坏语义。建议使用滑动窗口(Sliding Window)模式,例如每块 500 字符,重叠 50 字符,以保持上下文连贯性。

总结

通过 .NET 10 的标准化接口,开发者可以构建出既灵活又强大的 AI 应用。AWS Bedrock 提供了顶级的模型能力,而 OpenSearch 则充当了可靠的长期记忆库。如果您希望在生产环境中进一步提升多模型调度的效率,n1n.ai 是一个非常值得尝试的高性能聚合平台。

立即在 n1n.ai 获取免费 API 密钥。