Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
56 changes: 56 additions & 0 deletions .github/workflows/vector-search-dotnet-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: "Vector Search .NET — Build"

on:
pull_request:
branches: [master]
paths:
- ".github/workflows/vector-search-dotnet-ci.yml"
- "samples/features/vector-search/data/**"
- "samples/features/vector-search/vector-search-query-dotnet/**"
push:
branches: [master]
paths:
- ".github/workflows/vector-search-dotnet-ci.yml"
- "samples/features/vector-search/data/**"
- "samples/features/vector-search/vector-search-query-dotnet/**"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
build:
name: Build and test
runs-on: ubuntu-latest
timeout-minutes: 10

defaults:
run:
working-directory: samples/features/vector-search/vector-search-query-dotnet

steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup .NET
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: '10.0.x'

- name: Restore
run: dotnet restore Tests/vector-search-query-dotnet.Tests.csproj

- name: Format
run: dotnet format Tests/vector-search-query-dotnet.Tests.csproj --verify-no-changes --no-restore

- name: Build
run: dotnet build Tests/vector-search-query-dotnet.Tests.csproj --configuration Release --no-restore

- name: Test
run: dotnet test Tests/vector-search-query-dotnet.Tests.csproj --configuration Release --no-build --nologo

- name: Self-check
run: dotnet run --project vector-search-query-dotnet.csproj --configuration Release --no-build -- --self-check
75 changes: 75 additions & 0 deletions .github/workflows/vector-search-go-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# CI for the Azure SQL Vector Search Go quickstart sample.
# Security: uses `pull_request` (read-only token, no secrets on forks)
# with an explicit same-repo check to skip fork PRs entirely.
#
# This CI runs static analysis and unit tests only — no live Azure SQL
# Database or Azure OpenAI resource is required or contacted. Live
# end-to-end validation is a separate, manual step (see the sample's
# README and output/sample-output.txt for the explicit no-live-run
# disclosure).

name: "Vector Search Go — Build"

on:
pull_request:
branches: [master]
paths:
- "samples/features/vector-search/vector-search-query-go/**"
push:
branches: [master]
paths:
- "samples/features/vector-search/vector-search-query-go/**"

# Cancel redundant runs for the same PR / branch.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
build:
name: Vet, format-check, build, and test
runs-on: ubuntu-latest
timeout-minutes: 10

# Skip CI on fork PRs — prevents external actors from consuming minutes.
if: >-
github.event_name == 'push' ||
github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name

defaults:
run:
working-directory: samples/features/vector-search/vector-search-query-go

steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: "1.25"
cache-dependency-path: samples/features/vector-search/vector-search-query-go/go.sum

- name: Download dependencies
run: go mod download

- name: Format check (gofmt)
run: |
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "The following files are not gofmt-formatted:"
echo "$unformatted"
exit 1
fi

- name: Vet
run: go vet ./...

- name: Build
run: go build ./...

- name: Unit tests (no Azure connectivity required)
run: go test ./... -v
74 changes: 74 additions & 0 deletions .github/workflows/vector-search-python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# CI for the Azure SQL Vector Search Python quickstart sample.
# Security: uses `pull_request` (read-only token, no secrets on forks)
# with an explicit same-repo check to skip fork PRs entirely.
#
# This CI runs static analysis and unit tests only — no live Azure SQL
# Database or Azure OpenAI resource is required or contacted. Live
# end-to-end validation is a separate, manual step (see the sample's
# README and output/sample-output.txt for the explicit no-live-run
# disclosure).

name: "Vector Search Python — Build"

on:
pull_request:
branches: [master]
paths:
- "samples/features/vector-search/vector-search-query-python/**"
push:
branches: [master]
paths:
- "samples/features/vector-search/vector-search-query-python/**"

# Cancel redundant runs for the same PR / branch.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
build:
name: Lint, type-check, and test
runs-on: ubuntu-latest
timeout-minutes: 10

# Skip CI on fork PRs — prevents external actors from consuming minutes.
if: >-
github.event_name == 'push' ||
github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name

defaults:
run:
working-directory: samples/features/vector-search/vector-search-query-python

steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
cache: "pip"
cache-dependency-path: |
samples/features/vector-search/vector-search-query-python/requirements.txt
samples/features/vector-search/vector-search-query-python/requirements-dev.txt

- name: Install dependencies
run: |
pip install -r requirements.txt
pip install -r requirements-dev.txt

- name: Lint
run: python -m ruff check .

- name: Format check
run: python -m ruff format --check .

- name: Type-check
run: python -m mypy src

- name: Unit tests (no Azure connectivity required)
run: python -m pytest -v
6 changes: 6 additions & 0 deletions .github/workflows/vector-search-typescript-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,11 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Format check
run: npm run format:check

- name: Type-check (build)
run: npm run build
13 changes: 8 additions & 5 deletions samples/features/vector-search/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,22 @@ This provisions:
- **Azure OpenAI** with `text-embedding-3-small` deployment
- **Managed Identity** with appropriate role assignments

After deployment, create the `.env` file for the language samples:
After deployment, create the `.env` file for the language samples. For example, for TypeScript:

```bash
azd env get-values --cwd samples/features/vector-search > samples/features/vector-search/vector-search-query-typescript/.env
```

This writes the deployment outputs (`AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_EMBEDDING_DEPLOYMENT`, `AZURE_SQL_SERVER`, `AZURE_SQL_DATABASE`, etc.) directly into the `.env` file that the samples read at runtime.
This writes the deployment outputs (`AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_EMBEDDING_DEPLOYMENT`, `AZURE_SQL_SERVER`, `AZURE_SQL_DATABASE`, etc.) directly into the `.env` file that the TypeScript, Python, and Go samples read at runtime. Point the same command at another language's folder to configure it instead.

## Language samples

| Language | Folder | Description |
|----------|--------|-------------|
| TypeScript | [vector-search-query-typescript/](./vector-search-query-typescript/) | Vector search with Node.js, tedious driver, and Azure OpenAI |
| Language | Folder | Description | Status |
|----------|--------|-------------|--------|
| TypeScript | [vector-search-query-typescript/](./vector-search-query-typescript/) | Vector search with Node.js, tedious driver, and Azure OpenAI | Implemented; live end-to-end run captured |
| Python | [vector-search-query-python/](./vector-search-query-python/) | Vector search with pyodbc, azure-identity, and Azure OpenAI | Implemented; statically validated (lint, type-check, unit tests) — no live end-to-end run captured yet |
| .NET | [vector-search-query-dotnet/](./vector-search-query-dotnet/) | Vector search with Microsoft.Data.SqlClient, Azure.Identity, and Azure.AI.OpenAI | Implemented; static validation only — no live end-to-end run captured yet |
| Go | [vector-search-query-go/](./vector-search-query-go/) | Vector search with go-mssqldb, azidentity, and the OpenAI Go client configured for Azure | Implemented; statically validated (vet, build, unit tests) — no live end-to-end run captured yet; also out of scope for v1 by constitution decision ([ASV-LANG-GO-1](/.github/instructions/vector-search-constitution.instructions.md)) |

## Infrastructure only

Expand Down
2 changes: 1 addition & 1 deletion samples/features/vector-search/infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ param location string
@description('Object ID of the principal to assign database and application roles. Required — must be set via AZURE_PRINCIPAL_ID. Expected as UUID (36 characters).')
@minLength(36)
@maxLength(36)
param deploymentUserPrincipalId string = ''
param deploymentUserPrincipalId string

@description('Client IP address for SQL firewall rule (for local development).')
param clientIpAddress string = ''
Expand Down
2 changes: 1 addition & 1 deletion samples/features/vector-search/infra/main.bicepparam
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ using './main.bicep'
param environmentName = readEnvironmentVariable('AZURE_ENV_NAME', 'development')
param location = readEnvironmentVariable('AZURE_LOCATION', 'eastus2')
param sqlLocation = readEnvironmentVariable('AZURE_SQL_LOCATION', 'westus2')
param deploymentUserPrincipalId = readEnvironmentVariable('AZURE_PRINCIPAL_ID', '')
param deploymentUserPrincipalId = readEnvironmentVariable('AZURE_PRINCIPAL_ID')
param clientIpAddress = readEnvironmentVariable('AZURE_CLIENT_IP', '')
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
bin/
obj/
.vs/
.idea/
.env
.env.*
**/bin/
**/obj/
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using Microsoft.Extensions.Configuration;

