or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

data-management.mdindex-management.mdindex.mdmilvus-client.mdorm-collection.mdsearch-operations.mdtypes-enums.mduser-management.mdutility-functions.md

search-operations.mddocs/

0

# Search Operations

1

2

PyMilvus provides comprehensive search capabilities including vector similarity search, hybrid multi-vector search, scalar filtering, and advanced result handling. This covers single-vector ANN search, multi-vector hybrid search with reranking, and sophisticated result processing.

3

4

## Basic Vector Search

5

6

### Single Vector Search

7

8

```python { .api }

9

from pymilvus import MilvusClient

10

11

client = MilvusClient()

12

13

def search(

14

collection_name: str,

15

data: Union[List[List[float]], List[Dict]],

16

anns_field: str = "vector",

17

search_params: Optional[Dict] = None,

18

limit: int = 10,

19

expr: Optional[str] = None,

20

output_fields: Optional[List[str]] = None,

21

partition_names: Optional[List[str]] = None,

22

round_decimal: int = -1,

23

timeout: Optional[float] = None,

24

consistency_level: Optional[str] = None,

25

**kwargs

26

) -> List[List[Dict[str, Any]]]

27

```

28

29

**Parameters:**

30

- `data`: Query vectors as list of float lists or dict with vector field

31

- `anns_field`: Name of vector field to search against

32

- `search_params`: Algorithm-specific parameters (e.g., {"nprobe": 16})

33

- `limit`: Maximum results per query vector

34

- `expr`: Boolean filter expression

35

- `output_fields`: Fields to include in results

36

- `partition_names`: Target partitions for search

37

- `round_decimal`: Precision for distance values (-1 for no rounding)

38

- `consistency_level`: "Strong", "Eventually", "Bounded", "Session"

39

40

**Examples:**

41

```python

42

# Basic similarity search

43

query_vector = [0.1, 0.2, 0.3] * 256

44

results = client.search(

45

collection_name="documents",

46

data=[query_vector],

47

limit=5,

48

output_fields=["id", "title", "content"]

49

)

50

51

# Search with filtering

52

results = client.search(

53

collection_name="products",

54

data=[embedding],

55

expr="category == 'electronics' and price < 1000",

56

limit=10,

57

output_fields=["id", "name", "price", "description"]

58

)

59

60

# Multi-query search

61

query_vectors = [[0.1] * 768, [0.2] * 768, [0.3] * 768]

62

results = client.search(

63

collection_name="embeddings",

64

data=query_vectors,

65

search_params={"nprobe": 32, "ef": 64},

66

limit=20

67

)

68

69

# Search specific partitions

70

results = client.search(

71

collection_name="time_series",

72

data=[query_vector],

73

partition_names=["2024_q1", "2024_q2"],

74

expr="status == 'active'",

75

limit=15

76

)

77

```

78

79

### Search Parameters by Index Type

80

81

```python { .api }

82

# FLAT index (exact search)

83

flat_params = {} # No additional parameters needed

84

85

# IVF_FLAT index

86

ivf_flat_params = {

87

"nprobe": 16 # Number of clusters to search (1 to nlist)

88

}

89

90

# IVF_PQ index

91

ivf_pq_params = {

92

"nprobe": 32, # Number of clusters to search

93

}

94

95

# HNSW index

96

hnsw_params = {

97

"ef": 64 # Search scope (ef >= limit, higher = more accurate but slower)

98

}

99

100

# Example usage

101

results = client.search(

102

"hnsw_collection",

103

data=[query_vector],

104

search_params=hnsw_params,

105

limit=10

106

)

107

```

108

109

## Hybrid Multi-Vector Search

110

111

### AnnSearchRequest

112

113

```python { .api }

114

from pymilvus import AnnSearchRequest

115

116

def __init__(

117

self,

118

data: List,

119

anns_field: str,

120

param: Dict,

121

limit: int,

122

expr: Optional[str] = None,

123

partition_names: Optional[List[str]] = None,

124

ignore_growing: bool = False

125

)

126

```

127

128

**Parameters:**

129

- `data`: Query vectors for this field

130

- `anns_field`: Vector field name to search

131

- `param`: Search parameters including metric_type and algorithm params

132

- `limit`: Maximum results for this search request

133

- `expr`: Filter expression for this search

134

- `partition_names`: Target partitions

135

- `ignore_growing`: Skip growing segments for consistency

