使用 OllamaSharp 在 .NET 中实现简单的 RAG

本文将带你一步步使用 OllamaSharpCommunityToolkit.VectorData.InMemory 在 .NET 10 中构建一个简单的 RAG 系统。我们将从 NuGet 包安装开始,到向量存储、相似度搜索,最终实现一个基于本地 Ollama 模型的智能问答系统。


目录

  1. 环境准备与 NuGet 包安装
  2. 定义数据模型 CloudData
  3. Step 1:为知识库生成嵌入向量
  4. Step 2:将嵌入向量存储到向量库
  5. Step 3:对用户查询生成嵌入向量
  6. Step 4:向量相似度搜索
  7. 完整代码清单

1. 环境准备与 NuGet 包安装

1.1 前置条件

组件 版本要求
.NET SDK 10.0 或更高
Ollama 本地安装并运行(默认端口 11434)
嵌入模型 qwen3-embedding:0.6b(通过 ollama pull 下载)

1.2 安装 Ollama

前往 ollama.com 下载并安装 Ollama,然后拉取嵌入模型:

ollama pull qwen3-embedding:0.6b

1.3 创建项目并安装 NuGet 包

本项目需要安装以下 4 个 NuGet 包

# OllamaSharp — Ollama 的 .NET 客户端库,支持聊天、嵌入、模型管理等
dotnet add package OllamaSharp --version 5.4.30

# CommunityToolkit.VectorData.InMemory — 内存向量数据库,用于存储和检索嵌入向量
dotnet add package CommunityToolkit.VectorData.InMemory --version 1.0.0

# Microsoft.Extensions.Configuration.Json — 读取 JSON 配置文件
dotnet add package Microsoft.Extensions.Configuration.Json --version 10.0.8

# Microsoft.Agents.AI.OpenAI — 提供 AI 抽象层(VectorStore 等基础接口)
dotnet add package Microsoft.Agents.AI.OpenAI --version 1.9.0

1.4 各包的作用说明

NuGet 包 作用
OllamaSharp 与本地 Ollama 服务通信,调用嵌入模型和聊天模型
CommunityToolkit.VectorData.InMemory 提供内存中的向量存储,支持相似度搜索
Microsoft.Extensions.Configuration.Json llama.json 读取模型配置
Microsoft.Agents.AI.OpenAI 提供 VectorStoreEmbeddingGenerator 等统一接口

安装后的 .csproj 文件如下:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="CommunityToolkit.VectorData.InMemory" Version="1.0.0" />
    <PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.9.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.8" />
    <PackageReference Include="OllamaSharp" Version="5.4.30" />
  </ItemGroup>

  <ItemGroup>
    <None Update="llama.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

2. 定义数据模型 CloudData

向量存储中的每条记录需要是一个带有特定特性的类。创建 Models/CloudData.cs

using Microsoft.Extensions.VectorData;

namespace study_RAG.Models
{
    internal class CloudData
    {
        // 主键 — 向量存储中每条记录的唯一标识
        [VectorStoreKey]
        public int Key { get; set; }

        // 数据字段 — 会被存储但不参与向量计算
        [VectorStoreData]
        public string Name { get; set; }

        [VectorStoreData]
        public string Description { get; set; }

        // 向量字段 — 存储嵌入向量
        // dimensions: 384 表示向量维度,必须与嵌入模型输出维度一致
        // DistanceFunction.CosineSimilarity 表示使用余弦相似度计算距离
        [VectorStoreVector(
            dimensions: 384,
            DistanceFunction = DistanceFunction.CosineSimilarity)]
        public ReadOnlyMemory<float> Vector { get; set; }
    }
}

关键特性说明

特性 作用
[VectorStoreKey] 标记主键字段,用于唯一标识记录
[VectorStoreData] 标记数据字段,存储在向量库中但不参与向量计算
[VectorStoreVector] 标记向量字段,dimensions 必须与嵌入模型维度匹配
DistanceFunction.CosineSimilarity 使用余弦相似度(值越接近 1 越相似)

3. Step 1:为知识库生成嵌入向量

嵌入(Embedding) 是将文本转换为高维浮点数向量的过程。语义相近的文本在向量空间中距离更近。

// 从配置读取模型 ID
var modelId = configuration["ollama_model_id"]!.ToString();

