Instalacion
bash
terminal
Copiar
pip install azure-search-documents azure-identity
Requiere Python 3.9+. Incluye el cliente síncrono SearchClient, SearchIndexClient e SearchIndexerClient.
Variables de Entorno
bash
.env
Copiar
AZURE_SEARCH_ENDPOINT=https://<resource>.search.windows.net # Endpoint del servicio AZURE_SEARCH_INDEX_NAME=products-catalog # Nombre del índice AZURE_TOKEN_CREDENTIALS=prod # Solo si DefaultAzureCredential en produccion AZURE_SEARCH_API_KEY=<api-key> # Solo para legacy API-key auth (ver abajo)
Autenticacion & Lifecycle
🔑 Dos reglas aplican a TODOS los ejemplos de codigo:
- Prefiere
DefaultAzureCredential. Funciona localmente (Azure CLI / VS Code / Developer CLI) y en Azure (managed identity, workload identity) sin cambios de codigo. Evita connection strings y API keys — eluden el audit y la rotacion de Entra.- Dev local:
DefaultAzureCredentialfunciona tal cual. - Produccion: establece
AZURE_TOKEN_CREDENTIALS=prodpara restringir la credential chain a credenciales seguras.
- Dev local:
- Envuelve cada cliente en un context manager para liberar HTTP transports, sockets y token caches de forma determinista:
- Sync:
with <Client>(...) as client: - Async:
async with <Client>(...) as client:yasync with DefaultAzureCredential() as credential:
- Sync:
Autenticacion con DefaultAzureCredential (recomendado)
python
auth_example.py
Copiar
import os from azure.identity import DefaultAzureCredential, ManagedIdentityCredential from azure.search.documents import SearchClient from azure.search.documents.indexes import SearchIndexClient # Dev local: DefaultAzureCredential. Prod: AZURE_TOKEN_CREDENTIALS=prod credential = DefaultAzureCredential(require_envvar=True) # Produccion con credencial explicita: # credential = ManagedIdentityCredential() endpoint = os.environ["AZURE_SEARCH_ENDPOINT"] index_name = os.environ["AZURE_SEARCH_INDEX_NAME"] with SearchIndexClient(endpoint=endpoint, credential=credential) as index_client: names = [idx.name for idx in index_client.list_index_names()] print(f"Indices disponibles: {names}") with SearchClient(endpoint=endpoint, index_name=index_name, credential=credential) as client: result = client.search(search_text="*", top=1) print("Conexion OK — primer doc:", next(iter(result), None))
Legacy: API Key (deployments existentes)
⚠️ Solo para deployments existentes
El nuevo codigo debe usar DefaultAzureCredential. La ruta de API key elude el audit de Entra y la rotacion automatica. Usar solo mientras se completa la migracion.
python
legacy_key_auth.py
Copiar
from azure.core.credentials import AzureKeyCredential from azure.search.documents import SearchClient credential = AzureKeyCredential(os.environ["AZURE_SEARCH_API_KEY"]) with SearchClient(endpoint=endpoint, index_name=index_name, credential=credential) as client: ...
Core Workflow
Flujo completo: crear índice vectorial + ingestar productos + búsqueda semántica/vectorial.
1. Definir y crear el índice
python
create_index.py
Copiar
from azure.search.documents.indexes.models import ( SearchIndex, SearchField, SearchFieldDataType, SimpleField, SearchableField, VectorSearch, HnswAlgorithmConfiguration, VectorSearchProfile, SemanticConfiguration, SemanticSearch, SemanticPrioritizedFields, SemanticField ) fields = [ SimpleField(name="id", type=SearchFieldDataType.String, key=True), SearchableField(name="nombre", type=SearchFieldDataType.String, analyzer_name="es.microsoft"), SearchableField(name="descripcion", type=SearchFieldDataType.String, analyzer_name="es.microsoft"), SimpleField(name="categoria", type=SearchFieldDataType.String, filterable=True, facetable=True), SimpleField(name="precio", type=SearchFieldDataType.Double, filterable=True, sortable=True), SearchField( name="vector_descripcion", type=SearchFieldDataType.Collection(SearchFieldDataType.Single), searchable=True, vector_search_dimensions=1536, vector_search_profile_name="my-hnsw-profile" ), ] vector_search = VectorSearch( algorithms=[HnswAlgorithmConfiguration(name="my-hnsw")], profiles=[VectorSearchProfile(name="my-hnsw-profile", algorithm_configuration_name="my-hnsw")] ) semantic_config = SemanticConfiguration( name="my-semantic-config", prioritized_fields=SemanticPrioritizedFields( title_field=SemanticField(field_name="nombre"), content_fields=[SemanticField(field_name="descripcion")] ) ) index = SearchIndex( name=index_name, fields=fields, vector_search=vector_search, semantic_search=SemanticSearch(configurations=[semantic_config]) ) with SearchIndexClient(endpoint=endpoint, credential=credential) as index_client: created = index_client.create_or_update_index(index) print(f"Indice '{created.name}' listo — {len(created.fields)} campos")
2. Ingestar documentos
python
upload_docs.py
Copiar
from azure.search.documents.models import IndexDocumentsBatch productos = [ { "id": "001", "nombre": "Cafe de Especialidad Guatemala", "descripcion": "Granos arábica de altura, notas de chocolate y naranja", "categoria": "cafe", "precio": 18.50, "vector_descripcion": generar_embedding("Granos arábica de altura...") }, { "id": "002", "nombre": "Kit Inicio Cultivo Microgreens", "descripcion": "Todo lo necesario para cultivar microgreens en casa en 10 dias", "categoria": "cultivo", "precio": 34.90, "vector_descripcion": generar_embedding("Todo lo necesario para cultivar...") }, ] with SearchClient(endpoint=endpoint, index_name=index_name, credential=credential) as client: results = client.upload_documents(documents=productos) success = [r for r in results if r.succeeded] print(f"Cargados: {len(success)}/{len(productos)}")
3. Busqueda semantica + vectorial (RAG)
python
search_rag.py
Copiar
from azure.search.documents.models import VectorizedQuery, QueryType query = "regalo especial para amante del cafe con notas frutales" query_vector = generar_embedding(query) with SearchClient(endpoint=endpoint, index_name=index_name, credential=credential) as client: results = client.search( search_text=query, query_type=QueryType.SEMANTIC, semantic_configuration_name="my-semantic-config", query_caption="extractive", vector_queries=[ VectorizedQuery( vector=query_vector, k_nearest_neighbors=5, fields="vector_descripcion" ) ], select=["id", "nombre", "descripcion", "precio"], top=3 ) for doc in results: score = doc.get("@search.score", 0) print(f"[{score:.3f}] {doc['nombre']} — €{doc['precio']}")
Clientes y Metodos Principales
SearchIndexClient — Gestion de indices
| Metodo | Comportamiento | Devuelve |
|---|---|---|
create_index(index) | Crea nuevo; falla si existe | SearchIndex |
create_or_update_index(index) | Upsert del indice | SearchIndex |
get_index(name) | Obtiene; error si no existe | SearchIndex |
list_indexes() | Itera todos los indices | ItemPaged[SearchIndex] |
delete_index(name) | Elimina; ok si no existe | None |
get_index_statistics(name) | Doc count, storage size | SearchIndexStatistics |
SearchClient — Operaciones sobre documentos
| Metodo | Comportamiento | Devuelve |
|---|---|---|
upload_documents(documents) | Inserta o reemplaza | List[IndexingResult] |
merge_documents(documents) | Actualiza campos; falla si no existe | List[IndexingResult] |
merge_or_upload_documents(documents) | Upsert de documentos | List[IndexingResult] |
delete_documents(documents) | Elimina por key; ok si no existe | List[IndexingResult] |
search(search_text, **kwargs) | Busqueda full-text / semantica / vector | ItemPaged[dict] |
get_document(key) | Obtiene doc por clave; error si no existe | dict |
suggest(search_text, suggester_name) | Autocompletado | SuggestResults |
Verbos Azure SDK estandar
| Verbo | Comportamiento |
|---|---|
create | Crea nuevo; falla si ya existe |
upsert / create_or_update | Crea o actualiza |
get | Recupera; error si no existe |
list | Devuelve coleccion paginada (ItemPaged) |
delete | Elimina; exito incluso si no existe |
Best Practices
✅ Reglas obligatorias para Python — incluir en toda skill generada
Las dos primeras reglas deben aparecer VERBATIM en el SKILL.md final para que los usuarios las apliquen en su propio codigo.
- Elige sync O async y manten la consistencia. No mezcles
azure.search.documents(sync) conazure.search.documents.aio(async) en el mismo call path. Elige un modo por modulo. - Usa siempre context managers para clientes y credenciales async. Envuelve cada cliente en
with SearchClient(...) as client:(sync) oasync with SearchClient(...) as client:(async). ParaDefaultAzureCredentialasync deazure.identity.aio, usa tambienasync with credential:. - Usa
DefaultAzureCredentialpara codigo que corre localmente. Usa una credencial especifica (ManagedIdentityCredential,WorkloadIdentityCredential) para codigo en Azure en produccion. - Pagina siempre con
ItemPaged— no consumas toda la coleccion en memoria. Itera directamente sobre el resultado desearch()olist_indexes(). - Filtra con OData en el server — usa el parametro
filteren lugar de filtrar en Python. Ejemplo:filter="categoria eq 'cafe' and precio lt 25". - Separa
SearchIndexClientdeSearchClient— el primero gestiona el esquema del indice; el segundo opera sobre documentos. No uses el de indice para buscar. - Limpia el indice en ejemplos y tests — llama a
delete_index()al final de scripts de demostracion para no acumular indices de prueba.
Tests — Acceptance Criteria & Scenarios
Acceptance Criteria: patrones correctos vs. incorrectos
| Estado | Patron | Motivo |
|---|---|---|
| ✅ CORRECTO | from azure.search.documents import SearchClient | Ruta de importacion correcta |
| ✅ CORRECTO | with SearchClient(...) as client: | Context manager — limpia HTTP transport |
| ✅ CORRECTO | DefaultAzureCredential(require_envvar=True) | Auth Entra; configurable en produccion |
| ❌ INCORRECTO | AzureKeyCredential("hardcoded-key") | Credencial hardcoded — riesgo de seguridad |
| ❌ INCORRECTO | client = SearchClient(...) sin with | Fuga de HTTP transport al salir de scope |
| ❌ INCORRECTO | from azure.search.documents.models import SearchClient | Ruta de modulo incorrecta |
| ❌ INCORRECTO | Mezclar .documents (sync) + .aio (async) | Comportamiento impredecible en el event loop |
Scenarios YAML
basic_client_creation
Expected patterns
✓
DefaultAzureCredential✓
SearchClient✓
with SearchClient✓
azure.search.documentsForbidden patterns
✗
api_key=✗
hardcoded✗
AzureKeyCredential("create_index_with_vector
Expected patterns
✓
SearchIndexClient✓
VectorSearch✓
HnswAlgorithmConfiguration✓
create_or_update_indexForbidden patterns
✗
create_index (sin upsert)✗
ExhaustiveKnn para escalasemantic_hybrid_search
Expected patterns
✓
QueryType.SEMANTIC✓
VectorizedQuery✓
k_nearest_neighbors✓
semantic_configuration_nameForbidden patterns
✗ Cargar todos los resultados en lista
✗ Filtrar en Python en lugar de OData
async_variant
Expected patterns
✓
from azure.search.documents.aio✓
async with SearchClient✓
async with credential✓
from azure.identity.aioForbidden patterns
✗ Mezcla sync + async
✗
from azure.search.documents import en path asyncChecklist de Entrega
Prerequisitos
- SDK proporcionado:
azure-search-documents - URL de documentacion: learn.microsoft.com/azure/search
- Lenguaje target: Python (-py)
Creacion del Skill
- Description incluye QUE y CUANDO (trigger phrases)
- SKILL.md bajo 500 lineas
- Auth con DefaultAzureCredential + context manager en todos los ejemplos
- Cleanup/delete incluido en ejemplos
- Best Practices contiene las 2 reglas obligatorias Python (sync/async + context managers)
- Todos los ejemplos siguen ambas reglas
- API key legacy como subseccion demoted con aviso
Categorizacion
- Skill en
.github/skills/azure-ai-search-py/SKILL.md - Symlink:
skills/python/data/ai-search → ../../../.github/skills/azure-ai-search-py
Testing
-
tests/scenarios/azure-ai-search-py/acceptance-criteria.md -
tests/scenarios/azure-ai-search-py/scenarios.yaml— 4 scenarios -
pnpm harness azure-ai-search-py --mock— pendiente ejecucion
Documentacion
- README.md actualizado (catalogo + conteos)
-
npx tsx scripts/extract-skills.tsejecutado - Instrucciones para buscar en
microsoft-docsMCP incluidas