136

137

### Hybrid Search with Reranking

138

139

```python { .api }

140

def hybrid_search(

141

collection_name: str,

142

reqs: List[AnnSearchRequest],

143

ranker: Union[RRFRanker, WeightedRanker],

144

limit: int = 10,

145

partition_names: Optional[List[str]] = None,

146

output_fields: Optional[List[str]] = None,

147

timeout: Optional[float] = None,

148

round_decimal: int = -1,

149

**kwargs

150

) -> List[List[Dict[str, Any]]]

151

```

152

153

**Parameters:**

154

- `reqs`: List of AnnSearchRequest objects for different vector fields

155

- `ranker`: Ranking algorithm to combine results

156

- `limit`: Final number of results after reranking

157

158

### RRF (Reciprocal Rank Fusion) Ranker

159

160

```python { .api }

161

from pymilvus import RRFRanker

162

163

def __init__(self, k: int = 60)

164

```

165

166

**Parameters:**

167

- `k`: RRF parameter controlling rank fusion (default: 60)

168

169

RRF Formula: `score = Σ(1 / (k + rank_i))` for each search result

170

171

### WeightedRanker

172

173

```python { .api }

174

from pymilvus import WeightedRanker

175

176

def __init__(self, *nums, norm_score: bool = True)

177

```

178

179

**Parameters:**

180

- `*nums`: Weight values for each search request (must match number of requests)

181

- `norm_score`: Whether to normalize scores before weighting

182

183

### Hybrid Search Examples

184

185

```python { .api }

186

from pymilvus import AnnSearchRequest, RRFRanker, WeightedRanker

187

188

# Dense + Sparse hybrid search

189

dense_req = AnnSearchRequest(

190

data=dense_vectors, # [[0.1, 0.2, ...]]

191

anns_field="dense_embedding",

192

param={

193

"metric_type": "L2",

194

"params": {"nprobe": 16}

195

},

196

limit=100,

197

expr="status == 'published'"

198

)

199

200

sparse_req = AnnSearchRequest(

201

data=sparse_vectors, # Sparse vectors from BM25/TF-IDF

202

anns_field="sparse_embedding",

203

param={

204

"metric_type": "IP", # Inner Product for sparse

205

"params": {"drop_ratio_build": 0.2}

206

},

207

limit=100,

208

expr="status == 'published'"

209

)

210

211

# RRF hybrid search - good for combining different vector types

212

rrf_results = client.hybrid_search(

213

collection_name="hybrid_documents",

214

reqs=[dense_req, sparse_req],

215

ranker=RRFRanker(k=60),

216

limit=10,

217

output_fields=["id", "title", "content", "score"]

218

)

219

220

# Weighted hybrid search - control contribution of each vector type

221

weighted_results = client.hybrid_search(

222

collection_name="hybrid_documents",

223

reqs=[dense_req, sparse_req],

224

ranker=WeightedRanker(0.7, 0.3, norm_score=True), # 70% dense, 30% sparse

225

limit=10,

226

output_fields=["id", "title", "content"]

227

)

228

229

# Multi-modal search (text + image + audio)

230

text_req = AnnSearchRequest(

231

data=text_embeddings,

232

anns_field="text_vector",

233

param={"metric_type": "COSINE", "params": {"nprobe": 20}},

234

limit=50

235

)

236

237

image_req = AnnSearchRequest(

238

data=image_embeddings,

239

anns_field="image_vector",

240

param={"metric_type": "L2", "params": {"ef": 100}},

241

limit=50

242

)

243

244

audio_req = AnnSearchRequest(

245

data=audio_embeddings,

246

anns_field="audio_vector",

247

param={"metric_type": "IP", "params": {"nprobe": 10}},

248

limit=50

249

)

250

251

multimodal_results = client.hybrid_search(

252

collection_name="multimodal_content",

253

reqs=[text_req, image_req, audio_req],

254

ranker=WeightedRanker(0.5, 0.3, 0.2), # Text dominant

255

limit=15,

256

output_fields=["id", "title", "type", "metadata"]

257

)

258

```

259

260

### Advanced Hybrid Search Patterns

261

262