// 创建 Ollama 客户端
var ollamaClient = new OllamaApiClient(
    new Uri("http://localhost:11434"), modelId);

// 准备知识库数据
List<CloudData> cloudDatas = new()
{
    new() {
        Key = 0,
        Name = "Azure App Service",
        Description = "Host .NET, Java, Node.js, and Python web applications..."
    },
    new() {
        Key = 1,
        Name = "Azure Service Bus",
        Description = "A fully managed enterprise message broker..."
    },
    new() {
        Key = 2,
        Name = "Azure Blob Storage",
        Description = "Azure Blob Storage allows your applications to store and retrieve files in the cloud..."
    },
    // ... 更多服务
};

// 提取所有描述文本
var descriptions = cloudDatas.ConvertAll(cs => cs.Description);

// 调用 Ollama 嵌入 API 生成向量
var embResponse = await ollamaClient.EmbedAsync(new EmbedRequest
{
    Dimensions = 384,        // 向量维度
    Input = descriptions,    // 要嵌入的文本列表
    Model = modelId          // 使用的嵌入模型
});

关键参数

  • Dimensions = 384:指定输出向量的维度,必须与CloudData中定义的向量维度一致。
  • Input:可以一次传入多个文本,Ollama 会批量处理,返回对应的嵌入向量数组。

4. Step 2:将嵌入向量存储到向量库

使用 InMemoryVectorStore 将生成的向量持久化到内存集合中:

// 创建内存向量存储
var vectorStore = new InMemoryVectorStore();

// 获取/创建名为 "cloudDatas" 的集合,键类型为 int,记录类型为 CloudData
var collection = vectorStore.GetCollection<int, CloudData>("cloudDatas");

// 确保集合存在(首次调用时创建)
await collection.EnsureCollectionExistsAsync();

// 将原始数据与嵌入向量组合,构建完整的记录列表
var records = new List<CloudData>();
for (int i = 0; i < cloudDatas.Count; i++)
{
    records.Add(new CloudData
    {
        Key = cloudDatas[i].Key,
        Name = cloudDatas[i].Name,
        Description = cloudDatas[i].Description,
        Vector = embResponse.Embeddings[i]  // 对应第 i 条描述的嵌入向量
    });
}

// 批量插入/更新到向量库
await collection.UpsertAsync(records);

核心 API 说明

API 作用
InMemoryVectorStore() 创建内存中的向量数据库(进程退出后数据丢失)
GetCollection<TKey, TRecord>("name") 按名称和类型获取集合
EnsureCollectionExistsAsync() 确保集合存在,不存在则创建
UpsertAsync(records) 批量插入或更新记录

5. Step 3:对用户查询生成嵌入向量

将用户的问题也转换为向量,以便在向量空间中进行相似度比较:

// 用户查询
string query = "我应该使用哪个服务来存储word文档?";

// 对查询文本生成嵌入向量
var queryEmbResponse = await ollamaClient.EmbedAsync(new EmbedRequest
{
    Dimensions = 384,
    Input = new List<string> { query },
    Model = modelId
});

// queryEmbResponse.Embeddings[0] 就是查询的向量表示

重要:查询和知识库必须使用同一个嵌入模型,否则向量空间不一致,搜索结果无意义。


6. Step 4:向量相似度搜索

在向量库中搜索与查询向量最相似的记录:

// 搜索最相似的 top 2 条记录
var searchResults = collection.SearchAsync(
    queryEmbResponse.Embeddings[0],  // 查询向量
    top: 3                           // 返回前 3 个最相似的结果
);

// 遍历搜索结果
await foreach (var result in searchResults)
{
    Console.WriteLine($"Name: {result.Record.Name}");
    Console.WriteLine($"Description: {result.Record.Description}");
    Console.WriteLine($"Vector match score: {result.Score}");
    Console.WriteLine();
}

搜索结果说明

属性 含义
result.Record 匹配到的完整记录(包含 Name、Description 等)
result.Score 相似度分数(余弦相似度,范围 -1 到 1,越接近 1 越相似)

7. 完整代码清单

以下是 Program.cs 的完整实现:

using CommunityToolkit.VectorData.InMemory;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.VectorData;
using OllamaSharp;
using OllamaSharp.Models;
using OllamaSharp.Models.Chat;
using System.Text;

