Vespa.NET.Models
1.1.0
dotnet add package Vespa.NET.Models --version 1.1.0
NuGet\Install-Package Vespa.NET.Models -Version 1.1.0
<PackageReference Include="Vespa.NET.Models" Version="1.1.0" />
<PackageVersion Include="Vespa.NET.Models" Version="1.1.0" />
<PackageReference Include="Vespa.NET.Models" />
paket add Vespa.NET.Models --version 1.1.0
#r "nuget: Vespa.NET.Models, 1.1.0"
#:package Vespa.NET.Models@1.1.0
#addin nuget:?package=Vespa.NET.Models&version=1.1.0
#tool nuget:?package=Vespa.NET.Models&version=1.1.0
<div align="center">
Vespa.NET
The strongly-typed .NET SDK for Vespa.ai.
</div>
// Define your model → deploy schema → search. That's it.
await client.Admin.DeploySchemaAsync<Product>();
await client.Documents.PutAsync(product.Id, product);
var results = await client.Search.NearestNeighborSearchAsync<Product>(embedding, p => p.Embedding, topK: 10);
Why Vespa.NET?
| Without Vespa.NET | With Vespa.NET |
|---|---|
Write .sd schema files by hand |
[VespaDocument] + [VespaField] on your C# model |
| Build ZIP packages, POST to config server | await client.Admin.DeploySchemaAsync<Product>() |
| Construct YQL strings with string concatenation | Fluent YqlBuilder<T> with lambda field selectors |
| Manual HTTP calls, JSON parsing, error handling | Typed SearchAsync<T>, GetAsync<T>, BulkPutAsync<T> |
| Roll your own retry, circuit breaker, metrics | Built-in Polly resilience + OpenTelemetry out of the box |
Features
| Schema | Code-first .sd generation from attributes, multi-type deploy, custom services.xml |
| Documents | CRUD, conditional writes, field-level ops, visit/iterate, group/number addressing (read+write), selection-based update/delete/copy with continuation auto-loop + crash-safe manual paging |
| Search | Full-text, nearest-neighbor, hybrid, streaming, auto-paginated IAsyncEnumerable |
| YQL | Fluent type-safe builder with boolean composition, grouping, ranking DSL |
| Feed | Parallel /document/v1 pipeline with Channel<T> backpressure, progress callbacks |
| Multi-tenant | [VespaExtraFields] catch-all for dynamic fields, configurable tenant |
| Cloud | mTLS (PEM, PFX, in-memory cert), Bearer token auth |
| Ops | OpenTelemetry traces + metrics, ASP.NET Core health checks, Testcontainers |
| Performance | HTTP/2 multiplexing, connection pooling, GZip/Deflate, ResponseHeadersRead |
| Resilience | Retry + circuit breaker via Polly, zero-allocation [LoggerMessage] logging |
Quick Start
1. Install
dotnet add package Vespa.NET
2. Define a model
[VespaDocument("product", Namespace = "myapp")]
public record Product
{
[VespaId]
public string Id { get; init; } = "";
[VespaField(Name = "product_name", IndexingMode = IndexingMode.IndexAttributeSummary)]
public string Name { get; init; } = "";
[VespaField(Name = "price", IndexingMode = IndexingMode.AttributeSummary)]
public decimal Price { get; init; }
[VespaTensor("tensor<float>(x[128])", EnableIndex = true, DistanceMetric = DistanceMetric.Euclidean)]
public VespaTensor Embedding { get; init; } = null!;
}
3. Deploy, index, search
var options = new VespaClientOptions
{
Endpoint = "http://localhost:8080",
DefaultNamespace = "myapp"
};
using var httpClient = new HttpClient { BaseAddress = new Uri(options.Endpoint) };
using var client = new VespaClient(httpClient, options);
// Deploy schema from C# attributes
await client.Admin.DeploySchemaAsync<Product>();
// Index a document
var product = new Product { Id = "p-1", Name = "Laptop", Price = 999.99m, Embedding = embeddings };
await client.Documents.PutAsync(product.Id, product);
// Search
var results = await client.Search.NearestNeighborSearchAsync<Product>(
queryEmbedding, p => p.Embedding, topK: 10);
Tip: With
[VespaDocument]on your model,documentTypeandnamespaceare inferred automatically in all operations. No strings needed.
Model-Aware API
When your model has [VespaDocument], all operations infer types automatically. [VespaField(Name)] drives both schema generation and JSON serialization.
// CRUD — no documentType needed
await client.Documents.PutAsync(product.Id, product);
var doc = await client.Documents.GetAsync<Product>(product.Id);
await client.Documents.DeleteAsync<Product>(product.Id);
// Field-level update with typed lambda builder
await client.Documents.UpdateFieldsAsync<Product>(product.Id, ops => ops
.Field(p => p.Name, FieldOp.Assign("New Name"))
.Field(p => p.Price, FieldOp.Multiply(0.9)));
// Nearest-neighbor — field name resolved via lambda
var results = await client.Search.NearestNeighborSearchAsync<Product>(
queryEmbedding, p => p.Embedding, topK: 10);
// Visit
await foreach (var d in client.Documents.VisitAsync<Product>(selection: "product.price > 100"))
Process(d.Fields);
// Selection-based bulk ops — typed builder, auto-loops on Vespa's continuation token
var resp = await client.Documents.UpdateBySelectionAsync<Product>(
"product.category == \"legacy\"",
ops => ops.Field(p => p.Status, FieldOp.Assign("archived")),
cluster: "content");
// resp.DocumentCount is the total across all chunks
// Cross-cluster copy
await client.Documents.CopyBySelectionAsync(
"product.tier == \"cold\"", "product",
cluster: "hot", destinationCluster: "cold");
Note: ID normalisation: pass either
"p-1"or"id:myapp:product::p-1"— the client strips the prefix automatically.
YQL Builder
using Vespa.Query;
var yql = YqlBuilder<Product>
.Select(p => p.Name, p => p.Price)
.Where(w => w.Field(p => p.Price).GreaterThan(10)
.And(sub => sub.Field(p => p.Name).Contains("laptop")))
.OrderBy(p => p.Price, descending: true)
.Limit(20)
.Build();
Fluent request builder captures YQL + ranking in one chain:
var request = YqlBuilder<Product>
.Select()
.Where(w => w.HybridSearch(p => p.Embedding, "q", "userQuery", targetHits: 100))
.Limit(20)
.ToSearchRequest()
.WithRankProfile("hybrid_twophase")
.WithQueryTensor("q", "embed(e5small, @userQuery)")
.WithUserInput("userQuery", searchText);
Full predicate reference, boolean composition, and validation rules in docs/yql-builder.md
Grouping & Aggregation
var request = new VespaSearchRequest
{
Yql = YqlBuilder<Product>
.Select()
.GroupBy(
GroupingBuilder.All()
.Group("category")
.Max(10)
.OrderByDescending(GroupingAgg.Count())
.Each(e => e.Output(GroupingAgg.Count(), GroupingAgg.Avg("price"))))
.Build(),
Hits = 0
};
var result = await client.Search.GroupByAsync<Product>(request);
Nested grouping, buckets, having, pagination, and full aggregation reference in docs/grouping.md
Bulk Feed
// Streaming pipeline with backpressure
var result = await client.Feed.FeedAsync(
ReadFromDatabase(), // IAsyncEnumerable<FeedDocument<Product>>
documentType: "product",
maxConcurrency: 64, // parallel HTTP/2 streams
boundedCapacity: 256, // backpressure buffer
onProgress: p => Console.Write($"\r{p.SuccessCount} docs..."));
Per-document conditions, BulkPut/Update/Delete, and FeedResult details in docs/feed.md
Multi-Tenant Dynamic Fields
For platforms where tenants define custom fields at runtime, [VespaExtraFields] provides a catch-all bag — similar to MongoDB's [BsonExtraElements]:
public record ProductFields
{
// App-owned fields — strongly typed, used for ranking
[VespaField(IndexingMode = IndexingMode.AttributeSummary)]
public double Popularity { get; init; }
// Tenant-defined fields — captured dynamically
[VespaExtraFields]
public Dictionary<string, JsonElement>? TenantFields { get; init; }
}
- Unmapped JSON fields are captured in the dictionary during deserialization
- Serialized flat alongside declared properties (not nested)
- Round-trip safe: deserialize then serialize preserves everything
- Schema builder ignores
[VespaExtraFields]properties
Admin API
// Deploy from C# attributes
await client.Admin.DeploySchemaAsync<Product>();
// Multi-tenant: override tenant per-call
await client.Admin.DeploySchemaAsync<Product>(tenant: "acme");
// Deploy raw ZIP
await client.Admin.DeployAsync(zipStream);
// Cluster status
var status = await client.Admin.GetApplicationStatusAsync();
var ready = await client.IsReadyAsync();
var version = await client.GetVersionAsync();
var metrics = await client.GetMetricsAsync();
Note:
ApiKey,DefaultRequestHeaders, compression, timeout, and mTLS settings are applied to both the data-plane client and the admin/config-server client.
Tip: Health/state helpers such as
HealthCheckAsync,IsReadyAsync,GetMetricsAsync,GetVersionAsync, andGetHistogramsAsyncare best-effort on ordinary failures, but they still propagateOperationCanceledExceptionwhen the request is cancelled.
Schema attributes,
[VespaExtraFields],ApplicationPackageOptions, andCustomServicesXmlin docs/schema.md
Configuration & Ops
<details> <summary><strong>DI Integration</strong></summary>
builder.Services.AddVespaClient(new VespaClientOptions
{
Endpoint = "http://localhost:8080",
DefaultNamespace = "myapp",
EnableRetry = true
});
// Named client (multi-cluster)
builder.Services.AddVespaClient("analytics", new VespaClientOptions
{
Endpoint = "http://analytics-vespa:8080"
});
// Inject
public class ProductService(IVespaClient vespa)
{
public Task<VespaDocument<Product>?> Get(string id)
=> vespa.Documents.GetAsync<Product>(id);
}
</details>
<details> <summary><strong>Vespa Cloud (mTLS)</strong></summary>
var options = new VespaClientOptions
{
Endpoint = "https://myapp.vespa-cloud.com",
CertificatePath = "/path/to/cert.pem",
ClientKeyPath = "/path/to/key.pem",
// or: ClientCertificate = myCert,
// or: ApiKey = "bearer-token"
};
</details>
<details> <summary><strong>Observability (OpenTelemetry)</strong></summary>
builder.Services.AddOpenTelemetry()
.WithTracing(b => b.AddSource("Vespa.NET"))
.WithMetrics(m => m.AddMeter("Vespa.NET"));
Spans: vespa.search, vespa.document.*, vespa.feed.pipeline, etc.
Metrics: vespa.client.requests, vespa.client.request_duration, vespa.client.documents_written, etc.
</details>
<details> <summary><strong>Health Checks</strong></summary>
builder.Services.AddHealthChecks().AddVespa();
</details>
<details> <summary><strong>Testcontainers</strong></summary>
await using var vespa = new VespaContainer();
await vespa.StartAsync();
var (client, http) = vespa.CreateClientWithHttp("test");
Set VESPA_INTEGRATION_TESTS=1 to enable in CI.
</details>
Full configuration reference, exceptions, metrics table, and more in docs/configuration.md
Documentation
| Guide | Content |
|---|---|
| Schema & Attributes | Code-first generation, [VespaExtraFields], ApplicationPackageOptions |
| Document Operations | CRUD, field ops, conditional writes, visit, addressing |
| Search | Basic, nearest-neighbor, hybrid, streaming, request options |
| YQL Builder | Fluent query builder, predicates, boolean composition |
| Grouping | Aggregations, buckets, nested grouping, pagination |
| Ranking DSL | RankingBuilder, code-first rank profiles |
| Feed | Bulk operations, streaming pipeline, backpressure |
| Configuration | Options, DI, mTLS, observability, health checks, testing |
Running the Sample App
docker run --detach --name vespa --publish 8080:8080 --publish 19071:19071 vespaengine/vespa
dotnet run --project samples/Vespa.NET.Samples
Dependencies
| Package | Purpose |
|---|---|
Microsoft.Extensions.Http |
IHttpClientFactory |
Microsoft.Extensions.Http.Resilience |
Retry + circuit breaker (Polly) |
Microsoft.Extensions.Diagnostics.HealthChecks |
ASP.NET Core health check |
System.Text.Json |
JSON serialization (built-in) |
DotNet.Testcontainers (test project only) |
Docker test fixtures |
Resources
License
MIT License — see LICENSE for details.
| Product | Versions 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 is compatible. 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. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Vespa.NET.Models:
| Package | Downloads |
|---|---|
|
Vespa.NET
High-performance, feature-rich .NET client for Vespa.ai. Supports Document API, YQL Builder, Nearest-Neighbor search, Grouping/Aggregation, Streaming search, Ranking DSL, and Bulk Feed with resilience built-in. |
GitHub repositories
This package is not used by any popular GitHub repositories.