```python { .api }

263

# Query-time vector generation with different strategies per field

264

def multi_strategy_search(query_text: str, query_image_path: str):

265

# Generate embeddings for different modalities

266

text_dense = text_encoder.encode(query_text)

267

text_sparse = bm25_encoder.encode(query_text)

268

image_vector = image_encoder.encode(query_image_path)

269

270

# Different search strategies

271

requests = [

272

# Semantic text search

273

AnnSearchRequest(

274

data=[text_dense],

275

anns_field="text_dense_vector",

276

param={"metric_type": "COSINE", "params": {"ef": 200}},

277

limit=200,

278

expr="content_type in ['article', 'blog']"

279

),

280

281

# Lexical text search

282

AnnSearchRequest(

283

data=[text_sparse],

284

anns_field="text_sparse_vector",

285

param={"metric_type": "IP"},

286

limit=200,

287

expr="content_type in ['article', 'blog']"

288

),

289

290

# Visual similarity

291

AnnSearchRequest(

292

data=[image_vector],

293

anns_field="image_vector",

294

param={"metric_type": "L2", "params": {"nprobe": 50}},

295

limit=100,

296

expr="content_type in ['image', 'video']"

297

)

298

]

299

300

# Combine with RRF for balanced results

301

return client.hybrid_search(

302

"multimedia_collection",

303

reqs=requests,

304

ranker=RRFRanker(k=100),

305

limit=20,

306

output_fields=["id", "title", "content_type", "url", "metadata"]

307

)

308

```

309

310

## Search Result Handling

311

312

### SearchResult Structure

313

314

```python { .api }

315

# SearchResult contains results for all query vectors

316

from pymilvus.client.search_result import SearchResult, Hits, Hit

317

318

class SearchResult:

319

hits: List[Hits] # One Hits object per query vector

320

distances: List[List[float]] # Nested distances [query][result]

321

ids: List[List] # Nested primary keys [query][result]

322

323

def __len__(self) -> int # Number of queries

324

def __getitem__(self, index: int) -> Hits # Access query results

325

```

326

327

### Hits Object

328

329

```python { .api }

330

class Hits:

331

ids: List # Primary key values for this query

332

distances: List[float] # Distance/similarity scores

333

334

def __len__(self) -> int # Number of results

335

def __getitem__(self, index: int) -> Hit # Access individual result

336

def __iter__(self) -> Iterator[Hit] # Iterate over results

337

```

338

339

### Hit Object

340

341

```python { .api }

342

class Hit:

343

id: Any # Primary key value

344

distance: float # Distance/similarity score

345

score: float # Alias for distance

346

entity: Dict[str, Any] # Returned field values

347

348

def get(self, field: str, default=None) -> Any # Get field with default

349

def to_dict(self) -> Dict[str, Any] # Convert to dictionary

350

```

351

352

### Result Processing Examples

353

354

```python { .api }

355

# Process search results

356

results = client.search(

357

"documents",

358

data=[query_vector],

359

limit=5,

360

output_fields=["id", "title", "content", "score"]

361

)

362

363

# Access first query results (single query)

364

first_query_hits = results[0]

365

print(f"Found {len(first_query_hits)} results")

366

367

# Process individual hits

368

for hit in first_query_hits:

369

print(f"Document ID: {hit.id}")

370

print(f"Similarity Score: {hit.score:.4f}")

371

print(f"Title: {hit.entity.get('title', 'No title')}")

372

print(f"Content: {hit.entity.get('content', '')[:100]}...")

373

print("---")

374

375

# Multi-query result processing

376

multi_results = client.search(

377

"products",

378

data=[vector1, vector2, vector3],

379

limit=10,

380

output_fields=["id", "name", "category", "price"]

381

)

382

383

for query_idx, hits in enumerate(multi_results):

384

print(f"Query {query_idx + 1} results:")

385

for rank, hit in enumerate(hits):

386

product_name = hit.entity.get('name', 'Unknown')

387

price = hit.entity.get('price', 0)

388

print(f" {rank + 1}. {product_name} - ${price:.2f} (score: {hit.score:.3f})")

389

```

390

391

### Advanced Result Analysis

392

393