// ========== 配置初始化 ==========
var configuration = new ConfigurationBuilder()
    .AddJsonFile("llama.json", false, false)
    .Build();

// ========== 知识库数据 ==========
List<CloudData> cloudDatas =
[
    new() {
        Key = 0,
        Name = "Azure App Service",
        Description = "Host .NET, Java, Node.js, and Python web applications and APIs in a fully managed Azure service."
    },
    new() {
        Key = 1,
        Name = "Azure Service Bus",
        Description = "A fully managed enterprise message broker supporting both point to point and publish-subscribe integrations."
    },
    new() {
        Key = 2,
        Name = "Azure Blob Storage",
        Description = "Azure Blob Storage allows your applications to store and retrieve files in the cloud."
    },
    new() {
        Key = 3,
        Name = "Microsoft Entra ID",
        Description = "Manage user identities and control access to your apps, data, and resources."
    },
    new() {
        Key = 4,
        Name = "Azure Key Vault",
        Description = "Store and access application secrets like connection strings and API keys in an encrypted vault."
    },
    new() {
        Key = 5,
        Name = "Azure AI Search",
        Description = "Information retrieval at scale for traditional and conversational search applications."
    }
];

// ========== Ollama 客户端初始化 ==========
var modelId = configuration["ollama_model_id"]!.ToString();
var ollamaClient = new OllamaApiClient(new Uri("http://localhost:11434"), modelId);

// ========== Step 1: 为知识库生成嵌入向量 ==========
var descriptions = cloudDatas.ConvertAll(cs => cs.Description);
var embResponse = await ollamaClient.EmbedAsync(new EmbedRequest
{
    Dimensions = 384,
    Input = descriptions,
    Model = modelId
});

// ========== Step 2: 存储到向量库 ==========
var vectorStore = new InMemoryVectorStore();
var collection = vectorStore.GetCollection<int, CloudData>("cloudDatas");
await collection.EnsureCollectionExistsAsync(); //在向量库中创建集合

var records = new List<CloudData>();
for (int i = 0; i < cloudDatas.Count; i++)
{
    records.Add(new CloudData
    {
        Key = cloudDatas[i].Key,
        Name = cloudDatas[i].Name,
        Description = cloudDatas[i].Description,
        Vector = embResponse.Embeddings[i]
    });
}
await collection.UpsertAsync(records);

// ========== Step 3: 对查询生成嵌入向量 ==========
string query = "我应该使用哪个服务来存储word文档?";
var queryEmbResponse = await ollamaClient.EmbedAsync(new EmbedRequest
{
    Dimensions = 384,
    Input = new List<string> { query },
    Model = modelId
});

// ========== Step 4: 相似度搜索 ==========
var context = new StringBuilder();
context.AppendLine("匹配的 Azure services:");
Console.WriteLine($"=============匹配知识库 START==============");
await foreach (var result in searchResults)
{
    context.AppendLine($"- {result.Record.Name}: {result.Record.Description}");
    Console.WriteLine($"Name: {result.Record.Name}----Score:{result.Score}");
}
Console.WriteLine($"=============匹配知识库 END==============\n\n");

Console.WriteLine($"根据匹配到的信息构建查询\n\n");




// 构建带上下文的 Prompt
var prompt = $"""
请根据以下参考资料回答用户问题:

{context}

用户问题:{query}
回答:
""";

// 调用聊天模型生成回答
var chatResponse = ollamaClient.ChatAsync(new ChatRequest
{
    Model = "qwen3.5:2b",  // 聊天模型
    Messages = new List<Message>
    {
        new(OllamaSharp.Models.Chat.ChatRole.User, prompt)
    }
});

await foreach (var res in chatResponse)
{
    if (!string.IsNullOrWhiteSpace(res.Message.Content))
    {
        Console.Write(res.Message.Content);
    }
    else if(!string.IsNullOrWhiteSpace(res.Message.Thinking))
    {
        Console.Write(res.Message.Thinking);
    }
}

:本示例使用内存向量存储,适合学习和原型验证。生产环境请使用支持持久化的向量数据库,并注意嵌入模型维度与 VectorStoreVector 特性中 dimensions 参数的一致性。

posted @ 2026-08-11 13:34  scotly  阅读(0)  评论(0)    收藏  举报