Skip to content

Setup & Configuration

Granit.AI brings Large Language Model (LLM) capabilities to your application through a provider-agnostic abstraction layer built on Microsoft.Extensions.AI (IChatClient, IEmbeddingGenerator). AI providers are interchangeable per environment — Ollama for development, Azure OpenAI for production — without changing application code.

The module introduces workspaces: named AI configurations that bind a provider, model, system prompt, and parameters. Workspaces are multi-tenant, permission-scoped, and can be declared in code (immutable) or managed via API (dynamic). Every AI interaction is tracked for cost monitoring and ISO 27001 compliance.

ApproachProsCons
Direct SDK usage (OpenAI, Anthropic)Full API accessVendor lock-in, no middleware pipeline, each module reimplements integration
Custom abstractions (ITextGenerationService)Full controlIsolated from .NET ecosystem, reinvents the wheel
Microsoft.Extensions.AIStandard .NET 10, ~100 compatible packages, middleware pipeline (logging, caching, OTel)Newer API surface

Granit.AI uses the .NET standard. Your application code depends on IChatClient, not on OpenAI or Anthropic. Swapping providers is a one-line configuration change.

  • DirectoryGranit.AI/ Core abstractions, workspace management, factory pattern
    • Granit.AI.OpenAI OpenAI provider (GPT-4o, o3/o4-mini, embeddings)
    • Granit.AI.AzureOpenAI Azure OpenAI with Managed Identity support
    • Granit.AI.Anthropic Anthropic Claude (Opus, Sonnet, Haiku)
    • Granit.AI.Ollama Local models via OllamaSharp (dev, on-premise, GDPR)
    • Granit.AI.EntityFrameworkCore Isolated DbContext for workspaces and usage tracking
    • Granit.AI.Extraction Structured document extraction (PDF → typed C# object)
    • Granit.AI.VectorData Multi-tenant vector storage for semantic search (RAG)
  • DirectoryGranit.DataExchange.AI/ AI-powered column mapping for CSV/Excel import
  • DirectoryGranit.Querying.AI/ Natural Language Query — phrases to structured filters
PackageRoleDepends on
Granit.AIIAIChatClientFactory, IAIProviderFactory, workspace CQRS, usage trackingGranit.Core
Granit.AI.OpenAIOpenAI provider via Microsoft.Extensions.AI.OpenAIGranit.AI
Granit.AI.AzureOpenAIAzure OpenAI with API key + DefaultAzureCredentialGranit.AI
Granit.AI.AnthropicClaude models via official Anthropic SDKGranit.AI
Granit.AI.OllamaLocal models, health check, zero API keyGranit.AI
Granit.AI.EntityFrameworkCoreAIDbContext, EfAIWorkspaceStore, EfAIUsageStoreGranit.AI, Granit.Persistence
Granit.AI.ExtractionIDocumentExtractor<T>, structured output from documentsGranit.AI
Granit.AI.VectorDataIVectorCollection<T>, ISemanticSearchService for RAGGranit.AI
Granit.DataExchange.AIAI-powered ISemanticMappingService for import mappingGranit.AI, Granit.DataExchange
Granit.Querying.AIINaturalLanguageQueryTranslator (NLQ → QueryRequest)Granit.AI, Granit.Querying
ProviderModelsEmbeddingsAuthBest for
OpenAIGPT-4o, o3, o4-minitext-embedding-3API keyGeneral-purpose, global deployments
Azure OpenAISame as OpenAISameAPI key / Managed IdentityEU data sovereignty, HDS, enterprise
AnthropicClaude Opus, Sonnet, HaikuNot supportedAPI keyLong document analysis, reasoning
OllamaLlama 3.1, Mistral, Phi, Gemmanomic-embed-textNoneDevelopment, on-premise, GDPR-strict
graph TD
    AI[Granit.AI] --> CO[Granit.Core]
    OAI[Granit.AI.OpenAI] --> AI
    AAI[Granit.AI.AzureOpenAI] --> AI
    AN[Granit.AI.Anthropic] --> AI
    OL[Granit.AI.Ollama] --> AI
    EF[Granit.AI.EntityFrameworkCore] --> AI
    EF --> P[Granit.Persistence]
    EX[Granit.AI.Extraction] --> AI
    VD[Granit.AI.VectorData] --> AI
    DEX[Granit.DataExchange.AI] --> AI
    DEX --> DE[Granit.DataExchange]
    QAI[Granit.Querying.AI] --> AI
    QAI --> Q[Granit.Querying]
[DependsOn(typeof(GranitAIOllamaModule))]
public class AppModule : GranitModule { }
builder.AddGranitAI();
builder.AddGranitAIOllama();

No configuration needed — connects to http://localhost:11434 with llama3.1 by default.

Terminal window
# Start Ollama and pull the default model
ollama pull llama3.1
ollama serve

For production, add Granit.AI.EntityFrameworkCore to persist workspaces and track usage:

[DependsOn(
typeof(GranitAIOllamaModule), // or any provider
typeof(GranitAIEntityFrameworkCoreModule))]
public class AppModule : GranitModule { }
builder.AddGranitAI();
builder.AddGranitAIOllama();
builder.AddGranitAIEntityFrameworkCore(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("AI")));