```python { .api }

394

def analyze_search_results(results: SearchResult) -> Dict[str, Any]:

395

"""Analyze search result quality and distribution"""

396

analysis = {

397

"total_queries": len(results),

398

"query_stats": []

399

}

400

401

for query_idx, hits in enumerate(results):

402

if len(hits) == 0:

403

continue

404

405

scores = [hit.score for hit in hits]

406

query_analysis = {

407

"query_index": query_idx,

408

"result_count": len(hits),

409

"score_stats": {

410

"min": min(scores),

411

"max": max(scores),

412

"avg": sum(scores) / len(scores),

413

"range": max(scores) - min(scores)

414

},

415

"categories": {}

416

}

417

418

# Analyze result categories

419

for hit in hits:

420

category = hit.entity.get('category', 'unknown')

421

query_analysis["categories"][category] = query_analysis["categories"].get(category, 0) + 1

422

423

analysis["query_stats"].append(query_analysis)

424

425

return analysis

426

427

# Use analysis

428

search_results = client.search("products", [query_vector], limit=20, output_fields=["category"])

429

stats = analyze_search_results(search_results)

430

print(f"Search returned results across {len(stats['query_stats'][0]['categories'])} categories")

431

```

432

433

## Paginated Search with Iterators

434

435

### search_iterator

436

437

```python { .api }

438

def search_iterator(

439

collection_name: str,

440

data: Union[List[List[float]], List[Dict]],

441

anns_field: str = "vector",

442

batch_size: int = 1000,

443

limit: Optional[int] = None,

444

search_params: Optional[Dict] = None,

445

expr: Optional[str] = None,

446

output_fields: Optional[List[str]] = None,

447

**kwargs

448

) -> SearchIterator

449

```

450

451

**Parameters:**

452

- `batch_size`: Results per iteration batch

453

- `limit`: Total maximum results across all batches

454

455

```python { .api }

456

# Large-scale similarity search with pagination

457

iterator = client.search_iterator(

458

collection_name="large_embeddings",

459

data=[query_vector],

460

anns_field="embedding",

461

batch_size=1000,

462

limit=10000, # Process up to 10K results

463

output_fields=["id", "metadata", "score"],

464

expr="status == 'active'"

465

)

466

467

# Process results in batches

468

total_processed = 0

469

for batch in iterator:

470

# batch is a list of Hit objects

471

print(f"Processing batch of {len(batch)} results")

472

473

# Process each result in batch

474

for hit in batch:

475

# Custom processing logic

476

if hit.score > 0.8: # High similarity threshold

477

process_high_similarity(hit)

478

479

total_processed += 1

480

481

# Optional: stop early based on conditions

482

if total_processed >= 5000:

483

break

484

485

print(f"Total processed: {total_processed} results")

486

```

487

488

## Query Operations (Scalar Search)

489

490

### Basic Query

491

492

```python { .api }

493

def query(

494

collection_name: str,

495

filter: str,

496

output_fields: Optional[List[str]] = None,

497

partition_names: Optional[List[str]] = None,

498

limit: int = 16384,

499

offset: int = 0,

500

timeout: Optional[float] = None,

501

consistency_level: Optional[str] = None,

502

**kwargs

503

) -> List[Dict[str, Any]]

504

```

505

506

### Query Iterator

507

508

```python { .api }

509

def query_iterator(

510

collection_name: str,

511

filter: str,

512

output_fields: Optional[List[str]] = None,

513

batch_size: int = 1000,

514

limit: Optional[int] = None,

515

**kwargs

516

) -> QueryIterator

517

```

518

519

### Query Expression Syntax

520

521

```python { .api }

522

# Comparison operators

523

"age > 25"

524

"price <= 100.0"

525

"category == 'electronics'"

526

"status != 'inactive'"

527

528

# Logical operators

529

"age > 18 and age < 65"

530

"category == 'books' or category == 'ebooks'"

531

"not (status == 'deleted')"

532

533

# List operations

534

"category in ['electronics', 'computers', 'mobile']"

535

"tag_id not in [1, 2, 3]"

536

537

# JSON field queries

538

"metadata['color'] == 'red'"

539

"metadata['specs']['weight'] < 1.5"

540

"json_contains(metadata['tags'], 'premium')"

541

542

# Array field queries

543

"array_contains(tags, 'new')"

544

"array_contains_all(categories, ['tech', 'gadget'])"

545

"array_contains_any(features, ['bluetooth', 'wifi'])"

546

"array_length(tags) > 2"

547

548

# String operations

549

"title like 'Python%'" # Starts with 'Python'

550

"description like '%machine learning%'" # Contains 'machine learning'

551

552

# Complex expressions

553

"(category == 'books' and price < 50) or (category == 'ebooks' and price < 20)"

554

"json_contains(metadata['tags'], 'bestseller') and rating >= 4.5"

555

"array_contains(features, 'wireless') and price between 50 and 200"

556

```

