Problem Description
During a rollout of an A/B test that adds dynamic metadata fields (e.g., promotion_code, confidence_score, user_attributes), the Gemini RAG pipeline starts rejecting a large fraction of candidate documents. The symptoms observed in production logs are:
- Retrieval failures with HTTP 400 responses.
- Log entries such as:
2026-07-08T12:34:56.789Z ERROR gemini.retrieval.filter
Metadata schema validation failed: missing required field 'document_id'
Document rejected by Gemini RAG metadata filter: unexpected field 'promotion_code'
Failed to parse RetrievalMetadata: type mismatch for field 'confidence_score' (expected string, got number)
The impact includes a 30 % drop in click‑through rate for the experimental variant and missing analytics for two hours in an internal pipeline.
Root Cause Analysis
The Gemini Retrieval API expects metadata to conform strictly to the RetrievalMetadata schema. The schema defines a fixed set of top‑level properties (document_id, title, mime_type, tags, etc.) and enforces:
- Presence of required fields (e.g.,
document_id). - Exact type matches (strings only for most fields).
- Rejection of any unknown property.
When the A/B test injects custom fields, the filter treats them as “unexpected” and discards the entire document, even though the core metadata is valid. This behavior is documented in the Metadata Filtering guide, which states that the filter performs schema validation before any retrieval logic.
Community reports (e.g., GitHub issue #312 in googleapis/python-vertexai and LangChain issue #8456) confirm that the filter does not support “additionalProperties” or nested objects, leading to the observed rejections.
Investigation and Debugging
Follow these steps to isolate the offending metadata:
- Capture the raw request payload. Enable request logging in the SDK:
import logging
logging.basicConfig(level=logging.DEBUG)
from vertexai.preview import generative_models as gen
retriever = gen.GenerativeModel("gemini-1.5-pro").as_retriever()
retriever.filter = gen.RetrievalMetadataFilter(strict=True)
The debug output will show the JSON sent to the service.
- Validate the payload against the official schema. Use
jsonschemalocally:
pip install jsonschema
cat payload.json | python -c "
import json, sys, jsonschema, urllib.request
schema = urllib.request.urlopen(
'https://vertexai.googleapis.com/v1/projects/.../indexes#retrievalmetadata').read()
schema = json.loads(schema)
payload = json.load(sys.stdin)
jsonschema.validate(instance=payload, schema=schema)
"
The validator will raise errors matching the log messages above.
- Inspect the index definition. Retrieve the index metadata via
gcloud:
gcloud ai indexes describe INDEX_ID \
--region=us-central1 \
--format=json | jq '.metadata'
Confirm that strict_schema_validation is enabled (default).
- Check for nested or non‑string fields. Example problematic document:
{
"document_id": "doc-123",
"title": "Summer Sale",
"promotion_code": "SUMMER23", // unknown field
"confidence_score": 0.92, // number instead of string
"user_attributes": {"age": 30} // nested object not allowed
}
Resolution
There are three practical approaches to make the filter accept the documents while preserving data integrity.
1. Use a whitelist‑based filter with allow_unknown_fields=True
The Python SDK exposes a flag that relaxes strict validation.
# Before (strict validation – default)
retriever.filter = gen.RetrievalMetadataFilter(strict=True)
# After (relaxed validation)
retriever.filter = gen.RetrievalMetadataFilter(
strict=False, # disables unknown‑field rejection
allowed_fields=["promotion_code", "confidence_score", "user_attributes"]
)
By disabling strict mode, the service passes the document through and only validates required fields.
2. Encode custom metadata under a reserved namespace
Gemini reserves the custom_metadata map for extensibility. Move dynamic fields there:
# Before – custom fields at top level
payload = {
"document_id": "doc-123",
"promotion_code": "SUMMER23",
"confidence_score": "0.92"
}
# After – using the approved namespace
payload = {
"document_id": "doc-123",
"custom_metadata": {
"promotion_code": "SUMMER23",
"confidence_score": "0.92"
}
}
This pattern is recommended in the A/B testing best practices and avoids schema violations.
3. Upgrade the index schema to include optional fields
If the custom fields are stable across experiments, create a new index version with an extended schema. The REST definition allows adding optional properties:
PATCH https://vertexai.googleapis.com/v1/projects/PROJECT/locations/REGION/indexes/INDEX_ID
{
"metadata_schema": {
"type": "object",
"properties": {
"document_id": {"type": "string"},
"title": {"type": "string"},
"promotion_code": {"type": "string"},
"confidence_score": {"type": "string"},
"custom_metadata": {"type": "object", "additionalProperties": true}
},
"required": ["document_id"]
}
}
After the schema update, re‑ingest the documents or perform a partial update.
Choosing the right approach
| Scenario | Recommended Fix | Pros | Cons |
|---|---|---|---|
| Short‑lived experiment with ad‑hoc fields | Relaxed filter + namespace | Zero downtime, minimal code change | Potentially less strict data hygiene |
| Stable custom attributes across releases | Schema extension | Strong validation, future‑proof | Requires index rebuild |
| Multiple A/B variants sharing different fields | Namespace + per‑variant allowed_fields list | Granular control per variant | Management overhead |
Verification
After applying the chosen fix, run the following checks:
- Smoke‑test the retrieval endpoint.
curl -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
-d '{
"queries": [{"text": "summer deals"}],
"metadata_filter": {"custom_metadata.promotion_code": "SUMMER23"}
}' \
"https://us-central1-aiplatform.googleapis.com/v1/projects/PROJECT/locations/us-central1/indexes/INDEX_ID:search"
Expected response contains the previously filtered documents.
- Inspect logs for absence of validation errors.
2026-07-08T13:01:12.345Z INFO gemini.retrieval.filter
Document accepted: id=doc-123, metadata fields=5
- Monitor A/B test KPIs. Verify that click‑through rate and analytics pipelines return to baseline within the next 30 minutes.
Prevention and Best Practices
- Namespace custom fields. Always place experiment‑specific metadata under
custom_metadatato avoid schema clashes. - Validate locally before ingestion. Integrate a CI step that runs
jsonschema.validateagainst the RetrievalMetadata definition. - Version index schemas. When adding optional fields that will become permanent, create a new index version rather than relying on relaxed validation.
- Instrument metrics. Emit a custom metric (e.g.,
gemini_rag_metadata_rejections) and set alerts for spikes > 5 %. - Document A/B test contracts. Keep a YAML file that enumerates allowed custom fields per variant and use it as the source of truth for the filter configuration.
Related Topic Hub: LLM Systems Troubleshooting Hub
FAQ
- Why does the filter reject documents only after adding a new field?
Because the default filter enforces the strict RetrievalMetadata schema, any property not defined in that schema is considered unknown and causes a 400 validation error. - Can I use numeric values in custom metadata?
Only if you encode them as strings or place them insidecustom_metadata, which acceptsadditionalPropertiesof any JSON type. - Will disabling
strictaffect security or data quality?
It relaxes unknown‑field checks but still validates required fields. Use it only for controlled experiments and revert to strict mode for production. - How do I migrate existing documents that were rejected?
Re‑ingest them after transforming the payload to the approved namespace or after updating the index schema. A bulk script using the Python SDK can automate the conversion. - Is there a way to see which fields caused a rejection without parsing logs?
Set the environment variableVERTEXAI_LOG_LEVEL=DEBUGor enable themetadata_filter_debugflag in the SDK to have the service return avalidation_errorsarray in the error payload.