# SkillPatch skill: azure-search-documents-ts

This skill enables agents to build search applications using the Azure AI Search SDK for TypeScript/JavaScript (@azure/search-documents). It covers creating and managing indexes with vector fields, indexing documents, and performing full-text, vector, and hybrid search queries. It also includes authentication patterns using Azure Identity credentials for both local development and production environments.

You (the agent) were given this URL and asked to use this skill. This is a **public** skill — no SkillPatch account, API key, or prior setup is required. Two ways to use it:

**1. Use it right now** — the skill's complete file tree (SKILL.md + all reference files) is inlined below; read `SKILL.md` first, then follow it, consulting the other files as it directs.

**2. Install the exact package onto disk** (recommended if you can run a shell — this reproduces the skill byte-for-byte, including any binary assets that can't be inlined):

```bash
mkdir -p .claude/skills/azure-search-documents-ts
curl -sSL https://skillpatch.dev/install_skill/azure-search-documents-ts | tar -xz -C .claude/skills/
```

(`.claude/skills/` is Claude Code's convention; use whatever directory your agent loads skills from.)


---

## Skill files (3)

- `SKILL.md`
- `references/semantic-ranking.md`
- `references/vector-search.md`


### `SKILL.md`

````markdown
---
name: azure-search-documents-ts
description: Build search applications using Azure AI Search SDK for JavaScript (@azure/search-documents). Use when creating/managing indexes, implementing vector/hybrid search, semantic ranking, or building agentic retrieval with knowledge bases.
license: MIT
metadata:
  author: Microsoft
  version: "1.0.0"
  package: '@azure/search-documents'
---

# Azure AI Search SDK for TypeScript

Build search applications with vector, hybrid, and semantic search capabilities.

## Installation

```bash
npm install @azure/search-documents @azure/identity
```

## Environment Variables

```bash
AZURE_SEARCH_ENDPOINT=https://<service-name>.search.windows.net
AZURE_SEARCH_INDEX_NAME=my-index
AZURE_SEARCH_ADMIN_KEY=<admin-key>  # Optional if using Entra ID
AZURE_TOKEN_CREDENTIALS=prod # Required only if DefaultAzureCredential is used in production
```

## Authentication

```typescript
import { SearchClient, SearchIndexClient } from "@azure/search-documents";
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";

const endpoint = process.env.AZURE_SEARCH_ENDPOINT!;
const indexName = process.env.AZURE_SEARCH_INDEX_NAME!;
// Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=<specific_credential>
const credential = new DefaultAzureCredential({requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"]});
// Or use a specific credential directly in production:
// See https://learn.microsoft.com/javascript/api/overview/azure/identity-readme?view=azure-node-latest#credential-classes
// const credential = new ManagedIdentityCredential();

// For searching
const searchClient = new SearchClient(endpoint, indexName, credential);

// For index management
const indexClient = new SearchIndexClient(endpoint, credential);
```

## Core Workflow

### Create Index with Vector Field

```typescript
import { SearchIndex, SearchField, VectorSearch } from "@azure/search-documents";

const index: SearchIndex = {
  name: "products",
  fields: [
    { name: "id", type: "Edm.String", key: true },
    { name: "title", type: "Edm.String", searchable: true },
    { name: "description", type: "Edm.String", searchable: true },
    { name: "category", type: "Edm.String", filterable: true, facetable: true },
    {
      name: "embedding",
      type: "Collection(Edm.Single)",
      searchable: true,
      vectorSearchDimensions: 1536,
      vectorSearchProfileName: "vector-profile",
    },
  ],
  vectorSearch: {
    algorithms: [
      { name: "hnsw-algorithm", kind: "hnsw" },
    ],
    profiles: [
      { name: "vector-profile", algorithmConfigurationName: "hnsw-algorithm" },
    ],
  },
};

await indexClient.createOrUpdateIndex(index);
```

### Index Documents

```typescript
const documents = [
  { id: "1", title: "Widget", description: "A useful widget", category: "Tools", embedding: [...] },
  { id: "2", title: "Gadget", description: "A cool gadget", category: "Electronics", embedding: [...] },
];

const result = await searchClient.uploadDocuments(documents);
console.log(`Indexed ${result.results.length} documents`);
```

### Full-Text Search

```typescript
const results = await searchClient.search("widget", {
  select: ["id", "title", "description"],
  filter: "category eq 'Tools'",
  orderBy: ["title asc"],
  top: 10,
});

for await (const result of results.results) {
  console.log(`${result.document.title}: ${result.score}`);
}
```

### Vector Search

```typescript
const queryVector = await getEmbedding("useful tool"); // Your embedding function

const results = await searchClient.search("*", {
  vectorSearchOptions: {
    queries: [
      {
        kind: "vector",
        vector: queryVector,
        fields: ["embedding"],
        kNearestNeighborsCount: 10,
      },
    ],
  },
  select: ["id", "title", "description"],
});

for await (const result of results.results) {
  console.log(`${result.document.title}: ${result.score}`);
}
```

### Hybrid Search (Text + Vector)

```typescript
const queryVector = await getEmbedding("useful tool");

const results = await searchClient.search("tool", {
  vectorSearchOptions: {
    queries: [
      {
        kind: "vector",
        vector: queryVector,
        fields: ["embedding"],
        kNearestNeighborsCount: 50,
      },
    ],
  },
  select: ["id", "title", "description"],
  top: 10,
});
```

### Semantic Search

```typescript
// Index must have semantic configuration
const index: SearchIndex = {
  name: "products",
  fields: [...],
  semanticSearch: {
    configurations: [
      {
        name: "semantic-config",
        prioritizedFields: {
          titleField: { name: "title" },
          contentFields: [{ name: "description" }],
        },
      },
    ],
  },
};

// Search with semantic ranking
const results = await searchClient.search("best tool for the job", {
  queryType: "semantic",
  semanticSearchOptions: {
    configurationName: "semantic-config",
    captions: { captionType: "extractive" },
    answers: { answerType: "extractive", count: 3 },
  },
  select: ["id", "title", "description"],
});

for await (const result of results.results) {
  console.log(`${result.document.title}`);
  console.log(`  Caption: ${result.captions?.[0]?.text}`);
  console.log(`  Reranker Score: ${result.rerankerScore}`);
}
```

## Filtering and Facets

```typescript
// Filter syntax
const results = await searchClient.search("*", {
  filter: "category eq 'Electronics' and price lt 100",
  facets: ["category,count:10", "brand"],
});

// Access facets
for (const [facetName, facetResults] of Object.entries(results.facets || {})) {
  console.log(`${facetName}:`);
  for (const facet of facetResults) {
    console.log(`  ${facet.value}: ${facet.count}`);
  }
}
```

## Autocomplete and Suggestions

```typescript
// Create suggester in index
const index: SearchIndex = {
  name: "products",
  fields: [...],
  suggesters: [
    { name: "sg", sourceFields: ["title", "description"] },
  ],
};

// Autocomplete
const autocomplete = await searchClient.autocomplete("wid", "sg", {
  mode: "twoTerms",
  top: 5,
});

// Suggestions
const suggestions = await searchClient.suggest("wid", "sg", {
  select: ["title"],
  top: 5,
});
```

## Batch Operations

```typescript
// Batch upload, merge, delete
const batch = [
  { upload: { id: "1", title: "New Item" } },
  { merge: { id: "2", title: "Updated Title" } },
  { delete: { id: "3" } },
];

const result = await searchClient.indexDocuments({ actions: batch });
```

## Key Types

```typescript
import {
  SearchClient,
  SearchIndexClient,
  SearchIndexerClient,
  SearchIndex,
  SearchField,
  SearchOptions,
  VectorSearch,
  SemanticSearch,
  SearchIterator,
} from "@azure/search-documents";
```

## Best Practices

1. **Use hybrid search** - Combine vector + text for best results
2. **Enable semantic ranking** - Improves relevance for natural language queries
3. **Batch document uploads** - Use `uploadDocuments` with arrays, not single docs
4. **Use filters for security** - Implement document-level security with filters
5. **Index incrementally** - Use `mergeOrUploadDocuments` for updates
6. **Monitor query performance** - Use `includeTotalCount: true` sparingly in production

````


### `references/semantic-ranking.md`

````markdown
# @azure/search-documents - Semantic Ranking Patterns

Reference documentation for semantic search and ranking in the Azure AI Search TypeScript SDK.

**Source**: [Azure SDK for JS - search-documents](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/search/search-documents)

---

## Installation

```bash
npm install @azure/search-documents @azure/identity
```

---

## SemanticSearchOptions Interface

```typescript
interface SemanticSearchOptions {
  /** Name of the semantic configuration to use */
  configurationName?: string;
  
  /** Error handling mode: "partial" or "fail" */
  errorMode?: SemanticErrorMode;
  
  /** Max wait time for semantic processing (ms) */
  maxWaitInMilliseconds?: number;
  
  /** Extract answers from documents */
  answers?: QueryAnswer;
  
  /** Extract captions with highlighting */
  captions?: QueryCaption;
  
  /** AI-generated query rewrites */
  queryRewrites?: QueryRewrites;
  
  /** Separate query for semantic reranking */
  semanticQuery?: string;
  
  /** Fields for semantic search */
  semanticFields?: string[];
  
  /** Enable debug info */
  debugMode?: QueryDebugMode;
}
```

---

## Basic Semantic Search

Enable semantic ranking with `queryType: "semantic"`:

```typescript
import { SearchClient } from "@azure/search-documents";
import { DefaultAzureCredential } from "@azure/identity";

interface Article {
  id: string;
  title: string;
  content: string;
  category: string;
}

const client = new SearchClient<Article>(
  process.env["AZURE_SEARCH_ENDPOINT"]!,
  "articles",
  new DefaultAzureCredential()
);

const results = await client.search("what causes climate change", {
  queryType: "semantic",
  semanticSearchOptions: {
    configurationName: "my-semantic-config",
  },
  select: ["id", "title", "content"],
  top: 10,
});

for await (const result of results.results) {
  console.log(`${result.document.title}`);
  console.log(`Reranker Score: ${result.rerankerScore}`);
}
```

---

## Extractive Captions

Extract representative passages with keyword highlighting:

```typescript
const results = await client.search("renewable energy benefits", {
  queryType: "semantic",
  semanticSearchOptions: {
    configurationName: "my-semantic-config",
    captions: {
      captionType: "extractive",
      highlight: true,
    },
  },
});

for await (const result of results.results) {
  console.log(`Title: ${result.document.title}`);
  
  // Access captions
  result.captions?.forEach((caption) => {
    console.log(`Caption: ${caption.text}`);
    console.log(`Highlighted: ${caption.highlights}`); // With <em> tags
  });
}
```

### Caption Options

```typescript
interface QueryCaption {
  /** Caption type: "extractive" | "none" */
  captionType: QueryCaptionType;
  
  /** Include highlighting with <em> tags */
  highlight?: boolean;
}
```

---

## Extractive Answers

Extract direct answers for Q&A-style queries:

```typescript
const results = await client.search("what is the speed of light", {
  queryType: "semantic",
  semanticSearchOptions: {
    configurationName: "science-config",
    answers: {
      answerType: "extractive",
      count: 3,           // Number of answers to extract
      threshold: 0.7,     // Minimum confidence threshold
    },
  },
});

// Access answers at the result set level
if (results.answers && results.answers.length > 0) {
  console.log("Top Answers:");
  results.answers.forEach((answer, i) => {
    console.log(`${i + 1}. ${answer.text}`);
    console.log(`   Highlights: ${answer.highlights}`);
    console.log(`   Score: ${answer.score}`);
    console.log(`   Document: ${answer.key}`);
  });
}
```

### Answer Options

```typescript
interface QueryAnswer {
  /** Answer type: "extractive" | "none" */
  answerType: QueryAnswerType;
  
  /** Number of answers to return (1-10) */
  count?: number;
  
  /** Minimum confidence threshold (0-1) */
  threshold?: number;
}
```

---

## Query Rewrites

Enable AI-generated query rewrites for better recall:

```typescript
const results = await client.search("ML algorithms", {
  queryType: "semantic",
  semanticSearchOptions: {
    configurationName: "tech-config",
    queryRewrites: {
      rewritesType: "generative",
      count: 3, // Number of rewrites to generate
    },
  },
});

// Access generated rewrites
if (results.queryRewrites && results.queryRewrites.length > 0) {
  console.log("Query Rewrites:");
  results.queryRewrites.forEach((rewrite) => {
    console.log(`- ${rewrite}`);
  });
}
```

---

## Semantic + Hybrid Search

Combine semantic ranking with vector search for best results:

```typescript
const results = await client.search("climate change solutions", {
  // Enable semantic reranking
  queryType: "semantic",
  semanticSearchOptions: {
    configurationName: "articles-semantic",
    captions: { captionType: "extractive", highlight: true },
    answers: { answerType: "extractive", count: 3 },
  },
  
  // Add vector search
  vectorSearchOptions: {
    queries: [
      {
        kind: "text",
        text: "climate change solutions",
        kNearestNeighborsCount: 50,
        fields: ["contentVector"],
      },
    ],
  },
  
  // Keyword search fields
  searchFields: ["title", "content"],
  
  top: 10,
  select: ["id", "title", "content", "category"],
});

// Results are:
// 1. Retrieved via hybrid (keyword + vector)
// 2. Reranked by semantic model
// 3. Enriched with captions and answers
```

---

## Semantic Query Override

Use different queries for retrieval vs. semantic reranking:

```typescript
const results = await client.search("ML", {
  // "ML" used for keyword matching
  queryType: "semantic",
  semanticSearchOptions: {
    configurationName: "tech-config",
    // Full question used for semantic reranking
    semanticQuery: "What are the best machine learning algorithms for classification?",
    captions: { captionType: "extractive" },
  },
});
```

---

## Debug Mode

Get detailed information about semantic processing:

```typescript
const results = await client.search("quantum computing applications", {
  queryType: "semantic",
  semanticSearchOptions: {
    configurationName: "science-config",
    debugMode: "semantic", // "disabled" | "speller" | "semantic" | "all"
  },
});

for await (const result of results.results) {
  // Access debug info per document
  console.log(`Document: ${result.document.title}`);
  console.log(`Debug Info:`, result.documentDebugInfo);
}
```

---

## Error Handling

Control behavior when semantic processing fails:

```typescript
const results = await client.search("complex query here", {
  queryType: "semantic",
  semanticSearchOptions: {
    configurationName: "my-config",
    
    // "partial" - Return results without semantic enrichment on failure
    // "fail" - Return error if semantic processing fails
    errorMode: "partial",
    
    // Timeout for semantic processing
    maxWaitInMilliseconds: 5000,
  },
});
```

---

## Index Semantic Configuration

Define semantic configuration in your index:

```typescript
import { SearchIndexClient, SearchIndex } from "@azure/search-documents";

const indexClient = new SearchIndexClient(endpoint, credential);

const index: SearchIndex = {
  name: "articles",
  fields: [
    { name: "id", type: "Edm.String", key: true },
    { name: "title", type: "Edm.String", searchable: true },
    { name: "content", type: "Edm.String", searchable: true },
    { name: "summary", type: "Edm.String", searchable: true },
    { name: "category", type: "Edm.String", filterable: true },
  ],
  semanticSearch: {
    configurations: [
      {
        name: "articles-semantic",
        prioritizedFields: {
          // Title field (highest priority for reranking)
          titleField: {
            fieldName: "title",
          },
          // Content fields (used for caption/answer extraction)
          contentFields: [
            { fieldName: "content" },
            { fieldName: "summary" },
          ],
          // Keyword fields (boost exact matches)
          keywordsFields: [
            { fieldName: "category" },
          ],
        },
      },
    ],
    defaultConfiguration: "articles-semantic",
  },
};

await indexClient.createOrUpdateIndex(index);
```

---

## Complete Example

```typescript
import { SearchClient } from "@azure/search-documents";
import { DefaultAzureCredential } from "@azure/identity";

interface Document {
  id: string;
  title: string;
  content: string;
  contentVector: number[];
  category: string;
}

async function semanticHybridSearch(query: string) {
  const client = new SearchClient<Document>(
    process.env["AZURE_SEARCH_ENDPOINT"]!,
    "knowledge-base",
    new DefaultAzureCredential()
  );

  const results = await client.search(query, {
    queryType: "semantic",
    semanticSearchOptions: {
      configurationName: "default-semantic",
      captions: { captionType: "extractive", highlight: true },
      answers: { answerType: "extractive", count: 3, threshold: 0.7 },
    },
    vectorSearchOptions: {
      queries: [
        {
          kind: "text",
          text: query,
          kNearestNeighborsCount: 50,
          fields: ["contentVector"],
        },
      ],
    },
    searchFields: ["title", "content"],
    top: 10,
    select: ["id", "title", "content", "category"],
  });

  // Process answers first (highest quality extracts)
  const response: {
    answers: Array<{ text: string; score: number; documentId: string }>;
    results: Array<{
      document: Document;
      score: number;
      rerankerScore: number;
      captions: string[];
    }>;
  } = {
    answers: [],
    results: [],
  };

  // Extract answers
  if (results.answers) {
    response.answers = results.answers.map((a) => ({
      text: a.highlights || a.text || "",
      score: a.score ?? 0,
      documentId: a.key || "",
    }));
  }

  // Extract results with captions
  for await (const result of results.results) {
    response.results.push({
      document: result.document,
      score: result.score ?? 0,
      rerankerScore: result.rerankerScore ?? 0,
      captions: result.captions?.map((c) => c.highlights || c.text || "") ?? [],
    });
  }

  return response;
}

// Usage
const response = await semanticHybridSearch("how does photosynthesis work");

console.log("=== Direct Answers ===");
response.answers.forEach((a, i) => {
  console.log(`${i + 1}. ${a.text} (score: ${a.score})`);
});

console.log("\n=== Search Results ===");
response.results.forEach((r, i) => {
  console.log(`${i + 1}. ${r.document.title}`);
  console.log(`   Reranker Score: ${r.rerankerScore}`);
  console.log(`   Caption: ${r.captions[0] || "N/A"}`);
});
```

---

## Best Practices

1. **Use extractive answers** - For Q&A scenarios, answers provide direct responses
2. **Enable captions** - They provide relevant snippets without returning full content
3. **Combine with hybrid search** - Semantic + vector + keyword yields best results
4. **Set appropriate thresholds** - Filter low-confidence answers
5. **Configure semantic fields** - Prioritize title > content > keywords
6. **Use partial error mode** - Gracefully degrade when semantic processing fails
7. **Monitor latency** - Semantic processing adds ~100-300ms latency

---

## See Also

- [Vector Search Patterns](./vector-search.md) - Combine with vector search
- [Official Documentation](https://learn.microsoft.com/azure/search/semantic-search-overview)

````


### `references/vector-search.md`

````markdown
# @azure/search-documents - Vector Search Patterns

Reference documentation for vector search in the Azure AI Search TypeScript SDK.

**Source**: [Azure SDK for JS - search-documents](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/search/search-documents)

---

## Installation

```bash
npm install @azure/search-documents @azure/identity
```

---

## Client Setup

```typescript
import { SearchClient, SearchIndexClient } from "@azure/search-documents";
import { DefaultAzureCredential } from "@azure/identity";

const credential = new DefaultAzureCredential();
const endpoint = process.env["AZURE_SEARCH_ENDPOINT"]!;

// For searching
const searchClient = new SearchClient<MyDocument>(
  endpoint,
  "my-index",
  credential
);

// For index management
const indexClient = new SearchIndexClient(endpoint, credential);
```

---

## VectorSearchOptions Interface

```typescript
interface VectorSearchOptions {
  /** Vector queries to execute */
  queries?: VectorQuery[];
  
  /** Filter mode for vector queries */
  filterMode?: VectorFilterMode; // "preFilter" | "postFilter"
}

type VectorQuery = 
  | VectorizedQuery      // Pre-computed vector
  | VectorizableTextQuery // Text to be vectorized
  | VectorizableImageUrlQuery
  | VectorizableImageBinaryQuery;
```

---

## Basic Vector Search

Search using a pre-computed embedding vector:

```typescript
import { SearchClient } from "@azure/search-documents";

interface Product {
  id: string;
  name: string;
  description: string;
  descriptionVector: number[];
}

const searchClient = new SearchClient<Product>(endpoint, "products", credential);

// Your embedding (e.g., from OpenAI, Azure OpenAI, etc.)
const queryVector = await getEmbedding("comfortable running shoes");

const results = await searchClient.search("*", {
  vectorSearchOptions: {
    queries: [
      {
        kind: "vector",
        vector: queryVector,
        kNearestNeighborsCount: 10,
        fields: ["descriptionVector"],
      },
    ],
  },
  select: ["id", "name", "description"],
});

for await (const result of results.results) {
  console.log(`${result.document.name} (score: ${result.score})`);
}
```

---

## Integrated Vectorization (Text)

Let Azure AI Search vectorize the query text automatically:

```typescript
const results = await searchClient.search("*", {
  vectorSearchOptions: {
    queries: [
      {
        kind: "text",
        text: "comfortable running shoes",
        kNearestNeighborsCount: 10,
        fields: ["descriptionVector"],
      },
    ],
  },
});
```

> **Note**: Requires a vectorizer configured on the index.

---

## Hybrid Search (Text + Vector)

Combine keyword search with vector search for better results:

```typescript
const results = await searchClient.search("running shoes", {
  // Keyword search component
  searchFields: ["name", "description"],
  
  // Vector search component
  vectorSearchOptions: {
    queries: [
      {
        kind: "text",
        text: "comfortable running shoes",
        kNearestNeighborsCount: 50,
        fields: ["descriptionVector"],
      },
    ],
  },
  
  // Return top 10 after RRF fusion
  top: 10,
  select: ["id", "name", "description"],
});
```

---

## Multi-Vector Search

Search across multiple vector fields simultaneously:

```typescript
const results = await searchClient.search("*", {
  vectorSearchOptions: {
    queries: [
      // Search title embeddings
      {
        kind: "text",
        text: "machine learning",
        kNearestNeighborsCount: 50,
        fields: ["titleVector"],
        weight: 2.0, // Boost title matches
      },
      // Search content embeddings
      {
        kind: "text",
        text: "machine learning",
        kNearestNeighborsCount: 50,
        fields: ["contentVector"],
        weight: 1.0,
      },
    ],
  },
  top: 10,
});
```

---

## Vector Filtering

### Pre-Filter (Default)

Apply filters before vector search - smaller search space, faster:

```typescript
const results = await searchClient.search("*", {
  filter: "category eq 'Electronics' and price lt 500",
  vectorSearchOptions: {
    filterMode: "preFilter", // Default
    queries: [
      {
        kind: "text",
        text: "wireless headphones",
        kNearestNeighborsCount: 10,
        fields: ["descriptionVector"],
      },
    ],
  },
});
```

### Post-Filter

Apply filters after vector search - ensures k results are found first:

```typescript
const results = await searchClient.search("*", {
  filter: "category eq 'Electronics'",
  vectorSearchOptions: {
    filterMode: "postFilter",
    queries: [
      {
        kind: "text",
        text: "wireless headphones",
        kNearestNeighborsCount: 50, // Get more, then filter
        fields: ["descriptionVector"],
      },
    ],
  },
  top: 10,
});
```

### Per-Query Filter Override

Apply different filters to different vector queries:

```typescript
const results = await searchClient.search("*", {
  filter: "inStock eq true", // Global filter
  vectorSearchOptions: {
    queries: [
      {
        kind: "text",
        text: "premium headphones",
        kNearestNeighborsCount: 20,
        fields: ["descriptionVector"],
        filterOverride: "category eq 'Audio' and price gt 200", // Override for this query
      },
    ],
  },
});
```

---

## Vector Thresholds

Set minimum similarity thresholds:

```typescript
const results = await searchClient.search("*", {
  vectorSearchOptions: {
    queries: [
      {
        kind: "text",
        text: "wireless headphones",
        kNearestNeighborsCount: 50,
        fields: ["descriptionVector"],
        threshold: {
          kind: "vectorSimilarity",
          value: 0.8, // Only results with similarity >= 0.8
        },
      },
    ],
  },
});
```

---

## Exhaustive Search

Force brute-force search instead of approximate (HNSW):

```typescript
const results = await searchClient.search("*", {
  vectorSearchOptions: {
    queries: [
      {
        kind: "text",
        text: "specific product query",
        kNearestNeighborsCount: 10,
        fields: ["descriptionVector"],
        exhaustive: true, // Slower but more accurate
      },
    ],
  },
});
```

---

## Index Configuration for Vector Search

Create an index with vector search capabilities:

```typescript
import { SearchIndexClient, SearchIndex } from "@azure/search-documents";

const indexClient = new SearchIndexClient(endpoint, credential);

const index: SearchIndex = {
  name: "products",
  fields: [
    { name: "id", type: "Edm.String", key: true },
    { name: "name", type: "Edm.String", searchable: true },
    { name: "description", type: "Edm.String", searchable: true },
    { name: "category", type: "Edm.String", filterable: true, facetable: true },
    { name: "price", type: "Edm.Double", filterable: true, sortable: true },
    {
      name: "descriptionVector",
      type: "Collection(Edm.Single)",
      searchable: true,
      vectorSearchDimensions: 1536,
      vectorSearchProfileName: "vector-profile",
    },
  ],
  vectorSearch: {
    algorithms: [
      {
        name: "hnsw-algorithm",
        kind: "hnsw",
        parameters: {
          m: 4,
          efConstruction: 400,
          efSearch: 500,
          metric: "cosine",
        },
      },
    ],
    profiles: [
      {
        name: "vector-profile",
        algorithmConfigurationName: "hnsw-algorithm",
        vectorizerName: "openai-vectorizer", // Optional: for integrated vectorization
      },
    ],
    vectorizers: [
      {
        name: "openai-vectorizer",
        kind: "azureOpenAI",
        azureOpenAIParameters: {
          resourceUri: process.env["AZURE_OPENAI_ENDPOINT"]!,
          deploymentId: "text-embedding-ada-002",
          modelName: "text-embedding-ada-002",
        },
      },
    ],
  },
};

await indexClient.createOrUpdateIndex(index);
```

---

## Complete Hybrid Search Example

```typescript
import { SearchClient } from "@azure/search-documents";
import { DefaultAzureCredential } from "@azure/identity";

interface Product {
  id: string;
  name: string;
  description: string;
  category: string;
  price: number;
}

async function hybridSearch(query: string, category?: string) {
  const client = new SearchClient<Product>(
    process.env["AZURE_SEARCH_ENDPOINT"]!,
    "products",
    new DefaultAzureCredential()
  );

  const filter = category ? `category eq '${category}'` : undefined;

  const results = await client.search(query, {
    filter,
    searchFields: ["name", "description"],
    vectorSearchOptions: {
      queries: [
        {
          kind: "text",
          text: query,
          kNearestNeighborsCount: 50,
          fields: ["descriptionVector"],
        },
      ],
    },
    top: 10,
    select: ["id", "name", "description", "category", "price"],
  });

  const items: Array<{ document: Product; score: number }> = [];
  
  for await (const result of results.results) {
    items.push({
      document: result.document,
      score: result.score ?? 0,
    });
  }

  return items;
}

// Usage
const results = await hybridSearch("wireless noise canceling", "Electronics");
results.forEach((r) => {
  console.log(`${r.document.name}: $${r.document.price} (score: ${r.score})`);
});
```

---

## Best Practices

1. **Use hybrid search** - Combining keyword + vector typically outperforms either alone
2. **Tune kNearestNeighborsCount** - Higher values increase recall but slow down search
3. **Use pre-filtering** - When filter selectivity is high (filters out most documents)
4. **Use post-filtering** - When you need exactly k vector matches, then filter
5. **Set appropriate thresholds** - Filter out low-quality matches
6. **Weight vector queries** - Boost more relevant vector fields
7. **Monitor index metrics** - Track search latency and recall

---

## See Also

- [Semantic Ranking](./semantic-ranking.md) - Combine with semantic reranking
- [Official Samples](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/search/search-documents/samples)

````