A workspace is a named AI configuration: which provider, which model, what parameters.

flowchart LR
    App[Application Code] -->|"CreateAsync('support-chat')"| F[IAIChatClientFactory]
    F --> WP[IAIWorkspaceProvider]
    WP --> SYS[System Workspaces<br/>code-defined, immutable]
    WP --> DYN[Dynamic Workspaces<br/>database, API-managed]
    F --> PF[IAIProviderFactory]
    PF --> OAI[OpenAI]
    PF --> AAI[Azure OpenAI]
    PF --> AN[Anthropic]
    PF --> OL[Ollama]
    F -->|returns| CC[IChatClient]

Declare workspaces in code for configurations that should not change at runtime:

public class AppWorkspaceDefinitionProvider : IAIWorkspaceDefinitionProvider
{
public void Define(IAIWorkspaceDefinitionContext context)
{
context.Add(new AIWorkspace
{
Name = "document-extraction",
Provider = "AzureOpenAI",
Model = "gpt-4o",
SystemPrompt = "Extract structured data from documents. Return valid JSON only.",
Temperature = 0.0f,
MaxOutputTokens = 4096,
});
context.Add(new AIWorkspace
{
Name = "support-chat",
Provider = "Anthropic",
Model = "claude-sonnet-4-6",
SystemPrompt = "You are a helpful customer support assistant.",
Temperature = 0.7f,
});
}
}

System workspaces take precedence over dynamic workspaces with the same name and cannot be modified or deleted via the API.

Dynamic workspaces are created and managed through IAIWorkspaceManager (CQRS writer) and stored by Granit.AI.EntityFrameworkCore. Use these when workspace configuration should be editable at runtime (e.g., per-tenant customization).

public class InvoiceSummaryService(IAIChatClientFactory chatClientFactory)
{
public async Task<string> SummarizeAsync(
Invoice invoice,
CancellationToken cancellationToken)
{
IChatClient client = await chatClientFactory
.CreateAsync("document-extraction", cancellationToken)
.ConfigureAwait(false);
ChatResponse response = await client.GetResponseAsync(
$"Summarize this invoice in 3 bullet points: {invoice.Description}",
cancellationToken: cancellationToken)
.ConfigureAwait(false);
return response.Text;
}
}
public class PatientSearchService(IAIEmbeddingGeneratorFactory embeddingFactory)
{
public async Task<Embedding<float>> EmbedAsync(
string searchQuery,
CancellationToken cancellationToken)
{
IEmbeddingGenerator<string, Embedding<float>> generator =
await embeddingFactory
.CreateAsync("embeddings", cancellationToken)
.ConfigureAwait(false);
Embedding<float> embedding = await generator
.GenerateEmbeddingAsync(searchQuery, cancellationToken: cancellationToken)
.ConfigureAwait(false);
return embedding;
}
}

When no provider is registered, IAIChatClientFactory.CreateAsync() throws AIProviderNotRegisteredException with a clear message indicating which package to install. When no persistence adapter is configured, null implementations are used — workspaces work from code definitions only, and usage records are discarded.

Every IChatClient call is automatically tracked when GranitAIOptions.EnableUsageTracking is true (default). The AIUsageRecord captures:

FieldDescription
TenantIdTenant that made the request
UserIdUser who triggered the AI call
WorkspaceNameWorkspace used
Provider / ModelProvider and model identifier
InputTokens / OutputTokensToken consumption
EstimatedCostUsdCost estimate based on configured pricing
DurationResponse time
TimestampWhen the interaction occurred (UTC)

Records are persisted by Granit.AI.EntityFrameworkCore and can be queried for billing dashboards, cost alerts, and rate limiting.

Each AI integration has a dedicated documentation page with detailed examples, architecture diagrams, risks analysis, and GDPR considerations:

ModuleWhat it doesDedicated page
DataExchange.AIAI column mapping for CSV/Excel import — handles cryptic columns like Nom_Clt_V2_FinalAI: Import Mapping
AI.ExtractionExtract typed C# objects from documents (invoice PDF → InvoiceData record)AI: Document Extraction
Querying.AINatural language → structured filters (“unpaid invoices from last week”)AI: Natural Language Query
AI.VectorDataSemantic search / RAG — find documents by meaning, build knowledge basesAI: Semantic Search & RAG

