Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Spice/Spice.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<PackageId>SpiceAI</PackageId>
<Version>0.3.0</Version>
<Version>0.4.0</Version>
<TargetFrameworks>netstandard2.0;net8.0;net9.0;net10.0</TargetFrameworks>

<!-- NuGet Package Metadata -->
Expand Down
101 changes: 101 additions & 0 deletions docs/release_notes/v0.4.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Spice.ai .NET SDK v0.4.0 Release Notes

## New Features

### Mutual TLS (mTLS) Client Certificate Support

`SpiceClientBuilder` now accepts a PEM-encoded client certificate and key for mTLS, in addition to the existing server-side TLS options.

```csharp
using var client = new SpiceClientBuilder()
.WithAddress("https://localhost:50051")
.WithTlsClientCertificate("client.pem", "client.key")
.WithTlsRootCertificate("ca.pem")
.Build();
```

`WithTlsClientCertificate` implies `WithTls(true)`. Applies to both the Flight (gRPC) and HTTP clients.

### Health and Readiness Checks

New `SpiceClient.IsSpiceHealthyAsync` and `SpiceClient.IsSpiceReadyAsync` methods wrap the runtime's `/health` and `/v1/ready` endpoints, reusing the endpoint, credentials, TLS/mTLS and user agent already configured on the builder.

```csharp
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
if (!await client.IsSpiceHealthyAsync(cts.Token))
{
// runtime unreachable or unhealthy
}

bool ready = await client.IsSpiceReadyAsync();
```

An unreachable or unhealthy runtime is reported as `false` rather than thrown as an exception; caller-requested cancellation still propagates.

### Vector, Keyword, and Hybrid Search

New `SpiceClient.SearchAsync(SearchRequest, CancellationToken)` wraps `POST /v1/search` for datasets with an embedding column and a loaded embedding model.

```csharp
using Spice.Search;

var response = await client.SearchAsync(new SearchRequest("cozy mountain cabin")
{
Datasets = new[] { "listings" },
Limit = 10,
Where = "city = 'Tahoe'",
AdditionalColumns = new[] { "price" },
});

foreach (var match in response.Results)
{
Console.WriteLine($"{match.Dataset}: {match.Score}");
}
```

Setting `Keywords` pre-filters the embedding column with a lexical search before the vector search runs, making the search hybrid.

### Natural Language to SQL (Nsql)

New `SpiceClient.NsqlAsync(NsqlRequest, CancellationToken)` translates a natural-language question into SQL via the runtime's configured LLM and runs it, returning the rows alongside the generated SQL. `NsqlGenerateSqlAsync` generates the SQL without running it.

```csharp
using Spice.Nsql;

var response = await client.NsqlAsync(new NsqlRequest("how many taxi trips were there yesterday?"));
Console.WriteLine(response.SQL); // the SQL the model generated
Console.WriteLine(response.Data); // the rows it returned, decoded from JSON

// Inspect or edit the generated SQL without running it:
string sql = await client.NsqlGenerateSqlAsync(new NsqlRequest("how many taxi trips were there yesterday?"));
```

Requires an LLM model configured in the Spicepod.

### On-Demand Dataset Refresh Options

`RefreshDatasetAsync` accepts an optional `RefreshOptions` to override the dataset's configured refresh behavior for a single on-demand refresh.

```csharp
await client.RefreshDatasetAsync("taxi_trips", new RefreshOptions()
.WithRefreshSql("SELECT * FROM taxi_trips WHERE tip_amount > 10.0")
.WithRefreshMode(RefreshMode.Append)
.WithMaxJitter(TimeSpan.FromSeconds(10)));
```

Any option left unset falls back to the dataset's configured value. Applies to datasets accelerated with `full` or `append` refresh mode; datasets using `changes` mode are kept up to date by change data capture and are unaffected.

## Bug Fixes

- Fixed `AuthenticateAsync` throwing `System.InvalidOperationException: Can't get the call trailers because the call has not completed successfully` on a failed handshake, which masked the actual gRPC authentication error. The auth token is now read from response headers first, falling back to trailers only when headers carry none.
- Fixed mTLS client certificate handshakes failing on Windows.
- `PooledConnectionLifetime` is now set on the gRPC channel's `SocketsHttpHandler` so long-lived connections are periodically recycled and DNS is re-resolved. Previously, clients connecting through load-balanced endpoints (e.g. AWS ALBs) could get stuck on a stale backend IP after a rebalance.

## Dependencies

Added:
- `System.Text.Json` 9.0.9 (netstandard2.0 target only)

## Breaking Changes

None.