557

558

### Query Examples

559

560

```python { .api }

561

# Basic filtering

562

products = client.query(

563

"products",

564

filter="category == 'electronics' and price < 500",

565

output_fields=["id", "name", "price", "rating"],

566

limit=50

567

)

568

569

# Complex JSON queries

570

documents = client.query(

571

"documents",

572

filter="metadata['author'] == 'Smith' and metadata['year'] >= 2020",

573

output_fields=["id", "title", "metadata"],

574

offset=100,

575

limit=25

576

)

577

578

# Array field filtering

579

articles = client.query(

580

"articles",

581

filter="array_contains_all(tags, ['AI', 'machine-learning']) and status == 'published'",

582

output_fields=["id", "title", "tags", "publish_date"]

583

)

584

585

# Paginated query processing

586

iterator = client.query_iterator(

587

"large_dataset",

588

filter="created_at > 1640995200", # After 2022-01-01

589

output_fields=["id", "data", "timestamp"],

590

batch_size=2000

591

)

592

593

for batch in iterator:

594

# Process each batch

595

process_data_batch(batch)

596

```

597

598

## Performance Optimization

599

600

### Search Parameter Tuning

601

602

```python { .api }

603

# HNSW index optimization

604

def optimize_hnsw_search(collection_name: str, query_vectors: List[List[float]], target_recall: float = 0.95):

605

"""Optimize HNSW search parameters for target recall"""

606

607

# Start with conservative parameters

608

base_ef = max(50, len(query_vectors[0])) # ef >= dimension recommended

609

610

# Test different ef values

611

ef_values = [base_ef, base_ef * 2, base_ef * 4]

612

613

best_params = None

614

best_latency = float('inf')

615

616

for ef in ef_values:

617

start_time = time.time()

618

results = client.search(

619

collection_name,

620

data=query_vectors,

621

search_params={"ef": ef},

622

limit=10

623

)

624

latency = time.time() - start_time

625

626

# In practice, measure recall against ground truth

627

recall = measure_recall(results, ground_truth) # Custom function

628

629

if recall >= target_recall and latency < best_latency:

630

best_params = {"ef": ef}

631

best_latency = latency

632

633

return best_params

634

635

# IVF index optimization

636

def optimize_ivf_search(collection_name: str, nlist: int):

637

"""Find optimal nprobe for IVF index"""

638

639

# Rule of thumb: nprobe = sqrt(nlist) to nlist/8

640

nprobe_candidates = [

641

max(1, int(nlist ** 0.5)), # sqrt(nlist)

642

max(1, nlist // 32),

643

max(1, nlist // 16),

644

max(1, nlist // 8)

645

]

646

647

best_nprobe = nprobe_candidates[0]

648

649

for nprobe in nprobe_candidates:

650

# Test search performance

651

start_time = time.time()

652

results = client.search(

653

collection_name,

654

data=[test_vector],

655

search_params={"nprobe": nprobe},

656

limit=100

657

)

658

latency = time.time() - start_time

659

660

print(f"nprobe={nprobe}, latency={latency:.4f}s")

661

662

# Choose based on latency/accuracy tradeoff

663

if latency < target_latency:

664

best_nprobe = nprobe

665

break

666

667

return {"nprobe": best_nprobe}

668

```

669

670

### Batch Search Optimization

671

672

```python { .api }

673

def batch_search_optimize(collection_name: str, query_vectors: List[List[float]], batch_size: int = 100):

674

"""Optimize batch search for large query sets"""

675

676

all_results = []

677

678

# Process queries in batches to optimize memory usage

679

for i in range(0, len(query_vectors), batch_size):

680

batch_vectors = query_vectors[i:i + batch_size]

681

682

batch_results = client.search(

683

collection_name,

684

data=batch_vectors,

685

search_params={"nprobe": 16}, # Adjust based on index

686

limit=10,

687

round_decimal=4 # Reduce precision for network efficiency

688

)

689

690

all_results.extend(batch_results)

691

692

# Optional: progress tracking

693

print(f"Processed {min(i + batch_size, len(query_vectors))}/{len(query_vectors)} queries")

694

695

return all_results

696

```

697

698

PyMilvus search operations provide powerful capabilities for similarity search, hybrid retrieval, and scalar filtering, enabling sophisticated search applications with fine-tuned performance optimization.