Every AI integration follows the same pattern — the existing module defines an interface with a null-object default. The AI package replaces it via DI:

Granit.{Module} → defines IService + NullService (no-op default)
no reference to AI
Granit.{Module}.AI → implements AIService
references Granit.{Module} replaces via DI
references Granit.AI

Without the AI package: the feature is silently skipped. With it: the feature activates. No if statements, no feature flags — just DI composition.

PropertyTypeDefaultDescription
DefaultWorkspacestring"default"Workspace used when none is specified
EnableUsageTrackingbooltrueTrack token consumption and cost
EnableAuditTrailbooltrueLog every AI interaction (ISO 27001)

OpenAIProviderOptions (section: AI:OpenAI)

Section titled “OpenAIProviderOptions (section: AI:OpenAI)”
PropertyTypeDefaultDescription
ApiKeystringOpenAI API key (inject from Vault)
Endpointstring?nullCustom base URL (for proxies)
DefaultModelstring"gpt-4o"Fallback model
DefaultEmbeddingModelstring"text-embedding-3-small"Fallback embedding model

AzureOpenAIProviderOptions (section: AI:AzureOpenAI)

Section titled “AzureOpenAIProviderOptions (section: AI:AzureOpenAI)”
PropertyTypeDefaultDescription
EndpointstringAzure resource endpoint (HTTPS)
ApiKeystring?nullAPI key; empty = Managed Identity
DefaultDeploymentstring"gpt-4o"Fallback deployment name
DefaultEmbeddingDeploymentstring"text-embedding-3-small"Fallback embedding deployment

AnthropicProviderOptions (section: AI:Anthropic)

Section titled “AnthropicProviderOptions (section: AI:Anthropic)”
PropertyTypeDefaultDescription
ApiKeystringAnthropic API key (inject from Vault)
DefaultModelstring"claude-sonnet-4-6"Fallback model
PropertyTypeDefaultDescription
Endpointstring"http://localhost:11434"Ollama server URL
DefaultModelstring"llama3.1"Fallback model (8B server variant)

DataExchangeAIOptions (section: AI:DataExchange)

Section titled “DataExchangeAIOptions (section: AI:DataExchange)”
PropertyTypeDefaultDescription
WorkspaceNamestring"default"AI workspace for mapping suggestions
TimeoutSecondsint10LLM call timeout
MinConfidenceScoredouble0.6Minimum score to accept a suggestion
IncludePreviewRowsboolfalseInclude sample data rows in prompt (GDPR opt-in)
PreviewRowCountint5Number of preview rows when enabled

ExtractionOptions (section: AI:Extraction)

Section titled “ExtractionOptions (section: AI:Extraction)”
PropertyTypeDefaultDescription
WorkspaceNamestring"default"AI workspace for extraction
ReviewThresholddouble0.7Below this confidence → NeedsReview status
TimeoutSecondsint30Extraction timeout (OCR + LLM)
PropertyTypeDefaultDescription
WorkspaceNamestring"default"AI workspace for NLQ translation
TimeoutSecondsint5NLQ should be fast — short timeout

VectorDataOptions (section: AI:VectorData)

Section titled “VectorDataOptions (section: AI:VectorData)”
PropertyTypeDefaultDescription
EmbeddingWorkspacestring"default"Workspace for embedding generation
DefaultSearchLimitint10Default number of results
  • Data minimization: workspaces control what data reaches the LLM via system prompts
  • Right to erasure: workspace and usage records support soft delete
  • Data residency: Azure OpenAI supports EU-only deployments; Ollama keeps data on-premise
  • PII protection: usage records store tenant/user IDs, not conversation content
  • Audit trail: every AI interaction is recorded with tenant, user, workspace, model, timestamp
  • Usage records are immutable: CreationAuditedEntity base class, no update/delete operations
  • Workspace changes are audited: AuditedEntity with CreatedAt/By, ModifiedAt/By

When using Granit.AI.EntityFrameworkCore, two tables are created:

TableEntityPurpose
ai_workspacesAIWorkspaceEntityDynamic workspace configurations (soft-deletable, audited)
ai_usage_recordsAIUsageRecordEntityToken consumption and cost tracking (immutable)

Key indexes:

  • uq_ai_workspaces_tenant_name — unique workspace name per tenant
  • ix_ai_usage_records_tenant_workspace_date — billing queries by workspace and period
  • ix_ai_usage_records_tenant_provider_model — cost aggregation by provider/model