Criando um Assistente de IA com C#, RAG, LLM, Embeddings, Ollama, Chroma DB e .NET
A inteligência artificial se tornou parte...
A inteligência artificial se tornou parte essencial do mercado, ampliando a demanda por aplicações que integrem seus recursos de forma fluida. Neste artigo, vamos mostrar como criar um assistente de IA 100% em C#, utilizando um banco de dados vetorial RAG (Retrieval-Augmented Generation), LLMs locais rodando no Ollama e o Chroma DB para armazenar e consultar embeddings.
Essa abordagem acelera a recuperação de informações relevantes e eleva a qualidade das respostas em cenários como suporte ao cliente, documentação interna e chatbots de atendimento.
Exporando embedding
Um embedding é uma representação numérica de texto em forma de vetor (array de números) transformando palavras e frases para números permitindo busca semântica.
Ao executar o modelo LLM usando o bge-large:335m
para transformar o texto "Principais benefícios da arquitetura de microsserviços" teriamos um vetor de 1024 dimensões, porém abaixo compartilho uma visão resumida com 10 dimensões para compreensão
nesse artigo.
[
0.1324, -0.0578, 0.2981, 0.0452, -0.1137,
0.2519, 0.0783, -0.2034, 0.1897, -0.0641
]
Fundamentos de RAG (Retrieval-Augmented Generation)
O RAG (Retrieval-Augmented Generation), é uma técnica que une busca de informação e geração de texto para enriquecer as respostas de um modelo de linguagem. Em vez de depender apenas do que foi aprendido durante o treinamento do LLM, o sistema pesquisa em um repositório de documentos os trechos mais relevantes para a pergunta feita.
O funcionamento ocorre em duas fases: primeiro, a pergunta é transformada em um vetor numérico (embedding) e comparada com os embeddings dos textos armazenados, recuperando automaticamente os trechos mais similares. Em seguida, esses trechos são incorporados ao prompt original antes de enviá-lo ao modelo de linguagem, que então gera a resposta utilizando tanto seu conhecimento prévio quanto o contexto adicional fornecido. Isso permite obter respostas mais precisas, atualizadas e transparentes, embora exija uma infraestrutura capaz de indexar e buscar embeddings em alta velocidade.
Fluxo da aplicação
Iniciamos lendo um arquivo PDF e dividindo seu conteúdo em blocos de texto, que são então transformados em embeddings por meio de um modelo LLM local, fornecido pelo Ollama. Em seguida, inserimos esses vetores no Chroma DB, criando nossa base de conhecimento vetorial.
Com a base vetorial pronta, passamos ao fluxo de consulta interativo. Primeiro, recebemos uma pergunta do usuário e a convertemos em embeddings pelo mesmo LLM local. Depois, executamos uma busca semântica no Chroma DB para identificar os blocos de texto mais relevantes ao contexto da pergunta.
Por fim, construímos um prompt que mescla o contexto retornado e o enviamos ao LLM para gerar a resposta final. Esse processo aproveita tanto a precisão da pesquisa vetorial quanto a capacidade dos LLMs de entregar respostas coerentes e contextualizadas, tudo dentro do ecossistema .NET e C#.
Resumo dos fluxos
- Extrair texto de documentos PDF.
- Gerar embeddings semânticos localmente com LLM.
- Armazenar e consultar vetores no Chroma DB.
- Orquestrar consultas semânticas no RAG.
- Enviar os resultados do RAG ao LLM formatando a resposta.
Configuração do ambiente
- .NET SDK 9.x+
- Visual Studio Code
- Ollama / bge-large:335m / llama3.2:3b
- ChromaDB
- PdfPig 0.1.10+
- Exemplo completo desse artigo
- Docker
Inicialização do ChromaDB (porta 8000)
Esse projeto utilizará o ChromaDB como banco de dados para o RAG e será executado pelo Docker, caso deseje persistência é preciso passar o parâmetro.
Executando o container do ChromaDB
docker run -d --name chromadb -p 8000:8000 chromadb/chroma
Verificar se o serviço ChromaDB está ativo
curl -I -L http://localhost:8000/docs/
HTTP/1.1 200 OK
content-type: text/html
content-length: 734
date: Tue, 17 Jun 2025 10:13:08 GMT
Inicialização do Ollama (porta 11434)
Este projeto utilizará o Ollama, que deve ser instalado, com o modelo LLM bge-large
para a criação dos embeddings e o LLM llama3.2
para a geração do texto final. Você pode utilizar outros modelos de sua preferência ou, se preferir, usar diretamente a API da OpenAI, por exemplo.
ollama serve
ollama pull bge-large:335m
ollama pull llama3.2:3b
Verificar se o serviço Ollama está ativo
curl -i http://localhost:11434/
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Tue, 17 Jun 2025 10:12:10 GMT
Content-Length: 17
Ollama is running
Iniciando o Projeto
Agora chegou o momento de colocar em prática a implementação de um projeto RAG utilizando C# e um LLM local. Nos artigos anteriores Explorando MCP Server para .NET/C# Developers e Explorando o MCP Client para Desenvolvedores .NET/C# discutimos os fundamentos da abordagem de IA aplicada ao ecossistema .NET, com foco na utilização do MCP, sempre trazendo exemplos práticos e objetivos.
Neste artigo, avançamos para uma construção mais robusta, onde o projeto utiliza exclusivamente o pacote NuGet PdfPig para leitura de arquivos PDF. Todo o restante da solução foi desenvolvido de forma manual, proporcionando uma compreensão aprofundada de cada etapa do processo.
Por isso, recomendo fortemente que você utilize o repositório no GitHub como apoio para acompanhar o código completo, entender cada detalhe da implementação e acelerar seu aprendizado sobre como construir soluções RAG em .NET.
Você pode iniciar totalmente do zero com os seguintes passos:
- Crie um novo projeto console e entre na pasta:
dotnet new console -o dotnet-ai-rag-chromadb-ollama cd dotnet-ai-rag-chromadb-ollama
- Adicione o pacote NuGet PdfPig:
dotnet add package PdfPig --version 0.1.10
Porém, minha recomendação é clonar o repositório completo, acompanhar os procedimentos descritos neste artigo e, se desejar, ir copiando partes específicas para entender e testar o fluxo.
git clone https://github.com/ramonduraes/dotnet-ai-rag-chromadb-ollama.git
Estrutura inicial do projeto RAG
Durante esse artigo eu recomendo efetuar um clone do projeto, pois implemaremos um sistema RAG em C# processa PDFs, gera e armazena embeddings em ChromaDB e utiliza Ollama para consultas contextuais, orquestrando tudo a partir de um menu interativo. A estrutura simpificada separa as classes para facilitar o entendimento, com configurações centralizadas e logging estruturado para fácil manutenção e extensibilidade.
Program.cs É o ponto de entrada da aplicação: inicializa os clientes de ChromaDB e Ollama, estabelece conexões externas, exibe um menu interativo e gerencia erros ao longo do fluxo RAG.
ChromaDB.cs Implementa o cliente para o banco vetorial, criando coleções, armazenando documentos com embeddings e executando consultas por similaridade, além de tratar falhas de comunicação.
Ollama.cs Faz a ponte com a API local do Ollama para gerar embeddings (modelo bge-large:335m) e completions de texto (modelo llama3.2:3b), suportando streaming, timeouts e ajuste de parâmetros de geração.
PdfProcessor.cs Extrai texto de PDFs página a página usando PdfPig, divide em chunks, gera embeddings e envia esses vetores ao ChromaDB para armazenamento.
RagService.cs Orquestra o processo de consulta: converte perguntas em embeddings, busca documentos relevantes no ChromaDB e monta prompts contextuais para gerar respostas baseadas no contexto.
Config.cs Centraliza todas as configurações da aplicação: URLs, nomes de bancos e coleções, modelos de IA, parâmetros de geração e limites de texto.
Log.cs Fornece logging estruturado com suporte a cores ANSI em Unix e compatibilidade Windows, oferecendo níveis de Debug, Warn e Error e formatação com timestamps.
git clone https://github.com/ramonduraes/dotnet-ai-rag-chromadb-ollama.git
Esse é um projeto de exemplo que pode sofrer alterações conforme atualizações das ferramentas, é recomendavel inclusive utilizar componente nugets para ChromaDB, Ollama e/ou SDKs como Semantic Kernel ou Devprime simplificando a quantidade de código envolvida com integrações.
Estrutura principal
A estrutura principal do projeto oferece, como primeira opção, inicializar o ChromaDB e, em seguida, executar ProcessPdfIndexing(pdfProcessor)
para extrair dados de um PDF externo e inseri-los no banco do ChormaDB. A segunda opção, via await ProcessRagQuery(ragService)
, recebe o texto do usuário para consulta RAG, gera um embedding para busca vetorial e retorna uma resposta formatada pelo LLM.
Logo abaixo é possivel acompanhar a tela inicial da aplicação rodando no console
[LOG] ?? Starting RAG Demo Application
[LOG] ?? Connecting to ChromaDB at http://localhost:8000/api/v2
[LOG] Checking if database 'default' exists in tenant 'default'
[LOG] Database 'default' already exists in tenant 'default'
[LOG] Checking if collection 'default' exists in database 'default'
[LOG] ? Connected to collection 'default' (ID=5ff62dfe-ccd9-45c6-8b4d-a7b2b3314d4d)
=== RAG Demo Application ===
1) Index PDF to RAG
2) Query RAG
Enter your choice:
Eplorando o código do Program.cs
//Program.cs
using System;
using System.Threading.Tasks;
try
{
Log.DebugWithTimestamp("?? Starting RAG Demo Application");
// Initialize external service clients
var chromaClient = new ChromaDBClient(Config.ChromaBaseUrl, Config.ChromaTenant, Config.ChromaDatabase);
var ollamaClient = new OllamaClient(Config.OllamaUrl);
// Establish connection to ChromaDB
Log.Debug($"?? Connecting to ChromaDB at {Config.ChromaBaseUrl}");
await chromaClient.EnsureDatabaseAsync();
var collectionId = await chromaClient.EnsureCollectionAsync(Config.ChromaCollection);
Log.Debug($"? Connected to collection '{Config.ChromaCollection}' (ID={collectionId})\n");
// Initialize services
var pdfProcessor = new PdfProcessor(ollamaClient, chromaClient, collectionId);
var ragService = new RagService(chromaClient, ollamaClient, collectionId);
// Main menu loop
bool exitApplication = false;
while (!exitApplication)
{
DisplayMenu();
string choice = Console.ReadLine() ?? string.Empty;
switch (choice.Trim())
{
case "1":
await ProcessPdfIndexing(pdfProcessor);
break;
case "2":
await ProcessRagQuery(ragService);
break;
default:
Log.Debug("?? Exiting application. Goodbye!");
exitApplication = true;
break;
}
if (!exitApplication)
{
Log.Debug("\nPress any key to continue...");
Console.ReadKey();
Console.Clear();
}
}
}
catch (Exception ex)
{
Log.Error($"Fatal application error: {ex.Message}");
Environment.Exit(1);
}
// Local functions
void DisplayMenu()
{
Console.WriteLine("=== RAG Demo Application ===");
Console.WriteLine("1) Index PDF to RAG");
Console.WriteLine("2) Query RAG");
Console.WriteLine("Any other key to exit");
Console.Write("\nEnter your choice: ");
}
async Task ProcessPdfIndexing(PdfProcessor pdfProcessor)
{
try
{
Log.Debug("\n?? PROCESS 1: Processing PDF document...");
await pdfProcessor.ProcessPdfAsync(Config.PdfPath, Config.EmbeddingModel);
Log.Debug("? PDF processing completed");
}
catch (Exception ex)
{
Log.Error($"Error processing PDF: {ex.Message}");
}
}
async Task ProcessRagQuery(RagService ragService)
{
try
{
Log.Debug("\n?? Enter your microservices-related question:");
Console.Write("> ");
string question = Console.ReadLine() ?? string.Empty;
if (string.IsNullOrWhiteSpace(question))
{
Log.Warn("? Empty question, returning to menu");
return;
}
Log.Debug("?? PROCESS 2 & 3: Executing RAG query...");
string response = await ragService.QueryAsync(
question,
Config.DefaultMaxResults,
Config.EmbeddingModel,
Config.GenerationModel
);
Log.Debug($"\n=== RAG Response ===\n{response}");
}
catch (Exception ex)
{
Log.Error($"Error processing query: {ex.Message}");
}
}
Extraindo texto de documentos PDF e adicionando o embeddings no RAG
A implementação para processar a carga do PDF no banco de dados RAG está concentrada no arquivo PdfProcessor.cs
que efetua um varredrua no conteudo do documento, separa por blocos, cria o embedding usando o modelo LLM local 'bge-large:335m' via Ollama
e depois insere no ChromaDB.
[LOG] Starting PDF ingestion process: 'microservices.pdf'
[LOG] Processing page 1/7: generating embedding
[LOG] Generating embedding using model 'bge-large:335m'
[LOG] Successfully generated embedding vector with 1024 dimensions
[LOG] Sending POST request to http://localhost:8000/api/v2/tenants/default/databases/default/collections/5ff62dfe-ccd9-45c6-8b4d-a7b2b3314d4d/add
[LOG] Successfully processed Document page_1
[LOG] Successfully processed and stored page 1/7
Abaixo é possivel conferir um detalhamento do código no metodo ProcessPdfAsync
.
public async Task ProcessPdfAsync(string pdfPath, string embeddingModel = "bge-large:335m")
{
if (!File.Exists(pdfPath))
{
Log.Warn($"PDF file not found: {pdfPath}");
return;
}
Log.Debug($"Starting PDF ingestion process: '{pdfPath}'");
using var doc = PdfDocument.Open(pdfPath);
for (int i = 0; i < doc.NumberOfPages; i++)
{
int page = i + 1;
string text = doc.GetPage(page).Text ?? string.Empty;
text = text[..Math.Min(Config.MaxTextLength, text.Length)]; // limit max characters
if (string.IsNullOrWhiteSpace(text)) continue;
Log.Debug($"Processing page {page}/{doc.NumberOfPages}: generating embedding");
float[] embedding = await _ollamaClient.GenerateEmbeddingAsync(text, embeddingModel);
await _chromaClient.AddDocumentAsync(_collectionId, $"page_{page}", text, embedding);
Log.Debug($"Successfully processed and stored page {page}/{doc.NumberOfPages}");
}
Log.Debug($"Completed PDF ingestion: processed {doc.NumberOfPages} pages total");
}
Efetuando uma Busca no RAG
A consulta em uma base RAG segue um processo semelhante a inclusão, o parâmetro de busca é transformado em um vetor embedding utilizando o modelo LLM. Esse processo é executado pelo método QueryAsync
, disponível na classe RagService
, localizada no arquivo RagService.cs
.
Por exemplo, ao buscar pelo termo "Principais benefícios da arquitetura de microsserviços", o texto é convertido em um embedding vetorial antes de ser comparado com os dados armazenados.
[LOG] ?? PROCESS 2 & 3: Executing RAG query...
[LOG] Processing RAG query: 'Principais benefícios da arquitetura de microsserviços'
[LOG] Generating embedding for question
[LOG] Generating embedding using model 'bge-large:335m'
[LOG] Successfully generated embedding vector with 1024 dimensions
[LOG] Generated embedding with 1024 dimensions: [-0.13619536, 0.6268783, -0.074571535, -0.34813362, -0.51012534...]
[LOG] Searching for relevant context in ChromaDB
[LOG] Sending query request to http://localhost:8000/api/v2/tenants/default/databases/default/collections/5ff62dfe-ccd9-45c6-8b4d-a7b2b3314d4d/query
[LOG] Received response from ChromaDB query
[LOG] Found 3 relevant document chunks
[1] ID=page_3: Uma base de código única gera alto acoplamento, exigindo recompilar e testar todo o sistema a cada mudança e causando lentidão por longas transações simultâneas no banco.
[2] ID=page_4: Microsserviços isolados com bancos de dados próprios promovem responsabilidade única, reduzindo complexidade e permitindo manutenção, independência e escalabilidade via processos assíncronos.
[3] ID=page_5: A comunicação assíncrona por barramento de eventos "RabbitMQ, Kafka" desacopla serviços e aumenta disponibilidade e escalabilidade, complementada por HTTP/gRPC com contratos e versionamento quando necessário.
=== RAG Response ===
Os principais benefícios da arquitetura de microsserviços incluem:
* **Agilidade e inovação**: equipes independentes podem desenvolver e implantar sem esperar por outras implementações.
* **Simplicidade e manutenibilidade**: cada serviço tem responsabilidade única e banco de dados próprio.
* **Escalabilidade e disponibilidade**: recursos isolados permitem ajustar capacidade e melhorar SLA.
* **Desacoplamento e comunicação assíncrona**: uso de eventos non-blocking garante independência entre serviços.
* **Modelagem orientada a eventos**: fatos de negócio são propagados e consumidos de forma independente.
public async Task<string> QueryAsync(string question, int maxResults = 3, string embeddingModel = "bge-large:335m", string generationModel = "llama3.2:3b")
{
Log.Debug($"Processing RAG query: '{question}'");
Log.Debug("Generating embedding for question");
float[] questionEmbedding = await _ollamaClient.GenerateEmbeddingAsync(question, embeddingModel);
Log.Debug($"Generated embedding with {questionEmbedding.Length} dimensions: [{string.Join(", ", questionEmbedding.Take(5))}...]");
Log.Debug("Searching for relevant context in ChromaDB");
var results = await _chromaClient.QueryAsync(_collectionId, questionEmbedding, maxResults);
Log.Debug($"Found {results.Length} relevant document chunks");
string context = string.Join("\n\n",
results.Select((r, idx) => $"[{idx + 1}] ID={r.Id}\n{r.Doc.Replace('\n', ' ')}")
);
var prompt = BuildPrompt(context, question);
Console.WriteLine("[PROMPT]\n" + prompt + "\n[/PROMPT]");
Log.Debug($"Built prompt with {prompt.Length} characters for LLM generation");
return await _ollamaClient.GenerateCompletionAsync(prompt, generationModel, Config.DefaultMaxTokens, Config.DefaultTemperature);
}
Considerações finais
Os desenvolvedores .NET vivem um momento extraordinário: agora podem ampliar as suas soluções para projetos de inteligência artificial de forma nativa. Nas minhas palestras e consultorias, tenho apoiado estratégias de software e, hoje, compartilho este pipeline RAG em C# e .NET para demonstrar como criar assistentes de IA e serviços de busca semântica com respostas precisas, contextualizadas e totalmente gerenciáveis em sua própria infraestrutura.
Ao experimentar diferentes abordagens de divisão de texto (chunking), ajustar parâmetros de geração e avaliar outras combinações de modelos e bancos vetoriais, você consegue moldar a solução para qualquer domínio de conhecimento, aumentando a eficiência e elevando a qualidade das interações em suas aplicações.
A sua contribuição é valiosa
?? Este artigo é fruto de longas horas de pesquisa, desenvolvimento, validação de contexto e elaboração minuciosa. Se fez sentido para você, deixe seu comentário e compartilhe com a sua rede além de sugerir novos temas.
Related articles
C# even or odd
Coding in practice
Curso básico de C# com aulas grátis
10 aulas gratuitas para você que quer aprender uma
ecode10 subscription
✓ Read full articles ✓ Read/write forums ✓ Access podcast ✓ Access full jobs opportunities (+288) ✓ Access eBooks ✓ Access magazine ✓ Access videos |
Subscribe now $1/mo |
✓ Read open articles x Read/write forums ✓ Access podcast x Access full jobs opportunities (+288) x Access eBooks x Access magazine x Access videos |