📦 deps(thirdparty): update snapshots
This commit is contained in:
+48
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: data-quality-frameworks
|
||||
description: "Implement data quality validation with Great Expectations, dbt tests, and data contracts. Use when building data quality pipelines, implementing validation rules, or establishing data contracts."
|
||||
risk: unknown
|
||||
source: community
|
||||
date_added: "2026-02-27"
|
||||
---
|
||||
|
||||
# Data Quality Frameworks
|
||||
|
||||
Production patterns for implementing data quality with Great Expectations, dbt tests, and data contracts to ensure reliable data pipelines.
|
||||
|
||||
## Use this skill when
|
||||
|
||||
- Implementing data quality checks in pipelines
|
||||
- Setting up Great Expectations validation
|
||||
- Building comprehensive dbt test suites
|
||||
- Establishing data contracts between teams
|
||||
- Monitoring data quality metrics
|
||||
- Automating data validation in CI/CD
|
||||
|
||||
## Do not use this skill when
|
||||
|
||||
- The data sources are undefined or unavailable
|
||||
- You cannot modify validation rules or schemas
|
||||
- The task is unrelated to data quality or contracts
|
||||
|
||||
## Instructions
|
||||
|
||||
- Identify critical datasets and quality dimensions.
|
||||
- Define expectations/tests and contract rules.
|
||||
- Automate validation in CI/CD and schedule checks.
|
||||
- Set alerting, ownership, and remediation steps.
|
||||
- If detailed patterns are required, open `resources/implementation-playbook.md`.
|
||||
|
||||
## Safety
|
||||
|
||||
- Avoid blocking critical pipelines without a fallback plan.
|
||||
- Handle sensitive data securely in validation outputs.
|
||||
|
||||
## Resources
|
||||
|
||||
- `resources/implementation-playbook.md` for detailed frameworks, templates, and examples.
|
||||
|
||||
## Limitations
|
||||
- Use this skill only when the task clearly matches the scope described above.
|
||||
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
|
||||
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
|
||||
+573
@@ -0,0 +1,573 @@
|
||||
# Data Quality Frameworks Implementation Playbook
|
||||
|
||||
This file contains detailed patterns, checklists, and code samples referenced by the skill.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. Data Quality Dimensions
|
||||
|
||||
| Dimension | Description | Example Check |
|
||||
|-----------|-------------|---------------|
|
||||
| **Completeness** | No missing values | `expect_column_values_to_not_be_null` |
|
||||
| **Uniqueness** | No duplicates | `expect_column_values_to_be_unique` |
|
||||
| **Validity** | Values in expected range | `expect_column_values_to_be_in_set` |
|
||||
| **Accuracy** | Data matches reality | Cross-reference validation |
|
||||
| **Consistency** | No contradictions | `expect_column_pair_values_A_to_be_greater_than_B` |
|
||||
| **Timeliness** | Data is recent | `expect_column_max_to_be_between` |
|
||||
|
||||
### 2. Testing Pyramid for Data
|
||||
|
||||
```
|
||||
/\
|
||||
/ \ Integration Tests (cross-table)
|
||||
/────\
|
||||
/ \ Unit Tests (single column)
|
||||
/────────\
|
||||
/ \ Schema Tests (structure)
|
||||
/────────────\
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Great Expectations Setup
|
||||
|
||||
```bash
|
||||
# Install
|
||||
pip install great_expectations
|
||||
|
||||
# Initialize project
|
||||
great_expectations init
|
||||
|
||||
# Create datasource
|
||||
great_expectations datasource new
|
||||
```
|
||||
|
||||
```python
|
||||
# great_expectations/checkpoints/daily_validation.yml
|
||||
import great_expectations as gx
|
||||
|
||||
# Create context
|
||||
context = gx.get_context()
|
||||
|
||||
# Create expectation suite
|
||||
suite = context.add_expectation_suite("orders_suite")
|
||||
|
||||
# Add expectations
|
||||
suite.add_expectation(
|
||||
gx.expectations.ExpectColumnValuesToNotBeNull(column="order_id")
|
||||
)
|
||||
suite.add_expectation(
|
||||
gx.expectations.ExpectColumnValuesToBeUnique(column="order_id")
|
||||
)
|
||||
|
||||
# Validate
|
||||
results = context.run_checkpoint(checkpoint_name="daily_orders")
|
||||
```
|
||||
|
||||
## Patterns
|
||||
|
||||
### Pattern 1: Great Expectations Suite
|
||||
|
||||
```python
|
||||
# expectations/orders_suite.py
|
||||
import great_expectations as gx
|
||||
from great_expectations.core import ExpectationSuite
|
||||
from great_expectations.core.expectation_configuration import ExpectationConfiguration
|
||||
|
||||
def build_orders_suite() -> ExpectationSuite:
|
||||
"""Build comprehensive orders expectation suite"""
|
||||
|
||||
suite = ExpectationSuite(expectation_suite_name="orders_suite")
|
||||
|
||||
# Schema expectations
|
||||
suite.add_expectation(ExpectationConfiguration(
|
||||
expectation_type="expect_table_columns_to_match_set",
|
||||
kwargs={
|
||||
"column_set": ["order_id", "customer_id", "amount", "status", "created_at"],
|
||||
"exact_match": False # Allow additional columns
|
||||
}
|
||||
))
|
||||
|
||||
# Primary key
|
||||
suite.add_expectation(ExpectationConfiguration(
|
||||
expectation_type="expect_column_values_to_not_be_null",
|
||||
kwargs={"column": "order_id"}
|
||||
))
|
||||
suite.add_expectation(ExpectationConfiguration(
|
||||
expectation_type="expect_column_values_to_be_unique",
|
||||
kwargs={"column": "order_id"}
|
||||
))
|
||||
|
||||
# Foreign key
|
||||
suite.add_expectation(ExpectationConfiguration(
|
||||
expectation_type="expect_column_values_to_not_be_null",
|
||||
kwargs={"column": "customer_id"}
|
||||
))
|
||||
|
||||
# Categorical values
|
||||
suite.add_expectation(ExpectationConfiguration(
|
||||
expectation_type="expect_column_values_to_be_in_set",
|
||||
kwargs={
|
||||
"column": "status",
|
||||
"value_set": ["pending", "processing", "shipped", "delivered", "cancelled"]
|
||||
}
|
||||
))
|
||||
|
||||
# Numeric ranges
|
||||
suite.add_expectation(ExpectationConfiguration(
|
||||
expectation_type="expect_column_values_to_be_between",
|
||||
kwargs={
|
||||
"column": "amount",
|
||||
"min_value": 0,
|
||||
"max_value": 100000,
|
||||
"strict_min": True # amount > 0
|
||||
}
|
||||
))
|
||||
|
||||
# Date validity
|
||||
suite.add_expectation(ExpectationConfiguration(
|
||||
expectation_type="expect_column_values_to_be_dateutil_parseable",
|
||||
kwargs={"column": "created_at"}
|
||||
))
|
||||
|
||||
# Freshness - data should be recent
|
||||
suite.add_expectation(ExpectationConfiguration(
|
||||
expectation_type="expect_column_max_to_be_between",
|
||||
kwargs={
|
||||
"column": "created_at",
|
||||
"min_value": {"$PARAMETER": "now - timedelta(days=1)"},
|
||||
"max_value": {"$PARAMETER": "now"}
|
||||
}
|
||||
))
|
||||
|
||||
# Row count sanity
|
||||
suite.add_expectation(ExpectationConfiguration(
|
||||
expectation_type="expect_table_row_count_to_be_between",
|
||||
kwargs={
|
||||
"min_value": 1000, # Expect at least 1000 rows
|
||||
"max_value": 10000000
|
||||
}
|
||||
))
|
||||
|
||||
# Statistical expectations
|
||||
suite.add_expectation(ExpectationConfiguration(
|
||||
expectation_type="expect_column_mean_to_be_between",
|
||||
kwargs={
|
||||
"column": "amount",
|
||||
"min_value": 50,
|
||||
"max_value": 500
|
||||
}
|
||||
))
|
||||
|
||||
return suite
|
||||
```
|
||||
|
||||
### Pattern 2: Great Expectations Checkpoint
|
||||
|
||||
```yaml
|
||||
# great_expectations/checkpoints/orders_checkpoint.yml
|
||||
name: orders_checkpoint
|
||||
config_version: 1.0
|
||||
class_name: Checkpoint
|
||||
run_name_template: "%Y%m%d-%H%M%S-orders-validation"
|
||||
|
||||
validations:
|
||||
- batch_request:
|
||||
datasource_name: warehouse
|
||||
data_connector_name: default_inferred_data_connector_name
|
||||
data_asset_name: orders
|
||||
data_connector_query:
|
||||
index: -1 # Latest batch
|
||||
expectation_suite_name: orders_suite
|
||||
|
||||
action_list:
|
||||
- name: store_validation_result
|
||||
action:
|
||||
class_name: StoreValidationResultAction
|
||||
|
||||
- name: store_evaluation_parameters
|
||||
action:
|
||||
class_name: StoreEvaluationParametersAction
|
||||
|
||||
- name: update_data_docs
|
||||
action:
|
||||
class_name: UpdateDataDocsAction
|
||||
|
||||
# Slack notification on failure
|
||||
- name: send_slack_notification
|
||||
action:
|
||||
class_name: SlackNotificationAction
|
||||
slack_webhook: ${SLACK_WEBHOOK}
|
||||
notify_on: failure
|
||||
renderer:
|
||||
module_name: great_expectations.render.renderer.slack_renderer
|
||||
class_name: SlackRenderer
|
||||
```
|
||||
|
||||
```python
|
||||
# Run checkpoint
|
||||
import great_expectations as gx
|
||||
|
||||
context = gx.get_context()
|
||||
result = context.run_checkpoint(checkpoint_name="orders_checkpoint")
|
||||
|
||||
if not result.success:
|
||||
failed_expectations = [
|
||||
r for r in result.run_results.values()
|
||||
if not r.success
|
||||
]
|
||||
raise ValueError(f"Data quality check failed: {failed_expectations}")
|
||||
```
|
||||
|
||||
### Pattern 3: dbt Data Tests
|
||||
|
||||
```yaml
|
||||
# models/marts/core/_core__models.yml
|
||||
version: 2
|
||||
|
||||
models:
|
||||
- name: fct_orders
|
||||
description: Order fact table
|
||||
tests:
|
||||
# Table-level tests
|
||||
- dbt_utils.recency:
|
||||
datepart: day
|
||||
field: created_at
|
||||
interval: 1
|
||||
- dbt_utils.at_least_one
|
||||
- dbt_utils.expression_is_true:
|
||||
expression: "total_amount >= 0"
|
||||
|
||||
columns:
|
||||
- name: order_id
|
||||
description: Primary key
|
||||
tests:
|
||||
- unique
|
||||
- not_null
|
||||
|
||||
- name: customer_id
|
||||
description: Foreign key to dim_customers
|
||||
tests:
|
||||
- not_null
|
||||
- relationships:
|
||||
to: ref('dim_customers')
|
||||
field: customer_id
|
||||
|
||||
- name: order_status
|
||||
tests:
|
||||
- accepted_values:
|
||||
values: ['pending', 'processing', 'shipped', 'delivered', 'cancelled']
|
||||
|
||||
- name: total_amount
|
||||
tests:
|
||||
- not_null
|
||||
- dbt_utils.expression_is_true:
|
||||
expression: ">= 0"
|
||||
|
||||
- name: created_at
|
||||
tests:
|
||||
- not_null
|
||||
- dbt_utils.expression_is_true:
|
||||
expression: "<= current_timestamp"
|
||||
|
||||
- name: dim_customers
|
||||
columns:
|
||||
- name: customer_id
|
||||
tests:
|
||||
- unique
|
||||
- not_null
|
||||
|
||||
- name: email
|
||||
tests:
|
||||
- unique
|
||||
- not_null
|
||||
# Custom regex test
|
||||
- dbt_utils.expression_is_true:
|
||||
expression: "email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$'"
|
||||
```
|
||||
|
||||
### Pattern 4: Custom dbt Tests
|
||||
|
||||
```sql
|
||||
-- tests/generic/test_row_count_in_range.sql
|
||||
{% test row_count_in_range(model, min_count, max_count) %}
|
||||
|
||||
with row_count as (
|
||||
select count(*) as cnt from {{ model }}
|
||||
)
|
||||
|
||||
select cnt
|
||||
from row_count
|
||||
where cnt < {{ min_count }} or cnt > {{ max_count }}
|
||||
|
||||
{% endtest %}
|
||||
|
||||
-- Usage in schema.yml:
|
||||
-- tests:
|
||||
-- - row_count_in_range:
|
||||
-- min_count: 1000
|
||||
-- max_count: 10000000
|
||||
```
|
||||
|
||||
```sql
|
||||
-- tests/generic/test_sequential_values.sql
|
||||
{% test sequential_values(model, column_name, interval=1) %}
|
||||
|
||||
with lagged as (
|
||||
select
|
||||
{{ column_name }},
|
||||
lag({{ column_name }}) over (order by {{ column_name }}) as prev_value
|
||||
from {{ model }}
|
||||
)
|
||||
|
||||
select *
|
||||
from lagged
|
||||
where {{ column_name }} - prev_value != {{ interval }}
|
||||
and prev_value is not null
|
||||
|
||||
{% endtest %}
|
||||
```
|
||||
|
||||
```sql
|
||||
-- tests/singular/assert_orders_customers_match.sql
|
||||
-- Singular test: specific business rule
|
||||
|
||||
with orders_customers as (
|
||||
select distinct customer_id from {{ ref('fct_orders') }}
|
||||
),
|
||||
|
||||
dim_customers as (
|
||||
select customer_id from {{ ref('dim_customers') }}
|
||||
),
|
||||
|
||||
orphaned_orders as (
|
||||
select o.customer_id
|
||||
from orders_customers o
|
||||
left join dim_customers c using (customer_id)
|
||||
where c.customer_id is null
|
||||
)
|
||||
|
||||
select * from orphaned_orders
|
||||
-- Test passes if this returns 0 rows
|
||||
```
|
||||
|
||||
### Pattern 5: Data Contracts
|
||||
|
||||
```yaml
|
||||
# contracts/orders_contract.yaml
|
||||
apiVersion: datacontract.com/v1.0.0
|
||||
kind: DataContract
|
||||
metadata:
|
||||
name: orders
|
||||
version: 1.0.0
|
||||
owner: data-platform-team
|
||||
contact: data-team@company.com
|
||||
|
||||
info:
|
||||
title: Orders Data Contract
|
||||
description: Contract for order event data from the ecommerce platform
|
||||
purpose: Analytics, reporting, and ML features
|
||||
|
||||
servers:
|
||||
production:
|
||||
type: snowflake
|
||||
account: company.us-east-1
|
||||
database: ANALYTICS
|
||||
schema: CORE
|
||||
|
||||
terms:
|
||||
usage: Internal analytics only
|
||||
limitations: PII must not be exposed in downstream marts
|
||||
billing: Charged per query TB scanned
|
||||
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
order_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Unique order identifier
|
||||
required: true
|
||||
unique: true
|
||||
pii: false
|
||||
|
||||
customer_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Customer identifier
|
||||
required: true
|
||||
pii: true
|
||||
piiClassification: indirect
|
||||
|
||||
total_amount:
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 100000
|
||||
description: Order total in USD
|
||||
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Order creation timestamp
|
||||
required: true
|
||||
|
||||
status:
|
||||
type: string
|
||||
enum: [pending, processing, shipped, delivered, cancelled]
|
||||
description: Current order status
|
||||
|
||||
quality:
|
||||
type: SodaCL
|
||||
specification:
|
||||
checks for orders:
|
||||
- row_count > 0
|
||||
- missing_count(order_id) = 0
|
||||
- duplicate_count(order_id) = 0
|
||||
- invalid_count(status) = 0:
|
||||
valid values: [pending, processing, shipped, delivered, cancelled]
|
||||
- freshness(created_at) < 24h
|
||||
|
||||
sla:
|
||||
availability: 99.9%
|
||||
freshness: 1 hour
|
||||
latency: 5 minutes
|
||||
```
|
||||
|
||||
### Pattern 6: Automated Quality Pipeline
|
||||
|
||||
```python
|
||||
# quality_pipeline.py
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict, Any
|
||||
import great_expectations as gx
|
||||
from datetime import datetime
|
||||
|
||||
@dataclass
|
||||
class QualityResult:
|
||||
table: str
|
||||
passed: bool
|
||||
total_expectations: int
|
||||
failed_expectations: int
|
||||
details: List[Dict[str, Any]]
|
||||
timestamp: datetime
|
||||
|
||||
class DataQualityPipeline:
|
||||
"""Orchestrate data quality checks across tables"""
|
||||
|
||||
def __init__(self, context: gx.DataContext):
|
||||
self.context = context
|
||||
self.results: List[QualityResult] = []
|
||||
|
||||
def validate_table(self, table: str, suite: str) -> QualityResult:
|
||||
"""Validate a single table against expectation suite"""
|
||||
|
||||
checkpoint_config = {
|
||||
"name": f"{table}_validation",
|
||||
"config_version": 1.0,
|
||||
"class_name": "Checkpoint",
|
||||
"validations": [{
|
||||
"batch_request": {
|
||||
"datasource_name": "warehouse",
|
||||
"data_asset_name": table,
|
||||
},
|
||||
"expectation_suite_name": suite,
|
||||
}],
|
||||
}
|
||||
|
||||
result = self.context.run_checkpoint(**checkpoint_config)
|
||||
|
||||
# Parse results
|
||||
validation_result = list(result.run_results.values())[0]
|
||||
results = validation_result.results
|
||||
|
||||
failed = [r for r in results if not r.success]
|
||||
|
||||
return QualityResult(
|
||||
table=table,
|
||||
passed=result.success,
|
||||
total_expectations=len(results),
|
||||
failed_expectations=len(failed),
|
||||
details=[{
|
||||
"expectation": r.expectation_config.expectation_type,
|
||||
"success": r.success,
|
||||
"observed_value": r.result.get("observed_value"),
|
||||
} for r in results],
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
|
||||
def run_all(self, tables: Dict[str, str]) -> Dict[str, QualityResult]:
|
||||
"""Run validation for all tables"""
|
||||
results = {}
|
||||
|
||||
for table, suite in tables.items():
|
||||
print(f"Validating {table}...")
|
||||
results[table] = self.validate_table(table, suite)
|
||||
|
||||
return results
|
||||
|
||||
def generate_report(self, results: Dict[str, QualityResult]) -> str:
|
||||
"""Generate quality report"""
|
||||
report = ["# Data Quality Report", f"Generated: {datetime.now()}", ""]
|
||||
|
||||
total_passed = sum(1 for r in results.values() if r.passed)
|
||||
total_tables = len(results)
|
||||
|
||||
report.append(f"## Summary: {total_passed}/{total_tables} tables passed")
|
||||
report.append("")
|
||||
|
||||
for table, result in results.items():
|
||||
status = "✅" if result.passed else "❌"
|
||||
report.append(f"### {status} {table}")
|
||||
report.append(f"- Expectations: {result.total_expectations}")
|
||||
report.append(f"- Failed: {result.failed_expectations}")
|
||||
|
||||
if not result.passed:
|
||||
report.append("- Failed checks:")
|
||||
for detail in result.details:
|
||||
if not detail["success"]:
|
||||
report.append(f" - {detail['expectation']}: {detail['observed_value']}")
|
||||
report.append("")
|
||||
|
||||
return "\n".join(report)
|
||||
|
||||
# Usage
|
||||
context = gx.get_context()
|
||||
pipeline = DataQualityPipeline(context)
|
||||
|
||||
tables_to_validate = {
|
||||
"orders": "orders_suite",
|
||||
"customers": "customers_suite",
|
||||
"products": "products_suite",
|
||||
}
|
||||
|
||||
results = pipeline.run_all(tables_to_validate)
|
||||
report = pipeline.generate_report(results)
|
||||
|
||||
# Fail pipeline if any table failed
|
||||
if not all(r.passed for r in results.values()):
|
||||
print(report)
|
||||
raise ValueError("Data quality checks failed!")
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
- **Test early** - Validate source data before transformations
|
||||
- **Test incrementally** - Add tests as you find issues
|
||||
- **Document expectations** - Clear descriptions for each test
|
||||
- **Alert on failures** - Integrate with monitoring
|
||||
- **Version contracts** - Track schema changes
|
||||
|
||||
### Don'ts
|
||||
- **Don't test everything** - Focus on critical columns
|
||||
- **Don't ignore warnings** - They often precede failures
|
||||
- **Don't skip freshness** - Stale data is bad data
|
||||
- **Don't hardcode thresholds** - Use dynamic baselines
|
||||
- **Don't test in isolation** - Test relationships too
|
||||
|
||||
## Resources
|
||||
|
||||
- [Great Expectations Documentation](https://docs.greatexpectations.io/)
|
||||
- [dbt Testing Documentation](https://docs.getdbt.com/docs/build/tests)
|
||||
- [Data Contract Specification](https://datacontract.com/)
|
||||
- [Soda Core](https://docs.soda.io/soda-core/overview.html)
|
||||
-499
@@ -1,499 +0,0 @@
|
||||
---
|
||||
name: embedding-strategies
|
||||
description: "Guide to selecting and optimizing embedding models for vector search applications."
|
||||
risk: unknown
|
||||
source: community
|
||||
date_added: "2026-02-27"
|
||||
---
|
||||
|
||||
# Embedding Strategies
|
||||
|
||||
Guide to selecting and optimizing embedding models for vector search applications.
|
||||
|
||||
## Do not use this skill when
|
||||
|
||||
- The task is unrelated to embedding strategies
|
||||
- You need a different domain or tool outside this scope
|
||||
|
||||
## Instructions
|
||||
|
||||
- Clarify goals, constraints, and required inputs.
|
||||
- Apply relevant best practices and validate outcomes.
|
||||
- Provide actionable steps and verification.
|
||||
- If detailed examples are required, open `resources/implementation-playbook.md`.
|
||||
|
||||
## Use this skill when
|
||||
|
||||
- Choosing embedding models for RAG
|
||||
- Optimizing chunking strategies
|
||||
- Fine-tuning embeddings for domains
|
||||
- Comparing embedding model performance
|
||||
- Reducing embedding dimensions
|
||||
- Handling multilingual content
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. Embedding Model Comparison
|
||||
|
||||
| Model | Dimensions | Max Tokens | Best For |
|
||||
|-------|------------|------------|----------|
|
||||
| **text-embedding-3-large** | 3072 | 8191 | High accuracy |
|
||||
| **text-embedding-3-small** | 1536 | 8191 | Cost-effective |
|
||||
| **voyage-2** | 1024 | 4000 | Code, legal |
|
||||
| **bge-large-en-v1.5** | 1024 | 512 | Open source |
|
||||
| **all-MiniLM-L6-v2** | 384 | 256 | Fast, lightweight |
|
||||
| **multilingual-e5-large** | 1024 | 512 | Multi-language |
|
||||
|
||||
### 2. Embedding Pipeline
|
||||
|
||||
```
|
||||
Document → Chunking → Preprocessing → Embedding Model → Vector
|
||||
↓
|
||||
[Overlap, Size] [Clean, Normalize] [API/Local]
|
||||
```
|
||||
|
||||
## Templates
|
||||
|
||||
### Template 1: OpenAI Embeddings
|
||||
|
||||
```python
|
||||
from openai import OpenAI
|
||||
from typing import List
|
||||
import numpy as np
|
||||
|
||||
client = OpenAI()
|
||||
|
||||
def get_embeddings(
|
||||
texts: List[str],
|
||||
model: str = "text-embedding-3-small",
|
||||
dimensions: int = None
|
||||
) -> List[List[float]]:
|
||||
"""Get embeddings from OpenAI."""
|
||||
# Handle batching for large lists
|
||||
batch_size = 100
|
||||
all_embeddings = []
|
||||
|
||||
for i in range(0, len(texts), batch_size):
|
||||
batch = texts[i:i + batch_size]
|
||||
|
||||
kwargs = {"input": batch, "model": model}
|
||||
if dimensions:
|
||||
kwargs["dimensions"] = dimensions
|
||||
|
||||
response = client.embeddings.create(**kwargs)
|
||||
embeddings = [item.embedding for item in response.data]
|
||||
all_embeddings.extend(embeddings)
|
||||
|
||||
return all_embeddings
|
||||
|
||||
|
||||
def get_embedding(text: str, **kwargs) -> List[float]:
|
||||
"""Get single embedding."""
|
||||
return get_embeddings([text], **kwargs)[0]
|
||||
|
||||
|
||||
# Dimension reduction with OpenAI
|
||||
def get_reduced_embedding(text: str, dimensions: int = 512) -> List[float]:
|
||||
"""Get embedding with reduced dimensions (Matryoshka)."""
|
||||
return get_embedding(
|
||||
text,
|
||||
model="text-embedding-3-small",
|
||||
dimensions=dimensions
|
||||
)
|
||||
```
|
||||
|
||||
### Template 2: Local Embeddings with Sentence Transformers
|
||||
|
||||
```python
|
||||
from sentence_transformers import SentenceTransformer
|
||||
from typing import List, Optional
|
||||
import numpy as np
|
||||
|
||||
class LocalEmbedder:
|
||||
"""Local embedding with sentence-transformers."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str = "BAAI/bge-large-en-v1.5",
|
||||
device: str = "cuda"
|
||||
):
|
||||
self.model = SentenceTransformer(model_name, device=device)
|
||||
|
||||
def embed(
|
||||
self,
|
||||
texts: List[str],
|
||||
normalize: bool = True,
|
||||
show_progress: bool = False
|
||||
) -> np.ndarray:
|
||||
"""Embed texts with optional normalization."""
|
||||
embeddings = self.model.encode(
|
||||
texts,
|
||||
normalize_embeddings=normalize,
|
||||
show_progress_bar=show_progress,
|
||||
convert_to_numpy=True
|
||||
)
|
||||
return embeddings
|
||||
|
||||
def embed_query(self, query: str) -> np.ndarray:
|
||||
"""Embed a query with BGE-style prefix."""
|
||||
# BGE models benefit from query prefix
|
||||
if "bge" in self.model.get_sentence_embedding_dimension():
|
||||
query = f"Represent this sentence for searching relevant passages: {query}"
|
||||
return self.embed([query])[0]
|
||||
|
||||
def embed_documents(self, documents: List[str]) -> np.ndarray:
|
||||
"""Embed documents for indexing."""
|
||||
return self.embed(documents)
|
||||
|
||||
|
||||
# E5 model with instructions
|
||||
class E5Embedder:
|
||||
def __init__(self, model_name: str = "intfloat/multilingual-e5-large"):
|
||||
self.model = SentenceTransformer(model_name)
|
||||
|
||||
def embed_query(self, query: str) -> np.ndarray:
|
||||
return self.model.encode(f"query: {query}")
|
||||
|
||||
def embed_document(self, document: str) -> np.ndarray:
|
||||
return self.model.encode(f"passage: {document}")
|
||||
```
|
||||
|
||||
### Template 3: Chunking Strategies
|
||||
|
||||
```python
|
||||
from typing import List, Tuple
|
||||
import re
|
||||
|
||||
def chunk_by_tokens(
|
||||
text: str,
|
||||
chunk_size: int = 512,
|
||||
chunk_overlap: int = 50,
|
||||
tokenizer=None
|
||||
) -> List[str]:
|
||||
"""Chunk text by token count."""
|
||||
import tiktoken
|
||||
tokenizer = tokenizer or tiktoken.get_encoding("cl100k_base")
|
||||
|
||||
tokens = tokenizer.encode(text)
|
||||
chunks = []
|
||||
|
||||
start = 0
|
||||
while start < len(tokens):
|
||||
end = start + chunk_size
|
||||
chunk_tokens = tokens[start:end]
|
||||
chunk_text = tokenizer.decode(chunk_tokens)
|
||||
chunks.append(chunk_text)
|
||||
start = end - chunk_overlap
|
||||
|
||||
return chunks
|
||||
|
||||
|
||||
def chunk_by_sentences(
|
||||
text: str,
|
||||
max_chunk_size: int = 1000,
|
||||
min_chunk_size: int = 100
|
||||
) -> List[str]:
|
||||
"""Chunk text by sentences, respecting size limits."""
|
||||
import nltk
|
||||
sentences = nltk.sent_tokenize(text)
|
||||
|
||||
chunks = []
|
||||
current_chunk = []
|
||||
current_size = 0
|
||||
|
||||
for sentence in sentences:
|
||||
sentence_size = len(sentence)
|
||||
|
||||
if current_size + sentence_size > max_chunk_size and current_chunk:
|
||||
chunks.append(" ".join(current_chunk))
|
||||
current_chunk = []
|
||||
current_size = 0
|
||||
|
||||
current_chunk.append(sentence)
|
||||
current_size += sentence_size
|
||||
|
||||
if current_chunk:
|
||||
chunks.append(" ".join(current_chunk))
|
||||
|
||||
return chunks
|
||||
|
||||
|
||||
def chunk_by_semantic_sections(
|
||||
text: str,
|
||||
headers_pattern: str = r'^#{1,3}\s+.+$'
|
||||
) -> List[Tuple[str, str]]:
|
||||
"""Chunk markdown by headers, preserving hierarchy."""
|
||||
lines = text.split('\n')
|
||||
chunks = []
|
||||
current_header = ""
|
||||
current_content = []
|
||||
|
||||
for line in lines:
|
||||
if re.match(headers_pattern, line, re.MULTILINE):
|
||||
if current_content:
|
||||
chunks.append((current_header, '\n'.join(current_content)))
|
||||
current_header = line
|
||||
current_content = []
|
||||
else:
|
||||
current_content.append(line)
|
||||
|
||||
if current_content:
|
||||
chunks.append((current_header, '\n'.join(current_content)))
|
||||
|
||||
return chunks
|
||||
|
||||
|
||||
def recursive_character_splitter(
|
||||
text: str,
|
||||
chunk_size: int = 1000,
|
||||
chunk_overlap: int = 200,
|
||||
separators: List[str] = None
|
||||
) -> List[str]:
|
||||
"""LangChain-style recursive splitter."""
|
||||
separators = separators or ["\n\n", "\n", ". ", " ", ""]
|
||||
|
||||
def split_text(text: str, separators: List[str]) -> List[str]:
|
||||
if not text:
|
||||
return []
|
||||
|
||||
separator = separators[0]
|
||||
remaining_separators = separators[1:]
|
||||
|
||||
if separator == "":
|
||||
# Character-level split
|
||||
return [text[i:i+chunk_size] for i in range(0, len(text), chunk_size - chunk_overlap)]
|
||||
|
||||
splits = text.split(separator)
|
||||
chunks = []
|
||||
current_chunk = []
|
||||
current_length = 0
|
||||
|
||||
for split in splits:
|
||||
split_length = len(split) + len(separator)
|
||||
|
||||
if current_length + split_length > chunk_size and current_chunk:
|
||||
chunk_text = separator.join(current_chunk)
|
||||
|
||||
# Recursively split if still too large
|
||||
if len(chunk_text) > chunk_size and remaining_separators:
|
||||
chunks.extend(split_text(chunk_text, remaining_separators))
|
||||
else:
|
||||
chunks.append(chunk_text)
|
||||
|
||||
# Start new chunk with overlap
|
||||
overlap_splits = []
|
||||
overlap_length = 0
|
||||
for s in reversed(current_chunk):
|
||||
if overlap_length + len(s) <= chunk_overlap:
|
||||
overlap_splits.insert(0, s)
|
||||
overlap_length += len(s)
|
||||
else:
|
||||
break
|
||||
current_chunk = overlap_splits
|
||||
current_length = overlap_length
|
||||
|
||||
current_chunk.append(split)
|
||||
current_length += split_length
|
||||
|
||||
if current_chunk:
|
||||
chunks.append(separator.join(current_chunk))
|
||||
|
||||
return chunks
|
||||
|
||||
return split_text(text, separators)
|
||||
```
|
||||
|
||||
### Template 4: Domain-Specific Embedding Pipeline
|
||||
|
||||
```python
|
||||
class DomainEmbeddingPipeline:
|
||||
"""Pipeline for domain-specific embeddings."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embedding_model: str = "text-embedding-3-small",
|
||||
chunk_size: int = 512,
|
||||
chunk_overlap: int = 50,
|
||||
preprocessing_fn=None
|
||||
):
|
||||
self.embedding_model = embedding_model
|
||||
self.chunk_size = chunk_size
|
||||
self.chunk_overlap = chunk_overlap
|
||||
self.preprocess = preprocessing_fn or self._default_preprocess
|
||||
|
||||
def _default_preprocess(self, text: str) -> str:
|
||||
"""Default preprocessing."""
|
||||
# Remove excessive whitespace
|
||||
text = re.sub(r'\s+', ' ', text)
|
||||
# Remove special characters
|
||||
text = re.sub(r'[^\w\s.,!?-]', '', text)
|
||||
return text.strip()
|
||||
|
||||
async def process_documents(
|
||||
self,
|
||||
documents: List[dict],
|
||||
id_field: str = "id",
|
||||
content_field: str = "content",
|
||||
metadata_fields: List[str] = None
|
||||
) -> List[dict]:
|
||||
"""Process documents for vector storage."""
|
||||
processed = []
|
||||
|
||||
for doc in documents:
|
||||
content = doc[content_field]
|
||||
doc_id = doc[id_field]
|
||||
|
||||
# Preprocess
|
||||
cleaned = self.preprocess(content)
|
||||
|
||||
# Chunk
|
||||
chunks = chunk_by_tokens(
|
||||
cleaned,
|
||||
self.chunk_size,
|
||||
self.chunk_overlap
|
||||
)
|
||||
|
||||
# Create embeddings
|
||||
embeddings = get_embeddings(chunks, self.embedding_model)
|
||||
|
||||
# Create records
|
||||
for i, (chunk, embedding) in enumerate(zip(chunks, embeddings)):
|
||||
record = {
|
||||
"id": f"{doc_id}_chunk_{i}",
|
||||
"document_id": doc_id,
|
||||
"chunk_index": i,
|
||||
"text": chunk,
|
||||
"embedding": embedding
|
||||
}
|
||||
|
||||
# Add metadata
|
||||
if metadata_fields:
|
||||
for field in metadata_fields:
|
||||
if field in doc:
|
||||
record[field] = doc[field]
|
||||
|
||||
processed.append(record)
|
||||
|
||||
return processed
|
||||
|
||||
|
||||
# Code-specific pipeline
|
||||
class CodeEmbeddingPipeline:
|
||||
"""Specialized pipeline for code embeddings."""
|
||||
|
||||
def __init__(self, model: str = "voyage-code-2"):
|
||||
self.model = model
|
||||
|
||||
def chunk_code(self, code: str, language: str) -> List[dict]:
|
||||
"""Chunk code by functions/classes."""
|
||||
import tree_sitter
|
||||
|
||||
# Parse with tree-sitter
|
||||
# Extract functions, classes, methods
|
||||
# Return chunks with context
|
||||
pass
|
||||
|
||||
def embed_with_context(self, chunk: str, context: str) -> List[float]:
|
||||
"""Embed code with surrounding context."""
|
||||
combined = f"Context: {context}\n\nCode:\n{chunk}"
|
||||
return get_embedding(combined, model=self.model)
|
||||
```
|
||||
|
||||
### Template 5: Embedding Quality Evaluation
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
from typing import List, Tuple
|
||||
|
||||
def evaluate_retrieval_quality(
|
||||
queries: List[str],
|
||||
relevant_docs: List[List[str]], # List of relevant doc IDs per query
|
||||
retrieved_docs: List[List[str]], # List of retrieved doc IDs per query
|
||||
k: int = 10
|
||||
) -> dict:
|
||||
"""Evaluate embedding quality for retrieval."""
|
||||
|
||||
def precision_at_k(relevant: set, retrieved: List[str], k: int) -> float:
|
||||
retrieved_k = retrieved[:k]
|
||||
relevant_retrieved = len(set(retrieved_k) & relevant)
|
||||
return relevant_retrieved / k
|
||||
|
||||
def recall_at_k(relevant: set, retrieved: List[str], k: int) -> float:
|
||||
retrieved_k = retrieved[:k]
|
||||
relevant_retrieved = len(set(retrieved_k) & relevant)
|
||||
return relevant_retrieved / len(relevant) if relevant else 0
|
||||
|
||||
def mrr(relevant: set, retrieved: List[str]) -> float:
|
||||
for i, doc in enumerate(retrieved):
|
||||
if doc in relevant:
|
||||
return 1 / (i + 1)
|
||||
return 0
|
||||
|
||||
def ndcg_at_k(relevant: set, retrieved: List[str], k: int) -> float:
|
||||
dcg = sum(
|
||||
1 / np.log2(i + 2) if doc in relevant else 0
|
||||
for i, doc in enumerate(retrieved[:k])
|
||||
)
|
||||
ideal_dcg = sum(1 / np.log2(i + 2) for i in range(min(len(relevant), k)))
|
||||
return dcg / ideal_dcg if ideal_dcg > 0 else 0
|
||||
|
||||
metrics = {
|
||||
f"precision@{k}": [],
|
||||
f"recall@{k}": [],
|
||||
"mrr": [],
|
||||
f"ndcg@{k}": []
|
||||
}
|
||||
|
||||
for relevant, retrieved in zip(relevant_docs, retrieved_docs):
|
||||
relevant_set = set(relevant)
|
||||
metrics[f"precision@{k}"].append(precision_at_k(relevant_set, retrieved, k))
|
||||
metrics[f"recall@{k}"].append(recall_at_k(relevant_set, retrieved, k))
|
||||
metrics["mrr"].append(mrr(relevant_set, retrieved))
|
||||
metrics[f"ndcg@{k}"].append(ndcg_at_k(relevant_set, retrieved, k))
|
||||
|
||||
return {name: np.mean(values) for name, values in metrics.items()}
|
||||
|
||||
|
||||
def compute_embedding_similarity(
|
||||
embeddings1: np.ndarray,
|
||||
embeddings2: np.ndarray,
|
||||
metric: str = "cosine"
|
||||
) -> np.ndarray:
|
||||
"""Compute similarity matrix between embedding sets."""
|
||||
if metric == "cosine":
|
||||
# Normalize
|
||||
norm1 = embeddings1 / np.linalg.norm(embeddings1, axis=1, keepdims=True)
|
||||
norm2 = embeddings2 / np.linalg.norm(embeddings2, axis=1, keepdims=True)
|
||||
return norm1 @ norm2.T
|
||||
elif metric == "euclidean":
|
||||
from scipy.spatial.distance import cdist
|
||||
return -cdist(embeddings1, embeddings2, metric='euclidean')
|
||||
elif metric == "dot":
|
||||
return embeddings1 @ embeddings2.T
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
- **Match model to use case** - Code vs prose vs multilingual
|
||||
- **Chunk thoughtfully** - Preserve semantic boundaries
|
||||
- **Normalize embeddings** - For cosine similarity
|
||||
- **Batch requests** - More efficient than one-by-one
|
||||
- **Cache embeddings** - Avoid recomputing
|
||||
|
||||
### Don'ts
|
||||
- **Don't ignore token limits** - Truncation loses info
|
||||
- **Don't mix embedding models** - Incompatible spaces
|
||||
- **Don't skip preprocessing** - Garbage in, garbage out
|
||||
- **Don't over-chunk** - Lose context
|
||||
|
||||
## Resources
|
||||
|
||||
- [OpenAI Embeddings](https://platform.openai.com/docs/guides/embeddings)
|
||||
- [Sentence Transformers](https://www.sbert.net/)
|
||||
- [MTEB Benchmark](https://huggingface.co/spaces/mteb/leaderboard)
|
||||
|
||||
## Limitations
|
||||
- Use this skill only when the task clearly matches the scope described above.
|
||||
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
|
||||
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
|
||||
Reference in New Issue
Block a user