bigquery-data-science 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bigquery_data_science/__init__.py +58 -0
- bigquery_data_science/_skills/PLANS.md +76 -0
- bigquery_data_science/_skills/bigquery-ai/SKILL.md +66 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_agg.md +528 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_classify.md +450 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_count_tokens.md +180 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_detect_anomalies.md +322 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_embed.md +425 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_evaluate.md +368 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_forecast.md +437 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_generate.md +693 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_generate_bool.md +248 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_generate_double.md +254 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_generate_embedding.md +402 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_generate_int.md +252 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_generate_table.md +447 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_generate_text.md +356 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_if.md +373 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_key_drivers.md +331 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_parse_document.md +416 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_predict.md +559 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_score.md +310 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_search.md +465 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ai_similarity.md +371 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/catalog_search.md +951 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/content_analysis.md +284 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/content_moderation.md +369 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/data_enrichment.md +426 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/document_intelligence.md +470 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/document_rag.md +453 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/image_deduplication.md +770 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/log_analysis.md +668 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/metric_diagnostics.md +568 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ml_annotate_image.md +680 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ml_generate_embedding.md +217 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ml_generate_text.md +223 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ml_process_document.md +392 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ml_transcribe.md +815 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ml_translate.md +600 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/ml_understand_text.md +673 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/multimodal_analysis.md +266 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/rag_pipeline.md +593 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/semantic_search.md +510 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/tabular_prediction.md +485 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/time_series_intelligence.md +489 -0
- bigquery_data_science/_skills/bigquery-ai/narrative/vector_search.md +672 -0
- bigquery_data_science/_skills/bigquery-ai/reference/augmented-analytics.md +77 -0
- bigquery_data_science/_skills/bigquery-ai/reference/cloud-ai-service-models.md +88 -0
- bigquery_data_science/_skills/bigquery-ai/reference/document-processing.md +95 -0
- bigquery_data_science/_skills/bigquery-ai/reference/embeddings-and-search.md +166 -0
- bigquery_data_science/_skills/bigquery-ai/reference/general-purpose-generation.md +87 -0
- bigquery_data_science/_skills/bigquery-ai/reference/managed-functions.md +72 -0
- bigquery_data_science/_skills/bigquery-ai/reference/predictive-ai.md +113 -0
- bigquery_data_science/_skills/bigquery-ai/reference/workflows.md +56 -0
- bigquery_data_science/_skills/bigquery-ai/skill.manifest.json +109 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/INDEX.md +71 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aiagg.md +67 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aicausal_effect-covered-in-bigquery-ml.md +17 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aiclassify.md +72 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aicount_tokens.md +53 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aidetect_anomalies.md +63 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aiembed.md +85 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aievaluate.md +114 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aiforecast.md +123 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aigenerate.md +69 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aigenerate_bool.md +53 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aigenerate_double.md +45 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aigenerate_embedding.md +108 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aigenerate_int.md +45 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aigenerate_table.md +64 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aigenerate_text.md +167 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aiif.md +56 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aikey_drivers.md +76 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aiparse_document.md +91 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aipredict.md +74 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aiscore.md +52 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aisearch.md +66 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/aisimilarity.md +79 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/choosing-between-these-and-the-ai-functions.md +11 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/hybrid-search-capability.md +165 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/mlannotate_image.md +24 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/mlgenerate_embedding.md +34 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/mlgenerate_text.md +65 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/mlprocess_document.md +125 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/mltranscribe.md +26 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/mltranslate.md +27 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/mlunderstand_text.md +32 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/multimodal-input-patterns-by-function.md +128 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/object-tables.md +109 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/objectref-and-objectrefruntime-schema-reference.md +50 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/objfetch_metadata.md +49 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/objget_access_url.md +66 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/objget_read_url.md +40 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/objlist.md +34 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/objmake_ref.md +58 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/per-row-failures-and-the-retry-pattern.md +19 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/setup-apis-connection-roles.md +39 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/two-input-shapes.md +22 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/vector_search.md +78 -0
- bigquery_data_science/_skills/bigquery-ai/syntax/what-the-five-have-in-common.md +27 -0
- bigquery_data_science/_skills/bigquery-data-science/SKILL.md +69 -0
- bigquery_data_science/_skills/bigquery-data-science/reference/namespace-matrix.md +112 -0
- bigquery_data_science/_skills/bigquery-data-science/skill.manifest.json +13 -0
- bigquery_data_science/_skills/bigquery-ml/SKILL.md +70 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/airflow_with_kfp.md +379 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/anomaly_fraud_detection.md +195 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/arima_plus.md +635 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/arima_plus_xreg.md +453 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/autoencoder.md +470 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/automl_classifier.md +291 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/automl_regressor.md +274 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/boosted_tree_classifier.md +449 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/boosted_tree_regressor.md +400 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/bucketizing.md +201 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/causal_effect.md +773 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/churn_retention.md +271 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/cloud_workflows.md +370 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/composer_airflow.md +475 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/contribution_analysis.md +346 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/cross_validation.md +185 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/customer_segmentation.md +233 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/dataform.md +372 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/dbt.md +283 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/difference_in_differences.md +280 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/distance.md +195 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/dnn_classifier.md +326 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/dnn_regressor.md +308 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/embeddings_classification.md +426 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/encoding.md +238 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/ensembling.md +278 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/evaluation.md +445 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/exploration.md +779 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/export.md +321 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/feature_engineering.md +280 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/feature_store.md +652 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/feature_store_workflow.md +426 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/ga4_churn_prediction.md +285 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/hierarchical_forecasting.md +441 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/image.md +791 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/imported.md +426 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/kmeans.md +348 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/linear_regression.md +372 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/logistic_regression.md +390 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/matrix_factorization.md +404 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/model_monitoring.md +609 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/pca.md +357 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/price_elasticity_dml.md +286 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/propensity_score_matching.md +350 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/random_forest_classifier.md +445 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/random_forest_regressor.md +409 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/recommendation.md +281 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/regression_based_forecasting.md +491 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/remote.md +428 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/scalers.md +325 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/scheduled_queries.md +284 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/sql_scripting.md +243 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/survival_analysis.md +363 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/synthetic_control.md +221 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/text.md +215 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/time_series.md +669 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/transform_only.md +273 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/uplift_cate.md +241 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/vertex_kfp.md +360 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/wide_and_deep_classifier.md +316 -0
- bigquery_data_science/_skills/bigquery-ml/narrative/wide_and_deep_regressor.md +298 -0
- bigquery_data_science/_skills/bigquery-ml/reference/classification.md +80 -0
- bigquery_data_science/_skills/bigquery-ml/reference/model-management.md +90 -0
- bigquery_data_science/_skills/bigquery-ml/reference/preprocessing-functions.md +234 -0
- bigquery_data_science/_skills/bigquery-ml/reference/regression.md +79 -0
- bigquery_data_science/_skills/bigquery-ml/reference/unsupervised-and-specialized.md +101 -0
- bigquery_data_science/_skills/bigquery-ml/reference/workflows-and-pipelines.md +111 -0
- bigquery_data_science/_skills/bigquery-ml/skill.manifest.json +177 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/1-model_type-catalog.md +28 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/2-task-mlevaluate-metric-columns.md +26 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/3-capability-matrix-per-model-type.md +21 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/4-explainability-weights-functions.md +14 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/5-connection-matrix.md +11 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/6-which-model-free-function-no-create-model-no-connection.md +15 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/7-point-in-time-feature-retrieval-which-of-the-two.md +11 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/INDEX.md +111 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/aicausal_effect.md +85 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/arima_plus-forecasting-lifecycle-functions.md +14 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/arima_plus-univariate-time-series-forecasting.md +132 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/arima_plus_xreg-multivariate-time-series-with-external-regressors.md +103 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/autoencoder.md +102 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/automl_classifier-automl_regressor.md +88 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/boosted_tree_classifier-boosted_tree_regressor.md +117 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/contribution_analysis.md +113 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/dnn_classifier-dnn_regressor.md +107 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/dnn_linear_combined_classifier-dnn_linear_combined_regressor-wide-and-deep.md +98 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/explainability-functions-mlexplain_predict-mlglobal_explain-mlfeature_importance.md +7 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/exploratory-data-analysis-mldescribe_data-mlcorrelation.md +11 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/export-model.md +92 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/feature-engineering-standalone-preprocessing-functions.md +13 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/image-preprocessing-functions-mldecode_image-mlresize_image-mlconvert_image_type-mlconvert_color_space.md +63 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/imported-models-tensorflow-tensorflow_lite-onnx-xgboost.md +36 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/kmeans.md +87 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/linear_reg-logistic_reg-generalized-linear-models.md +101 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/matrix_factorization.md +162 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mladvanced_weights.md +76 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlarima_coefficients.md +30 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlarima_evaluate.md +49 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlbag_of_words.md +47 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlbucketize.md +47 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlcentroids.md +46 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlconfusion_matrix.md +56 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlconvert_color_space.md +53 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlconvert_image_type.md +39 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlcorrelation.md +78 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mldecode_image.md +44 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mldescribe_data.md +74 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mldetect_anomalies.md +86 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mldistance.md +48 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlentity_features_at_time.md +56 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlevaluate.md +72 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlexplain_forecast.md +50 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlexplain_predict.md +66 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlfeature_cross.md +46 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlfeature_importance.md +47 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlfeature_info.md +50 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlfeatures_at_time.md +51 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlforecast.md +76 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlgenerate_embedding-from-a-bqml-pca-autoencoder-matrix_factorization-model.md +64 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlglobal_explain.md +63 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlhash_bucketize.md +47 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlholiday_info.md +46 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlimputer.md +46 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mllp_norm.md +47 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlmetrics.md +80 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlngrams.md +37 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlone_hot_encoder-mlmulti_hot_encoder-mllabel_encoder.md +67 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlpolynomial_expand.md +47 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlpredict.md +67 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlprincipal_component_info.md +42 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlprincipal_components.md +40 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlquantile_bucketize.md +43 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlrecommend.md +60 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlreconstruction_loss.md +48 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlresize_image.md +45 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlroc_curve.md +63 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mltf_idf.md +37 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mltfdv_describe.md +34 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mltfdv_validate.md +48 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mltraining_info.md +78 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mltransform-applying-a-saved-transform.md +52 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mltranspose-not-a-function-pointer.md +32 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mltrial_info.md +60 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlvalidate_data_drift.md +99 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlvalidate_data_skew.md +50 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/mlweights.md +84 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/model-free-time-series-decomposition-mltrend-mlseasonality-mldetect_change_points.md +100 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/model-introspection-mlfeature_info-mltraining_info-mltrial_info.md +7 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/model-monitoring-data-validation.md +23 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/numerical-scalers-mlstandard_scaler-mlmin_max_scaler-mlmax_abs_scaler-mlrobust_scaler-mlnormalizer.md +77 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/onnx.md +55 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/pca.md +91 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/point-in-time-feature-retrieval-mlfeatures_at_time-mlentity_features_at_time.md +25 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/quick-comparison.md +14 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/random_forest_classifier-random_forest_regressor.md +123 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/remote-remote-model-over-a-vertex-ai-endpoint-the-mechanism.md +87 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/tensorflow.md +48 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/tensorflow_lite.md +29 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/text-preprocessing-functions-mlngrams-mltf_idf-mlbag_of_words.md +18 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/timesfm-built-in-foundation-forecaster-see-bigquery-ai.md +28 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/transform_only.md +91 -0
- bigquery_data_science/_skills/bigquery-ml/syntax/xgboost.md +72 -0
- bigquery_data_science/_skills/pinned-models.json +28 -0
- bigquery_data_science/catalog.py +1107 -0
- bigquery_data_science/cli.py +786 -0
- bigquery_data_science/installer.py +282 -0
- bigquery_data_science/mcp_server.py +317 -0
- bigquery_data_science/tooling/__init__.py +15 -0
- bigquery_data_science/tooling/convert_notebook.py +176 -0
- bigquery_data_science/tooling/link_policy.py +247 -0
- bigquery_data_science/tooling/manifest.py +152 -0
- bigquery_data_science/tooling/markdown_anchors.py +84 -0
- bigquery_data_science/tooling/narrative_drift.py +163 -0
- bigquery_data_science/tooling/pinned_models.py +216 -0
- bigquery_data_science/tooling/reference_structure.py +85 -0
- bigquery_data_science/tooling/sync_syntax.py +407 -0
- bigquery_data_science/tooling/validate.py +208 -0
- bigquery_data_science-0.7.0.dist-info/METADATA +306 -0
- bigquery_data_science-0.7.0.dist-info/RECORD +286 -0
- bigquery_data_science-0.7.0.dist-info/WHEEL +4 -0
- bigquery_data_science-0.7.0.dist-info/entry_points.txt +5 -0
- bigquery_data_science-0.7.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
> **Source (`statmike/bigquery-data-science`):** **Notebook:** [`bigquery-ai/functions/vector_search/vector_search.ipynb`](https://github.com/statmike/bigquery-data-science/blob/main/bigquery-ai/functions/vector_search/vector_search.ipynb) · **SQL:** [`bigquery-ai/functions/vector_search/vector_search.sql`](https://github.com/statmike/bigquery-data-science/blob/main/bigquery-ai/functions/vector_search/vector_search.sql)
|
|
2
|
+
|
|
3
|
+
# VECTOR_SEARCH — BigQuery AI Functions
|
|
4
|
+
|
|
5
|
+
`VECTOR_SEARCH` is a table-valued function that finds the top-K nearest neighbors from a base table using pre-computed embeddings. Supports vector indexes for efficient approximate nearest neighbor (ANN) search.
|
|
6
|
+
|
|
7
|
+
**Status:** `VECTOR_SEARCH` is **GA**. The single-search syntax (`query_value =>`) is **Preview**, and it is also the hybrid search syntax — `lexical_search_columns` is only accepted alongside `query_value` (verified 2026-09-01). Batch search with a query table is the GA form.
|
|
8
|
+
|
|
9
|
+
**When to use it:**
|
|
10
|
+
- You need semantic search over a table with pre-computed embeddings
|
|
11
|
+
- You want top-K nearest neighbor search at scale
|
|
12
|
+
- You need batch search (multiple queries at once)
|
|
13
|
+
- You need hybrid search — semantic similarity fused with exact keyword matching
|
|
14
|
+
- You want to build a RAG (Retrieval Augmented Generation) pipeline
|
|
15
|
+
|
|
16
|
+
**Vector indexes:** By default, `VECTOR_SEARCH` uses brute-force KNN (exact nearest neighbor), which scans every row. For large tables, BigQuery supports vector indexes that enable approximate nearest neighbor (ANN) search for dramatically faster queries:
|
|
17
|
+
|
|
18
|
+
| Index Type | Best For | How It Works |
|
|
19
|
+
|------------|----------|--------------|
|
|
20
|
+
| `IVF` (Inverted File) | General-purpose ANN | Partitions vectors into clusters; searches only the nearest clusters |
|
|
21
|
+
| `TreeAH` (Tree Asymmetric Hashing) | Large-scale, high-throughput | Uses a tree structure with asymmetric hashing for efficient search |
|
|
22
|
+
|
|
23
|
+
Create a vector index with `CREATE VECTOR INDEX` — no changes to your `VECTOR_SEARCH` queries are needed. Adding `lexical_search_columns` (with those same columns repeated in a `STORING` clause) extends the index to accelerate the keyword half of a hybrid search. See the [Vector Index documentation](https://cloud.google.com/bigquery/docs/vector-index) for details.
|
|
24
|
+
|
|
25
|
+
**Alternatives:**
|
|
26
|
+
- `functions/ai_search` (`AI.SEARCH`) — Simplified semantic search with autonomous embedding generation
|
|
27
|
+
- `functions/ai_similarity` (`AI.SIMILARITY`) — Cosine similarity between two specific inputs
|
|
28
|
+
- `functions/ai_embed` (`AI.EMBED`) — Create embeddings for individual values
|
|
29
|
+
|
|
30
|
+
**Featured in:** `workflows/semantic_search` (Semantic Search System) | `workflows/rag_pipeline` (RAG Pipeline) | `workflows/document_rag` (Document RAG Pipeline) | `workflows/catalog_search` (Catalog Search) | `workflows/log_analysis` (Log Analysis) | `workflows/image_deduplication` (Image Deduplication)
|
|
31
|
+
|
|
32
|
+
**References:** `RESOURCES.md` (Full syntax reference) | [Official documentation](https://cloud.google.com/bigquery/docs/reference/standard-sql/search_functions#vector_search) | `setup` (Setup guide)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
## Setup
|
|
36
|
+
|
|
37
|
+
Set your project and location, authenticate, and create a temporary dataset for this notebook.
|
|
38
|
+
|
|
39
|
+
> This function doesn't require a connection or model — it uses end-user credentials automatically. See the `setup` (Setup Reference) for details.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
PROJECT_ID = 'statmike-mlops-349915' # <-- Replace with your project ID
|
|
43
|
+
LOCATION = 'US' # BigQuery dataset location
|
|
44
|
+
DATASET_ID = 'bq_ai_functions' # Shared dataset across all notebooks
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Environment
|
|
48
|
+
|
|
49
|
+
> **Already set up the project environment?** The cell below is a no-op — packages are already in your kernel. See the `setup` (Setup Reference) for details.
|
|
50
|
+
>
|
|
51
|
+
> **Running standalone** (Colab, Colab Enterprise, Vertex AI Workbench)? The cell below installs required packages into your current kernel.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from google.cloud import bigquery
|
|
55
|
+
import pandas as pd
|
|
56
|
+
|
|
57
|
+
client = bigquery.Client(project=PROJECT_ID)
|
|
58
|
+
pd.set_option('display.max_colwidth', None)
|
|
59
|
+
|
|
60
|
+
# Create the shared dataset (idempotent)
|
|
61
|
+
dataset_ref = bigquery.DatasetReference(PROJECT_ID, DATASET_ID)
|
|
62
|
+
dataset = bigquery.Dataset(dataset_ref)
|
|
63
|
+
dataset.location = LOCATION
|
|
64
|
+
client.create_dataset(dataset, exists_ok=True)
|
|
65
|
+
print(f'Dataset {PROJECT_ID}.{DATASET_ID} ready')
|
|
66
|
+
|
|
67
|
+
# Register %%bigquery cell magic (auto-loaded in Colab, needed elsewhere)
|
|
68
|
+
%load_ext bigquery_magics
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
## Examples — SQL
|
|
73
|
+
|
|
74
|
+
Progressive examples from simplest to most advanced. Each cell adds one new concept.
|
|
75
|
+
|
|
76
|
+
### Setup: Create sample data with embeddings
|
|
77
|
+
|
|
78
|
+
First, create a table with pre-computed embeddings to search against.
|
|
79
|
+
|
|
80
|
+
**Note on task_type:** This example uses the **asymmetric** embedding pattern — documents are embedded with `RETRIEVAL_DOCUMENT` (below), while search queries use `RETRIEVAL_QUERY` (in later examples). This is the recommended pattern for retrieval use cases. See the `functions/ai_embed` (`AI.EMBED`) notebook for the full list of task types.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# Create a sample knowledge base with embeddings
|
|
84
|
+
query = f'''
|
|
85
|
+
CREATE OR REPLACE TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_products` AS
|
|
86
|
+
SELECT id, product, category, description,
|
|
87
|
+
(AI.EMBED(content => description, endpoint => 'text-embedding-005', task_type => 'RETRIEVAL_DOCUMENT')).result AS embedding
|
|
88
|
+
FROM UNNEST([
|
|
89
|
+
STRUCT(1 AS id, 'Laptop' AS product, 'Computing' AS category, 'High-performance laptop with 16GB RAM and SSD storage for data science and programming.' AS description),
|
|
90
|
+
STRUCT(2, 'Headphones', 'Audio', 'Wireless noise-cancelling headphones with 30-hour battery life and premium sound.'),
|
|
91
|
+
STRUCT(3, 'Standing Desk', 'Furniture', 'Electric height-adjustable standing desk with memory presets and cable management.'),
|
|
92
|
+
STRUCT(4, 'Monitor', 'Computing', '32-inch 4K monitor with USB-C connectivity and built-in KVM switch.'),
|
|
93
|
+
STRUCT(5, 'Keyboard', 'Peripherals', 'Mechanical keyboard with programmable keys and RGB backlighting.'),
|
|
94
|
+
STRUCT(6, 'Mouse', 'Peripherals', 'Ergonomic vertical mouse designed to reduce wrist strain.'),
|
|
95
|
+
STRUCT(7, 'Webcam', 'Audio', '4K webcam with auto-framing and noise-cancelling microphone.'),
|
|
96
|
+
STRUCT(8, 'Dock', 'Computing', 'Thunderbolt 4 docking station with dual 4K display support.')
|
|
97
|
+
])
|
|
98
|
+
'''
|
|
99
|
+
client.query(query).result()
|
|
100
|
+
print('Sample data created')
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 1. Single search (Preview)
|
|
104
|
+
|
|
105
|
+
Search for the nearest neighbors to a single query value. Use `query_value` for single searches.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
query = f'''
|
|
109
|
+
SELECT base.product, base.description, distance
|
|
110
|
+
FROM VECTOR_SEARCH(
|
|
111
|
+
TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_products`,
|
|
112
|
+
'embedding',
|
|
113
|
+
query_value => (AI.EMBED(content => 'comfortable work setup for long hours',
|
|
114
|
+
endpoint => 'text-embedding-005',
|
|
115
|
+
task_type => 'RETRIEVAL_QUERY')).result,
|
|
116
|
+
top_k => 3,
|
|
117
|
+
distance_type => 'COSINE'
|
|
118
|
+
)
|
|
119
|
+
'''
|
|
120
|
+
client.query(query).to_dataframe()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 2. Batch search
|
|
124
|
+
|
|
125
|
+
Search for multiple queries at once. Results include `query` and `base` STRUCTs plus `distance`.
|
|
126
|
+
|
|
127
|
+
**Batch and hybrid are mutually exclusive.** The keyword arguments shown in example 6 are only accepted alongside `query_value`, so a batch call can never be hybrid. The second cell below runs the combination and prints the error BigQuery returns.
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
query = f'''
|
|
131
|
+
SELECT
|
|
132
|
+
query.search_term,
|
|
133
|
+
base.product,
|
|
134
|
+
base.description,
|
|
135
|
+
distance
|
|
136
|
+
FROM VECTOR_SEARCH(
|
|
137
|
+
TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_products`,
|
|
138
|
+
'embedding',
|
|
139
|
+
(SELECT
|
|
140
|
+
search_term,
|
|
141
|
+
(AI.EMBED(content => search_term, endpoint => 'text-embedding-005',
|
|
142
|
+
task_type => 'RETRIEVAL_QUERY')).result AS embedding
|
|
143
|
+
FROM UNNEST(['audio equipment', 'computer display']) AS search_term),
|
|
144
|
+
top_k => 2,
|
|
145
|
+
distance_type => 'COSINE'
|
|
146
|
+
)
|
|
147
|
+
'''
|
|
148
|
+
client.query(query).to_dataframe()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
# Batch + hybrid: add the lexical arguments to the batch call above and it is rejected
|
|
153
|
+
batch_hybrid = f'''
|
|
154
|
+
SELECT query.search_term, base.product, distance
|
|
155
|
+
FROM VECTOR_SEARCH(
|
|
156
|
+
TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_products`,
|
|
157
|
+
'embedding',
|
|
158
|
+
(SELECT
|
|
159
|
+
search_term,
|
|
160
|
+
(AI.EMBED(content => search_term, endpoint => 'text-embedding-005',
|
|
161
|
+
task_type => 'RETRIEVAL_QUERY')).result AS embedding
|
|
162
|
+
FROM UNNEST(['audio equipment', 'computer display']) AS search_term),
|
|
163
|
+
lexical_search_columns => ['product', 'description'],
|
|
164
|
+
lexical_search_query_value => 'noise-cancelling',
|
|
165
|
+
top_k => 2,
|
|
166
|
+
distance_type => 'COSINE'
|
|
167
|
+
)
|
|
168
|
+
'''
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
client.query(batch_hybrid).result()
|
|
172
|
+
print('Batch + hybrid succeeded — the restriction has changed')
|
|
173
|
+
except Exception as e:
|
|
174
|
+
print('Batch + hybrid ->', str(e).splitlines()[0])
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 3. Using EUCLIDEAN distance
|
|
178
|
+
|
|
179
|
+
Change the distance metric. Options: `COSINE`, `EUCLIDEAN` (default), `DOT_PRODUCT`.
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
query = f'''
|
|
183
|
+
SELECT base.product, base.description, distance
|
|
184
|
+
FROM VECTOR_SEARCH(
|
|
185
|
+
TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_products`,
|
|
186
|
+
'embedding',
|
|
187
|
+
query_value => (AI.EMBED(content => 'typing device', endpoint => 'text-embedding-005',
|
|
188
|
+
task_type => 'RETRIEVAL_QUERY')).result,
|
|
189
|
+
top_k => 3,
|
|
190
|
+
distance_type => 'EUCLIDEAN'
|
|
191
|
+
)
|
|
192
|
+
'''
|
|
193
|
+
client.query(query).to_dataframe()
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 4. Filtered search (pre-filtering)
|
|
197
|
+
|
|
198
|
+
Restrict the search to a subset of the base table by passing a subquery instead of `TABLE`. Only matching rows are considered for nearest neighbor search — useful for scoping results by category, region, status, etc.
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
query = f'''
|
|
202
|
+
SELECT base.product, base.category, base.description, distance
|
|
203
|
+
FROM VECTOR_SEARCH(
|
|
204
|
+
(SELECT * FROM `{PROJECT_ID}.{DATASET_ID}.vector_search_products` WHERE category = 'Computing'),
|
|
205
|
+
'embedding',
|
|
206
|
+
query_value => (AI.EMBED(content => 'display for programming',
|
|
207
|
+
endpoint => 'text-embedding-005',
|
|
208
|
+
task_type => 'RETRIEVAL_QUERY')).result,
|
|
209
|
+
top_k => 3,
|
|
210
|
+
distance_type => 'COSINE'
|
|
211
|
+
)
|
|
212
|
+
'''
|
|
213
|
+
client.query(query).to_dataframe()
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 5. RAG pattern — search then generate
|
|
217
|
+
|
|
218
|
+
Retrieve relevant context with VECTOR_SEARCH, then pass it to AI.GENERATE for grounded answers.
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
query = f'''
|
|
222
|
+
WITH context AS (
|
|
223
|
+
SELECT
|
|
224
|
+
STRING_AGG(base.product || ': ' || base.description, '; ') AS retrieved_docs
|
|
225
|
+
FROM VECTOR_SEARCH(
|
|
226
|
+
TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_products`,
|
|
227
|
+
'embedding',
|
|
228
|
+
query_value => (AI.EMBED(content => 'ergonomic office equipment',
|
|
229
|
+
endpoint => 'text-embedding-005',
|
|
230
|
+
task_type => 'RETRIEVAL_QUERY')).result,
|
|
231
|
+
top_k => 3,
|
|
232
|
+
distance_type => 'COSINE'
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
SELECT (AI.GENERATE(
|
|
236
|
+
CONCAT(
|
|
237
|
+
'Based on these products: ', c.retrieved_docs,
|
|
238
|
+
' --- Recommend the best products for someone setting up an ergonomic home office. Explain why.'
|
|
239
|
+
)
|
|
240
|
+
)).result AS recommendation
|
|
241
|
+
FROM context c
|
|
242
|
+
'''
|
|
243
|
+
df = client.query(query).to_dataframe()
|
|
244
|
+
print(df.iloc[0]['recommendation'])
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### 6. Hybrid search (Preview) — semantic plus keyword
|
|
248
|
+
|
|
249
|
+
A hybrid search fuses the semantic result list with a lexical (keyword) result list. Two arguments turn it on:
|
|
250
|
+
|
|
251
|
+
- `lexical_search_columns` — an `ARRAY<STRING>` naming `STRING` columns in the base table. A **non-empty list is what makes the search hybrid**.
|
|
252
|
+
- `lexical_search_query_value` — the keyword text, matched against every column in that list. Required whenever `lexical_search_columns` is set.
|
|
253
|
+
|
|
254
|
+
Hybrid requires the **single-query syntax** (`query_value =>`) and cannot batch. The two legs are independent: the lexical columns need not be the column the embeddings came from, and `lexical_search_query_value` need not match `query_value`. Below, the vector leg searches on intent while the lexical leg pins an exact product token.
|
|
255
|
+
|
|
256
|
+
No vector index is needed — this runs brute force on an 8-row table. An index only speeds up the lexical leg (example 9).
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
query = f'''
|
|
260
|
+
SELECT base.product, base.category, base.description, distance
|
|
261
|
+
FROM VECTOR_SEARCH(
|
|
262
|
+
TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_products`,
|
|
263
|
+
'embedding',
|
|
264
|
+
query_value => (AI.EMBED(content => 'connect two external displays to a laptop',
|
|
265
|
+
endpoint => 'text-embedding-005',
|
|
266
|
+
task_type => 'RETRIEVAL_QUERY')).result,
|
|
267
|
+
lexical_search_columns => ['product', 'description'],
|
|
268
|
+
lexical_search_query_value => 'Thunderbolt 4 docking station',
|
|
269
|
+
top_k => 3,
|
|
270
|
+
distance_type => 'COSINE'
|
|
271
|
+
)
|
|
272
|
+
'''
|
|
273
|
+
client.query(query).to_dataframe()
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Setup: a larger catalog with part numbers
|
|
277
|
+
|
|
278
|
+
Examples 7 and 8 need a corpus where the semantic leg can plausibly be wrong. This catalog holds 24 products — a dense cluster of display-connectivity items among them — plus a `sku` column carrying manufacturer part codes.
|
|
279
|
+
|
|
280
|
+
The embedding is generated from `description` only, so **the part code never enters the embedding**. That is the realistic shape of the problem: a buyer who knows an exact part number, searching a catalog whose vectors know only prose.
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
# A 24-row catalog: descriptions are embedded, part codes are not
|
|
284
|
+
query = f'''
|
|
285
|
+
CREATE OR REPLACE TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_catalog` AS
|
|
286
|
+
SELECT id, sku, product, description,
|
|
287
|
+
(AI.EMBED(content => description, endpoint => 'text-embedding-005', task_type => 'RETRIEVAL_DOCUMENT')).result AS embedding
|
|
288
|
+
FROM UNNEST([
|
|
289
|
+
STRUCT(1 AS id, 'TBT-8841' AS sku, 'Thunderbolt Dock' AS product, 'Thunderbolt 4 docking station with dual 4K display support and 90W laptop charging.' AS description),
|
|
290
|
+
STRUCT(2, 'DCK-2210', 'USB-C Hub', 'Seven-port USB-C hub with HDMI output, card reader, and gigabit ethernet.'),
|
|
291
|
+
STRUCT(3, 'MON-3240', 'Ultrawide Monitor', '32-inch 4K monitor with USB-C connectivity and a built-in KVM switch.'),
|
|
292
|
+
STRUCT(4, 'MON-2711', 'Portable Monitor', '15-inch portable travel monitor powered over a single USB-C cable.'),
|
|
293
|
+
STRUCT(5, 'CBL-4417', 'DisplayPort Cable', 'DisplayPort 2.1 cable rated for 8K at 60Hz, two meters, braided jacket.'),
|
|
294
|
+
STRUCT(6, 'CBL-9032', 'HDMI Splitter', 'One-in two-out HDMI 2.1 splitter that mirrors a source to two screens.'),
|
|
295
|
+
STRUCT(7, 'ADP-5567', 'Display Adapter', 'USB-C to dual HDMI adapter using DisplayLink for extended desktops on any laptop.'),
|
|
296
|
+
STRUCT(8, 'KVM-7788', 'KVM Switch', 'Two-computer KVM switch sharing one keyboard, mouse, and monitor.'),
|
|
297
|
+
STRUCT(9, 'LAP-1120', 'Developer Laptop', 'High-performance laptop with 32GB RAM and NVMe storage for data science.'),
|
|
298
|
+
STRUCT(10, 'KBD-6310', 'Mechanical Keyboard', 'Mechanical keyboard with programmable keys and RGB backlighting.'),
|
|
299
|
+
STRUCT(11, 'MSE-4402', 'Vertical Mouse', 'Ergonomic vertical mouse designed to reduce wrist strain.'),
|
|
300
|
+
STRUCT(12, 'HDS-2077', 'Wireless Headset', 'Wireless noise-cancelling headset with 30-hour battery life.'),
|
|
301
|
+
STRUCT(13, 'CAM-9911', 'Conference Camera', '4K conference camera with auto-framing and beamforming microphones.'),
|
|
302
|
+
STRUCT(14, 'DSK-1450', 'Standing Desk', 'Electric height-adjustable standing desk with memory presets.'),
|
|
303
|
+
STRUCT(15, 'CHR-3390', 'Task Chair', 'Mesh task chair with adjustable lumbar support and 4D armrests.'),
|
|
304
|
+
STRUCT(16, 'ARM-8123', 'Monitor Arm', 'Gas-spring dual monitor arm clamping to desks up to 90mm thick.'),
|
|
305
|
+
STRUCT(17, 'HUB-5540', 'Travel Hub', 'Slim aluminum travel hub with pass-through power, HDMI, and gigabit ethernet.'),
|
|
306
|
+
STRUCT(18, 'PWR-7702', 'GaN Charger', '100W GaN wall charger with two USB-C ports and one USB-A port.'),
|
|
307
|
+
STRUCT(19, 'SSD-6604', 'Portable SSD', 'Two-terabyte portable NVMe SSD in a rugged USB-C enclosure.'),
|
|
308
|
+
STRUCT(20, 'NAS-3318', 'Desktop NAS', 'Four-bay desktop NAS with 2.5 gigabit ethernet and hardware transcoding.'),
|
|
309
|
+
STRUCT(21, 'RTR-2205', 'Mesh Router', 'Tri-band mesh router covering 5,000 square feet with wired backhaul.'),
|
|
310
|
+
STRUCT(22, 'SWT-1180', 'Network Switch', 'Eight-port managed network switch with power over ethernet.'),
|
|
311
|
+
STRUCT(23, 'UPS-4406', 'Battery Backup', 'Line-interactive UPS with 1500VA capacity and pure sine wave output.'),
|
|
312
|
+
STRUCT(24, 'PRN-8890', 'Label Printer', 'Thermal label printer for shipping labels, no ink required.')
|
|
313
|
+
])
|
|
314
|
+
'''
|
|
315
|
+
client.query(query).result()
|
|
316
|
+
print('Catalog created — 24 rows, embeddings from description only')
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### 7. Semantic-only vs hybrid, head to head
|
|
320
|
+
|
|
321
|
+
Same query text, same `top_k`, run twice. The first call is pure semantic. The second adds `lexical_search_columns => ['sku']` and puts an exact part code in `lexical_search_query_value` — a token the vector leg cannot see, because `sku` was never embedded.
|
|
322
|
+
|
|
323
|
+
`top_k => 5` against 24 rows is the setting that makes the comparison meaningful. A `top_k` equal to the row count returns the whole table on both legs, so the two rankings can only be reordered, never differ in membership. Retrieval can only miss something when top-K is smaller than the corpus.
|
|
324
|
+
|
|
325
|
+
The cell prints the two set differences instead of claiming an outcome, and reports each row's rank over the whole 24-row catalog so you can see where the vector leg alone puts the part-code row. Example 8 takes the fused score apart and derives the two gates that set how far a lexical match can reach: at `top_k => 5` it reaches down to semantic rank 10, and a row deeper than that stays out however exact the token.
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
search_text = 'run two external monitors from one laptop port'
|
|
329
|
+
part_code = 'DCK-2210'
|
|
330
|
+
top_k = 5
|
|
331
|
+
|
|
332
|
+
query = f'''
|
|
333
|
+
WITH semantic AS (
|
|
334
|
+
SELECT base.id, ROW_NUMBER() OVER (ORDER BY distance) AS semantic_rank
|
|
335
|
+
FROM VECTOR_SEARCH(
|
|
336
|
+
TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_catalog`,
|
|
337
|
+
'embedding',
|
|
338
|
+
query_value => (AI.EMBED(content => '{search_text}', endpoint => 'text-embedding-005',
|
|
339
|
+
task_type => 'RETRIEVAL_QUERY')).result,
|
|
340
|
+
top_k => {top_k},
|
|
341
|
+
distance_type => 'COSINE'
|
|
342
|
+
)
|
|
343
|
+
),
|
|
344
|
+
hybrid AS (
|
|
345
|
+
SELECT base.id, ROW_NUMBER() OVER (ORDER BY distance) AS hybrid_rank
|
|
346
|
+
FROM VECTOR_SEARCH(
|
|
347
|
+
TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_catalog`,
|
|
348
|
+
'embedding',
|
|
349
|
+
query_value => (AI.EMBED(content => '{search_text}', endpoint => 'text-embedding-005',
|
|
350
|
+
task_type => 'RETRIEVAL_QUERY')).result,
|
|
351
|
+
lexical_search_columns => ['sku'],
|
|
352
|
+
lexical_search_query_value => '{part_code}',
|
|
353
|
+
top_k => {top_k},
|
|
354
|
+
distance_type => 'COSINE'
|
|
355
|
+
)
|
|
356
|
+
),
|
|
357
|
+
whole_catalog AS (
|
|
358
|
+
SELECT base.id, base.sku, base.product,
|
|
359
|
+
ROW_NUMBER() OVER (ORDER BY distance) AS vector_rank_overall
|
|
360
|
+
FROM VECTOR_SEARCH(
|
|
361
|
+
TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_catalog`,
|
|
362
|
+
'embedding',
|
|
363
|
+
query_value => (AI.EMBED(content => '{search_text}', endpoint => 'text-embedding-005',
|
|
364
|
+
task_type => 'RETRIEVAL_QUERY')).result,
|
|
365
|
+
top_k => 24,
|
|
366
|
+
distance_type => 'COSINE'
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
SELECT c.sku, c.product, c.vector_rank_overall, s.semantic_rank, h.hybrid_rank
|
|
370
|
+
FROM whole_catalog c
|
|
371
|
+
LEFT JOIN semantic s USING (id)
|
|
372
|
+
LEFT JOIN hybrid h USING (id)
|
|
373
|
+
WHERE s.semantic_rank IS NOT NULL OR h.hybrid_rank IS NOT NULL
|
|
374
|
+
ORDER BY h.hybrid_rank NULLS LAST
|
|
375
|
+
'''
|
|
376
|
+
df = client.query(query).to_dataframe()
|
|
377
|
+
|
|
378
|
+
added = df.loc[df['semantic_rank'].isna(), 'sku'].tolist()
|
|
379
|
+
lost = df.loc[df['hybrid_rank'].isna(), 'sku'].tolist()
|
|
380
|
+
print(f'query text : {search_text}')
|
|
381
|
+
print(f'lexical value: {part_code} (matched against sku, which is not embedded)')
|
|
382
|
+
print(f'Rows hybrid added that the semantic-only top {top_k} missed: {added or "none"}')
|
|
383
|
+
print(f'Rows the semantic-only top {top_k} had that hybrid dropped : {lost or "none"}')
|
|
384
|
+
for s in added:
|
|
385
|
+
overall = int(df.loc[df['sku'] == s, 'vector_rank_overall'].iloc[0])
|
|
386
|
+
print(f' {s} is at vector rank {overall} of 24 on the semantic leg alone')
|
|
387
|
+
df
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### 8. Reading the hybrid score — reciprocal rank fusion
|
|
391
|
+
|
|
392
|
+
In hybrid mode `distance` is **not a distance**. Each leg ranks the rows independently and the two ranks are combined by reciprocal rank fusion, so the value depends only on positions — never on geometry. The cell below reproduces every returned value from integer ranks alone, to within 1e-12:
|
|
393
|
+
|
|
394
|
+
`distance = 1 - ( 1/(60 + rank_vector) + 1/(61 + rank_lexical) )`
|
|
395
|
+
|
|
396
|
+
Only one of those two ranks is ever read directly. `VECTOR_SEARCH` returns no rank column, so `rank_vector` comes from a separate semantic-only run over the same rows and the lexical term is whatever remains. What that pins is a set of *denominators*, not a pair of constants: the semantic leg's top row contributes `1/61`, and the lexical leg's top row contributes `1/62`. Two readings fit identically, because `1/(61 + rank_lexical)` and `1/(60 + (rank_lexical + 1))` are the same number.
|
|
397
|
+
|
|
398
|
+
| reading | semantic leg | lexical leg |
|
|
399
|
+
|---|---|---|
|
|
400
|
+
| A — two constants | k = 60, ranks `1..n` | k = 61, ranks `1..n` |
|
|
401
|
+
| **B — one constant, offset ranks** | k = 60, ranks `1..n` | k = 60, ranks `2..n+1` |
|
|
402
|
+
|
|
403
|
+
Nothing observable from outside BigQuery separates them, but **B is the likelier**. `k = 60` over 1-based ranks is canonical reciprocal rank fusion — Cormack, Clarke and Buettcher (2009) — and the default wherever the constant is exposed: Elasticsearch and OpenSearch both name it `rank_constant` and default it to 60, and Spanner and AlloyDB write 60 into their documented SQL. No published implementation uses 61 as the constant. The semantic leg here lands on the canonical `1/61` for its own top row, so the extra `+1` sits on the lexical side specifically.
|
|
404
|
+
|
|
405
|
+
**Why the lexical leg would start at 2 is a separate question, and it is open.** Reading B says *where* the `+1` sits, not *why*. Three mechanisms fit equally well: a **deliberate tie-break** (at equal ranks `1/(60 + r)` beats `1/(61 + r)`, so the semantic leg wins every tie by a hair — exactly what you would implement if hybrid should never reorder a tie against the embedding); a **reserved slot** (if lexical position 1 holds something that is not a result, real rows begin at 2 naturally); or an **off-by-one** (61 is universal as the rank-1 *denominator*, `1/(60 + 1)`, and transcribing that as the constant gives the observed `1/62`). Only the last is a defect, and it is the least charitable of the three. Nothing observable from outside distinguishes them, so none of them is presented here as the explanation.
|
|
406
|
+
|
|
407
|
+
One argument looks decisive and is not: decoding with 60 on both legs implies lexical ranks `2..n+1`, and no n-row list hands out rank n+1. That rules out a shared base with ordinary 1-based numbering, but it is exactly what reading B predicts — a contiguous block starting at 2 is evidence *for* an offset, not against a shared constant.
|
|
408
|
+
|
|
409
|
+
The 60/61 form is kept below because it is the shortest expression that reproduces every observed value. Read it as arithmetic that holds, not as two deliberate design decisions. Note that `1 - (1/61 + 1/62)` = `0.9674775251189847` is the score of a row first on *both* legs: the best attainable hybrid score, and not 0.
|
|
410
|
+
|
|
411
|
+
On the no-match run below both ranks are the same integer *r*, and `1/(60 + r) + 1/(61 + r)` reproduces all 24 returned values. Hybrid scores cluster just under 1, lower is still better, and they are not comparable to the distances a semantic-only search returns.
|
|
412
|
+
|
|
413
|
+
The query recovers the decomposition. It takes `rank_vector` from a semantic-only search over the same rows, subtracts that leg's contribution from `1 - distance`, and inverts what remains to reveal the lexical rank. `formula_holds` re-derives the returned score from the two integer ranks and compares — the ranks come back as exact integers, which is what pins the constants.
|
|
414
|
+
|
|
415
|
+
It runs the fusion twice: once with the exact part code, and once with `NOSUCHTOKEN`, a lexical value nothing in the catalog matches. **Compare those two columns — they are the whole lesson.**
|
|
416
|
+
|
|
417
|
+
- With the exact part code, that row solves to `rank_lexical` 1. Every row that outranked it on the vector leg shifts down exactly one lexical position; every row below it keeps the position it already had.
|
|
418
|
+
- With a value that matches nothing, `rank_lexical` equals `rank_vector` for every row. The lexical leg does not drop rows it cannot score — it ranks every candidate it is given, falling back to the vector ordering.
|
|
419
|
+
|
|
420
|
+
That second point is the one to carry away, together with the limit on which rows the lexical leg ever sees. BigQuery hands it the top `10 * top_k` rows by semantic rank — that **candidate pool** is BM25's whole world. Inside the pool the leg ranks everything: matches take lexical ranks 1..*m*, and every remaining pooled row falls back to its semantic order behind them. No pooled row forfeits a term, which is why a hybrid search whose lexical value matches nothing returns precisely the semantic ordering. The cell above runs at `top_k => 24` over 24 rows, so the pool of 240 covers the whole catalog and the fallback is visible on every row.
|
|
421
|
+
|
|
422
|
+
A matched row therefore has **two gates** to clear:
|
|
423
|
+
|
|
424
|
+
```
|
|
425
|
+
Gate 1 (candidate pool): rank_vector <= 10 * top_k
|
|
426
|
+
Gate 2 (fusion score): 1/(60 + rank_vector) + 1/(61 + rank_lexical)
|
|
427
|
+
must beat the row holding the last slot
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Gate 2 is the arithmetic of the score, and a match is worth a bounded amount. Promotion to lexical rank 1 contributes `1/62` ≈ `0.0161`, while the row holding the last returned slot sits at semantic rank `top_k` and — pushed down one by the match — lexical rank `top_k + 1`, scoring `1/(60 + top_k) + 1/(62 + top_k)`. Comparing those two gives the deepest semantic rank the score alone would allow.
|
|
431
|
+
|
|
432
|
+
Gate 1 is a hard ceiling over the top of that. A row deeper than `10 * top_k` on the semantic leg receives no lexical rank at all — BM25 never sees it, however exactly the token matches — so no score can rescue it. The **effective reach** of a lexical match is the smaller of the two, `min( score reach, 10 * top_k )`. The score gate binds below `top_k = 51`, the pool gate from 51 up.
|
|
433
|
+
|
|
434
|
+
| `top_k` | 2 | 3 | 5 | 10 | 20 | 30 | 40 | 50 | 51 | 64 | 100 | 208 | 300 |
|
|
435
|
+
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
436
|
+
| deepest semantic rank retrievable | 3 | 6 | 10 | 23 | 56 | 110 | 212 | 468 | 510 | 640 | 1,000 | 2,080 | 3,000 |
|
|
437
|
+
|
|
438
|
+
So hybrid re-ranks *and* widens — it does pull rows in from outside the semantic top-`top_k` — but the widening is bounded by `top_k` twice over, and it grows only in proportion to `top_k`. At the `top_k => 5` example 7 uses, a part-code match rescues a row from as deep as semantic rank 10 and no deeper. At `top_k => 64` it reaches semantic rank 640 and no deeper: the pool ends there, and rank 641 is invisible to BM25.
|
|
439
|
+
|
|
440
|
+
The pool gate is sharp enough to predict. Plant a target at semantic rank 3,000 in a 3,000-row table and the flip is predicted at `top_k = 300` — 299 returns nothing, 300 returns the row. Plant it at rank 1,500 in a 1,500-row table and the flip lands at 150, again exactly. At rank 500 in a 500-row table the pool never binds and the score gate sets the flip at 51. All three probe tables sit below BigQuery's 5,000-row `CREATE VECTOR INDEX` floor, so they are brute-force scanned and no index exists to restrict anything. The 7,275-row product catalog is the opposite case — it carries a hybrid `TREE_AH` index with `lexical_search_columns`, `ACTIVE` at 100% coverage — and the pool gate shows up there identically. The gate is therefore neither an index artifact nor something an index can avoid.
|
|
441
|
+
|
|
442
|
+
**The diagnostic signature.** A target outside the pool looks exactly like a token that matches nothing: `rank_lexical` equals `rank_vector` across the whole result set, the shape the `rank_lexical_nomatch` column shows above. Every returned distance is then `1 - ( 1/(60 + r) + 1/(61 + r) )`, so the top of the page reads `0.967478`. A page whose best value is `0.967734` had a match fire. That one number tells you whether to raise `top_k` or fix the token.
|
|
443
|
+
|
|
444
|
+
The rule that follows sizes `top_k` from the depth you need to reach, and it is a floor rather than a recipe: **to retrieve a row at semantic rank *R* by exact token, `top_k` ≥ *R*/10 is necessary but not sufficient.** That ratio clears the pool gate only. Below *R* of about 500 the score gate is the binding one and *R*/10 falls short — a row at semantic rank 100 needs `top_k` 29 rather than 10, at rank 200 exactly 40 rather than 20, at rank 500 exactly 51 rather than 50. Size `top_k` to at least *R*/10, then read the real requirement off the reach table above; the two gates cross at `top_k` 51. With a small `top_k` (≤ ~30) an exact token only nudges rows already near the top, so do not expect recall from it. A 7,275-row product catalog makes the cost concrete: with the wanted row at semantic rank 2,072, `top_k => 64` returns nothing at all, because the pool reached only 640 rows deep — that row needs `top_k >= 208`. And when the identifier *is* the entire query and you want certainty rather than a good chance, use `WHERE sku = @sku` — a predicate cannot be outranked by a fusion score, and it has no pool.
|
|
445
|
+
|
|
446
|
+
**Reverse-engineered, not documented.** No Google reference page states the fusion algorithm, no rank or score column is returned, and the constants can change without notice — a Google Cloud blog post naming "Reciprocal Rank Fusion and BM25" is the only official mention of the algorithms. Production code should treat a hybrid `distance` as an opaque ordering. (verified 2026-09-02)
|
|
447
|
+
|
|
448
|
+
```python
|
|
449
|
+
query = f'''
|
|
450
|
+
WITH vector_leg AS (
|
|
451
|
+
SELECT base.id, base.sku, base.product,
|
|
452
|
+
ROW_NUMBER() OVER (ORDER BY distance) AS rank_vector
|
|
453
|
+
FROM VECTOR_SEARCH(
|
|
454
|
+
TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_catalog`,
|
|
455
|
+
'embedding',
|
|
456
|
+
query_value => (AI.EMBED(content => '{search_text}', endpoint => 'text-embedding-005',
|
|
457
|
+
task_type => 'RETRIEVAL_QUERY')).result,
|
|
458
|
+
top_k => 24,
|
|
459
|
+
distance_type => 'COSINE'
|
|
460
|
+
)
|
|
461
|
+
),
|
|
462
|
+
hybrid_exact AS (
|
|
463
|
+
SELECT base.id, distance AS hybrid_distance
|
|
464
|
+
FROM VECTOR_SEARCH(
|
|
465
|
+
TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_catalog`,
|
|
466
|
+
'embedding',
|
|
467
|
+
query_value => (AI.EMBED(content => '{search_text}', endpoint => 'text-embedding-005',
|
|
468
|
+
task_type => 'RETRIEVAL_QUERY')).result,
|
|
469
|
+
lexical_search_columns => ['sku'],
|
|
470
|
+
lexical_search_query_value => '{part_code}',
|
|
471
|
+
top_k => 24,
|
|
472
|
+
distance_type => 'COSINE'
|
|
473
|
+
)
|
|
474
|
+
),
|
|
475
|
+
hybrid_nomatch AS (
|
|
476
|
+
SELECT base.id, distance AS nomatch_distance
|
|
477
|
+
FROM VECTOR_SEARCH(
|
|
478
|
+
TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_catalog`,
|
|
479
|
+
'embedding',
|
|
480
|
+
query_value => (AI.EMBED(content => '{search_text}', endpoint => 'text-embedding-005',
|
|
481
|
+
task_type => 'RETRIEVAL_QUERY')).result,
|
|
482
|
+
lexical_search_columns => ['sku'],
|
|
483
|
+
lexical_search_query_value => 'NOSUCHTOKEN',
|
|
484
|
+
top_k => 24,
|
|
485
|
+
distance_type => 'COSINE'
|
|
486
|
+
)
|
|
487
|
+
),
|
|
488
|
+
solved AS (
|
|
489
|
+
SELECT v.sku, v.product, v.rank_vector, h.hybrid_distance,
|
|
490
|
+
CAST(ROUND(1 / ((1 - h.hybrid_distance) - 1 / (60 + v.rank_vector)) - 61) AS INT64) AS rank_lexical,
|
|
491
|
+
CAST(ROUND(1 / ((1 - n.nomatch_distance) - 1 / (60 + v.rank_vector)) - 61) AS INT64) AS rank_lexical_nomatch
|
|
492
|
+
FROM vector_leg v
|
|
493
|
+
JOIN hybrid_exact h USING (id)
|
|
494
|
+
JOIN hybrid_nomatch n USING (id)
|
|
495
|
+
)
|
|
496
|
+
SELECT sku, product, rank_vector, rank_lexical, rank_lexical_nomatch, hybrid_distance,
|
|
497
|
+
1 - (1 / (60 + rank_vector) + 1 / (61 + rank_lexical)) AS rebuilt_distance,
|
|
498
|
+
ABS(hybrid_distance - (1 - (1 / (60 + rank_vector) + 1 / (61 + rank_lexical)))) < 1e-12 AS formula_holds
|
|
499
|
+
FROM solved
|
|
500
|
+
ORDER BY rank_vector
|
|
501
|
+
'''
|
|
502
|
+
client.query(query).to_dataframe()
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### 9. CREATE VECTOR INDEX for hybrid — accelerating the lexical leg
|
|
506
|
+
|
|
507
|
+
Hybrid search needs no index. An index makes the lexical leg fast on large tables, and it is what lets `AI.SEARCH` choose hybrid on its own in `AUTO` mode. It is a speed optimization, not a prerequisite.
|
|
508
|
+
|
|
509
|
+
Two rules the DDL enforces:
|
|
510
|
+
|
|
511
|
+
- Every column in `lexical_search_columns` must also appear in the `STORING` clause. Leave it out and the statement fails with `Lexical search column product must be in the list of stored columns.`
|
|
512
|
+
- A lexical column cannot also be the index key — that fails with `Column product found multiple times in ...`. The key is the embedding column; the lexical columns ride along in `STORING`.
|
|
513
|
+
|
|
514
|
+
`lexical_search_columns` works with either index type — `IVF` or `TREE_AH`.
|
|
515
|
+
|
|
516
|
+
**An autonomous-embedding column cannot be indexed.** A generated `STRUCT<result ARRAY<FLOAT64>, status STRING>` column is rejected as an index key at creation time, and so is an expression key such as `embedding_col.result`. A table that generates its own embeddings therefore needs a second table holding the projected `ARRAY<FLOAT64>` before it can carry a hybrid index. A dry run does not catch this. (verified 2026-09-01)
|
|
517
|
+
|
|
518
|
+
**Nothing is created here.** BigQuery only populates a vector index once the base table reaches 5,000 rows and 10 MB, so this 8-row sample table can never carry one. The cell below validates the DDL with a dry run, which neither creates the index nor bills.
|
|
519
|
+
|
|
520
|
+
```python
|
|
521
|
+
ddl = f'''
|
|
522
|
+
CREATE VECTOR INDEX vector_search_products_hybrid_index
|
|
523
|
+
ON `{PROJECT_ID}.{DATASET_ID}.vector_search_products`(embedding)
|
|
524
|
+
STORING (product, description, category)
|
|
525
|
+
OPTIONS (
|
|
526
|
+
index_type = 'TREE_AH',
|
|
527
|
+
distance_type = 'COSINE',
|
|
528
|
+
lexical_search_columns = ['product', 'description']
|
|
529
|
+
)
|
|
530
|
+
'''
|
|
531
|
+
|
|
532
|
+
# Dry run: validates the statement without creating the index and without billing
|
|
533
|
+
client.query(ddl, job_config=bigquery.QueryJobConfig(dry_run=True))
|
|
534
|
+
print('DDL validated — no index created')
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### 10. Managing an index — coverage, drift, and rebuild
|
|
538
|
+
|
|
539
|
+
Creating an index is not managing one. Three signals describe an index's health, and they move **independently**:
|
|
540
|
+
|
|
541
|
+
| Signal | Where to read it | What it actually means |
|
|
542
|
+
|---|---|---|
|
|
543
|
+
| `coverage_percentage`, `unindexed_row_count` | `INFORMATION_SCHEMA.VECTOR_INDEXES` | how much of the base table the index currently covers |
|
|
544
|
+
| `tfdv_drift` | `VECTOR_INDEX.STATISTICS(TABLE t)` | how far the *vector distribution* has moved since the build |
|
|
545
|
+
| `last_model_build_time` | `INFORMATION_SCHEMA.VECTOR_INDEXES` | when the TreeAH model itself was built — **not** `last_refresh_time` |
|
|
546
|
+
|
|
547
|
+
**`VECTOR_INDEX.STATISTICS` takes exactly one argument, and it must be a relation.** Adding the index name as a second argument fails with `Signature accepts at most 1 argument, found 2 arguments`; passing a string instead of `TABLE t` fails with `argument 1 must be a relation (i.e. table subquery)`. It returns a single column, `tfdv_drift` — `null` until the index's first refresh, `"0.0"` at fresh 100% coverage. On a table with **no** index it returns zero rows rather than an error and scans 0 bytes, which is what the cell below shows against this notebook's sample table.
|
|
548
|
+
|
|
549
|
+
**A negative `tfdv_drift` is a sentinel, not a magnitude.** Two freshly built indexes at 100% coverage reported different things: `"0.0"` from an unpartitioned hybrid index, and `-1.0` from a `PARTITION BY` index with no `lexical_search_columns`. Those two differ in more than one way, so this does not isolate a cause. Read a negative value as *no drift figure available for this index yet* and corroborate with `coverage_percentage` and `last_refresh_time` before acting on it.
|
|
550
|
+
|
|
551
|
+
**Coverage and drift are not the same alarm.** Measured against a live 7,275-row `TREE_AH` hybrid index (the kind `workflows/catalog_search` (`workflows/catalog_search/`) runs at real scale), inserting 11,780 rows whose vectors duplicate ones already indexed — 7,275 → 19,055 rows — moved the three signals in completely different directions:
|
|
552
|
+
|
|
553
|
+
| | before | after |
|
|
554
|
+
|---|---|---|
|
|
555
|
+
| `coverage_percentage` | 100 | **38** |
|
|
556
|
+
| `unindexed_row_count` | 0 | **11,780** |
|
|
557
|
+
| `tfdv_drift` | `0.0` | `1.97e-5` (essentially unchanged) |
|
|
558
|
+
| `last_model_build_time` | 03:14:31 | 03:14:31 (unchanged, across six 90-second polls) |
|
|
559
|
+
|
|
560
|
+
A large volume of *new but distributionally identical* rows is a **coverage** problem, not a drift problem. Coverage recovers on the automatic refresh; drift is the signal that argues for a rebuild.
|
|
561
|
+
|
|
562
|
+
**`ALTER VECTOR INDEX ... REBUILD` requires a BACKGROUND reservation.** With no reservation on the project it is rejected outright:
|
|
563
|
+
|
|
564
|
+
```
|
|
565
|
+
Cannot alter vector index on table catalog_products because there is no BACKGROUND reservation.
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
The check fires *before* validation, so the statement cannot even be dry-run — `--dry_run` returns the same error rather than `Query successfully validated`, even when the named index does not exist. That is why there is no dry-run cell for it here, unlike section 9. Index **maintenance** runs as background work and background work needs slots assigned to it; creating an index, populating it, and querying through it all work with no reservation at all. Running the rebuild needs an Enterprise-edition reservation carrying a `BACKGROUND` job-type assignment — an autoscale reservation with baseline 0 is the cheap shape, billing only while the rebuild runs. The identical gate covers `ALTER SEARCH INDEX` (see `functions/ai_search` (`functions/ai_search/`)).
|
|
569
|
+
|
|
570
|
+
```sql
|
|
571
|
+
-- Requires a BACKGROUND reservation; not run here
|
|
572
|
+
ALTER VECTOR INDEX catalog_products_hybrid_index
|
|
573
|
+
ON `PROJECT_ID.DATASET.catalog_products`
|
|
574
|
+
REBUILD;
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
**`PARTITION BY` must match the base table's partitioning exactly.** The clause does not let you choose a different partitioning for the index. Against a table partitioned by `RANGE_BUCKET(id, GENERATE_ARRAY(0, 30000, 5000))`, repeating that expression validates; naming any other column fails with `Invalid Partition By expression` — as does naming a column on a table that is not partitioned at all.
|
|
578
|
+
|
|
579
|
+
One more field worth knowing: `last_index_alteration_info` stays `null` until an `ALTER` succeeds, and it is a `RECORD`, so `bq query --format=csv` refuses to print it (`Cannot print record field ... in CSV format`). Use `--format=prettyjson`, or read it through the client library as below.
|
|
580
|
+
|
|
581
|
+
```python
|
|
582
|
+
# Both queries below are safe on any table — including one with no index at all.
|
|
583
|
+
|
|
584
|
+
# (a) Coverage and freshness for every vector index in the dataset.
|
|
585
|
+
# last_index_alteration_info is a RECORD and stays NULL until an ALTER succeeds.
|
|
586
|
+
inventory = f'''
|
|
587
|
+
SELECT
|
|
588
|
+
table_name,
|
|
589
|
+
index_name,
|
|
590
|
+
index_status,
|
|
591
|
+
coverage_percentage,
|
|
592
|
+
unindexed_row_count,
|
|
593
|
+
last_refresh_time,
|
|
594
|
+
last_model_build_time,
|
|
595
|
+
disable_reason
|
|
596
|
+
FROM `{PROJECT_ID}.{DATASET_ID}.INFORMATION_SCHEMA.VECTOR_INDEXES`
|
|
597
|
+
'''
|
|
598
|
+
df = client.query(inventory).to_dataframe()
|
|
599
|
+
print(f'vector indexes in {DATASET_ID}: {len(df)}')
|
|
600
|
+
display(df)
|
|
601
|
+
|
|
602
|
+
# (b) Drift. Zero rows here is the expected result — the sample table carries no
|
|
603
|
+
# index, and STATISTICS reports per index, not per table.
|
|
604
|
+
stats = f'''
|
|
605
|
+
SELECT * FROM VECTOR_INDEX.STATISTICS(TABLE `{PROJECT_ID}.{DATASET_ID}.vector_search_products`)
|
|
606
|
+
'''
|
|
607
|
+
drift = client.query(stats).to_dataframe()
|
|
608
|
+
print(f'\nVECTOR_INDEX.STATISTICS rows: {len(drift)} (0 = no index on this table, not an error)')
|
|
609
|
+
display(drift)
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
## Examples — `%%bigquery` Magics
|
|
614
|
+
|
|
615
|
+
The same examples using IPython magic commands. Magics let you write SQL directly in notebook cells without Python string wrapping.
|
|
616
|
+
|
|
617
|
+
Key patterns:
|
|
618
|
+
- `%%bigquery` — run SQL, display results inline
|
|
619
|
+
- `%%bigquery df` — run SQL, capture results into a pandas DataFrame
|
|
620
|
+
|
|
621
|
+
### Single search with `%%bigquery`
|
|
622
|
+
|
|
623
|
+
```sql
|
|
624
|
+
%%bigquery --project {PROJECT_ID}
|
|
625
|
+
|
|
626
|
+
SELECT base.product, base.description, distance
|
|
627
|
+
FROM VECTOR_SEARCH(
|
|
628
|
+
TABLE `statmike-mlops-349915.bq_ai_functions.vector_search_products`,
|
|
629
|
+
'embedding',
|
|
630
|
+
query_value => (AI.EMBED(content => 'productivity tools',
|
|
631
|
+
endpoint => 'text-embedding-005',
|
|
632
|
+
task_type => 'RETRIEVAL_QUERY')).result,
|
|
633
|
+
top_k => 3,
|
|
634
|
+
distance_type => 'COSINE'
|
|
635
|
+
)
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
## Examples — BigFrames
|
|
640
|
+
|
|
641
|
+
BigFrames wraps `VECTOR_SEARCH` via `bbq.vector_search()`. It takes a base table name, column, and a query DataFrame/Series.
|
|
642
|
+
|
|
643
|
+
```python
|
|
644
|
+
import bigframes.pandas as bpd
|
|
645
|
+
import bigframes.bigquery as bbq
|
|
646
|
+
|
|
647
|
+
bpd.options.bigquery.project = PROJECT_ID
|
|
648
|
+
bpd.options.bigquery.location = LOCATION
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Vector search
|
|
652
|
+
|
|
653
|
+
```python
|
|
654
|
+
# Create query embeddings
|
|
655
|
+
query_df = bpd.read_gbq_query("""
|
|
656
|
+
SELECT
|
|
657
|
+
search_term,
|
|
658
|
+
(AI.EMBED(content => search_term, endpoint => 'text-embedding-005',
|
|
659
|
+
task_type => 'RETRIEVAL_QUERY')).result AS embedding
|
|
660
|
+
FROM UNNEST(['audio equipment']) AS search_term
|
|
661
|
+
""")
|
|
662
|
+
|
|
663
|
+
# Search
|
|
664
|
+
result = bbq.vector_search(
|
|
665
|
+
f'{PROJECT_ID}.{DATASET_ID}.vector_search_products',
|
|
666
|
+
'embedding',
|
|
667
|
+
query_df,
|
|
668
|
+
top_k=3,
|
|
669
|
+
distance_type='COSINE'
|
|
670
|
+
)
|
|
671
|
+
result[['search_term', 'product', 'description', 'distance']].to_pandas()
|
|
672
|
+
```
|