namespace VectorSearchSample;

public sealed class AppConfig
{
public string AzureOpenAiEndpoint { get; }
public string AzureOpenAiEmbeddingDeployment { get; }
public string AzureSqlServer { get; }
public string AzureSqlDatabase { get; }
public string VectorSearchAlgorithm { get; }
public string TableName { get; }
public bool DropTable { get; }

private AppConfig(
string azureOpenAiEndpoint,
string azureOpenAiEmbeddingDeployment,
string azureSqlServer,
string azureSqlDatabase,
string vectorSearchAlgorithm,
string tableName,
bool dropTable)
{
AzureOpenAiEndpoint = azureOpenAiEndpoint;
AzureOpenAiEmbeddingDeployment = azureOpenAiEmbeddingDeployment;
AzureSqlServer = azureSqlServer;
AzureSqlDatabase = azureSqlDatabase;
VectorSearchAlgorithm = vectorSearchAlgorithm;
TableName = tableName;
DropTable = dropTable;
}

public static AppConfig Load(IConfiguration configuration)
{
var azureOpenAiEndpoint = GetRequired(configuration, "AZURE_OPENAI_ENDPOINT");
var azureOpenAiEmbeddingDeployment = GetRequired(configuration, "AZURE_OPENAI_EMBEDDING_DEPLOYMENT");
var azureSqlServer = GetRequired(configuration, "AZURE_SQL_SERVER");
var azureSqlDatabase = GetRequired(configuration, "AZURE_SQL_DATABASE");

if (!Uri.TryCreate(azureOpenAiEndpoint, UriKind.Absolute, out var endpointUri) ||
endpointUri.Scheme != Uri.UriSchemeHttps ||
endpointUri.Host.Length == 0)
{
throw new InvalidOperationException(
"Invalid AZURE_OPENAI_ENDPOINT. Provide an absolute HTTPS endpoint, for example https://my-resource.openai.azure.com.");
}

if (azureSqlServer.Contains("://", StringComparison.Ordinal) ||
azureSqlServer.Any(char.IsWhiteSpace))
{
throw new InvalidOperationException(
"Invalid AZURE_SQL_SERVER. Provide a server host name without a protocol or whitespace.");
}

var algorithm = (configuration["VECTOR_SEARCH_ALGORITHM"] ?? "exact").Trim();
algorithm = algorithm.ToLowerInvariant();
if (algorithm is not ("exact" or "diskann"))
{
throw new InvalidOperationException($"Invalid VECTOR_SEARCH_ALGORITHM: \"{algorithm}\". Must be \"exact\" or \"diskann\".");
}

var tableName = (configuration["AZURE_SQL_TABLE_NAME"] ?? "hotels_dotnet").Trim();
if (!System.Text.RegularExpressions.Regex.IsMatch(tableName, "^[a-zA-Z_][a-zA-Z0-9_]{0,114}$"))
{
throw new InvalidOperationException(
$"Invalid AZURE_SQL_TABLE_NAME: \"{tableName}\". Must start with a letter or underscore, contain only letters, numbers, and underscores, and be at most 115 characters.");
}

var dropTable = bool.TryParse(configuration["SQL_DROP_TABLE"] ?? configuration["dropTable"], out var parsedDropTable) && parsedDropTable;

return new AppConfig(
azureOpenAiEndpoint,
azureOpenAiEmbeddingDeployment,
azureSqlServer,
azureSqlDatabase,
algorithm,
tableName,
dropTable);
}

private static string GetRequired(IConfiguration configuration, string key)
{
var value = configuration[key];
if (string.IsNullOrWhiteSpace(value) ||
value.Contains("<your-", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
$"Missing required configuration value: {key}. Replace the placeholder in appsettings.json or set the environment variable.");
}

return value.Trim();
}
}
Loading
Loading