Assistant de support RAG à double moteur sur Google Cloud : documents publics, manuels privés
Créer un assistant de support qui interroge à la fois Vertex AI Search et l’API Developer Knowledge, puis permet à Gemini de concilier les conseils avec les règles propres du client.
Tout ceux qui travaillent dans une file d’attente de support conformément à un SLA connaissent le coût lié au changement de fenêtres : une pour la documentation publique de Google Cloud, une autre pour le manuel d’opérations du client, ainsi qu’une synthèse mentale des deux. Demander à un assistant IA générique de simplifier ce processus donne souvent une réponse qui semble correcte sur le papier mais qui est inadaptée au client qui se trouve en face de vous.
La solution décrite ici repose sur une architecture à deux moteurs d’extraction de données. Une demande est envoyée en parallèle vers deux sources : la documentation publique de Google Cloud et un stock privé des manuels d’opérations du client. Un modèle Gemini réconcilie ensuite les informations obtenues pour produire un guide de résolution respectant les règles du client. Imaginez trouver une recette bien notée en ligne et faire modifier ses ingrédients par un nutritionniste en fonction de votre régime alimentaire avant de cuisiner.
Cette solution comprend quatre composants :
- Deux voies de recherche fonctionnant en parallèle : l’une pour les conseils techniques généraux et l’autre pour les politiques internes strictes.
- Vertex AI Search en tant que stockage privé, indexant les règles et contraintes métier directement à partir des fichiers de procédures.
- L’API Developer Knowledge en tant que source publique, fournissant des recommandations fondées et des étapes de dépannage tirées de la documentation officielle.
- Gemini en tant que couche de raisonnement, comparant les solutions publiques aux règles privées afin que le résultat soit un guide que l’ingénieur support puisse suivre en toute sécurité.
Pourquoi les conseils généraux échouent
Prenons un ticket comme celui-ci :
Acme signale un délai d’environ cinq secondes lors du démarrage de leur interface Cloud Run. Que devrions-nous faire ?
Un assistant se basant uniquement sur les documents publics dira presque certainement de mettre min-instances à 1. C’est la solution classique pour les démarrages lents, mais elle pose également problème à ce client pour deux raisons :
- Un plafond budgétaire. Acme limite le service frontend à au plus une seule instance, ce qui rend impossible l’utilisation d’une instance toujours active.
- Une règle interne. Le guide de fonctionnement d’Acme interdit de modifier
min-instancessans l’accord écrit de son architecte en chef, Sarah Connor.
Il faut donc un système capable de récupérer les directives publiques concernant la latence de démarrage de Cloud Run, de vérifier chaque suggestion par rapport aux contraintes privées d’Acme, et de signaler ensuite au développeur tout conflit en proposant une alternative conforme.
Prérequis
Ayez ces éléments prêts avant de commencer :
- Un projet Google Cloud avec la facturation activée.
- L’outil CLI gcloud, installé et authentifié sur votre machine.
- Python 3.10 ou une version plus récente.
Costs et quotas
Cet tutoriel vise à rester dans les forfaits gratuits et les quotas de développement prévus pour chaque service :
- Cloud Storage. Un petit fichier de guide reste bien dans la limite de stockage toujours gratuite. Des frais s’appliquent uniquement si vous dépassez cette limite ou générez des transferts de données vers l’extérieur.
- Vertex AI Search (Gemini Agent Search). Les nouveaux comptes bénéficient d’un forfait d’essai gratuit. Une fois les crédits d’essai épuisés, les requêtes sont facturées au forfait selon le nombre de requêtes effectuées (1 000 requêtes par période) ; veuillez donc consulter la page des tarifs en vigueur.
- Developer Knowledge API. Son activation et son utilisation sont gratuites dans les limites standard prévues pour les développeurs. Les allocations demandées peuvent être suivies sur les pages de quotas de Google Cloud.
Préparation de l’environnement Google Cloud
Avant même l’assistant lui-même, il faut mettre en place l’infrastructure sous-jacente. Un script bash gère trois tâches :
- Activation des API. Il active Cloud Storage, Vertex AI Agent Builder (
discoveryengine), Developer Knowledge ainsi que l’API de langage génératif, afin que l’orchestrateur puisse accéder à tous les points de terminaison nécessaires. - Création de l’identité du service Discovery Engine. Il crée à l’avance l’agent de service géré par Google (
service-PROJECT_NUMBER@gcp-sa-discoveryengine.iam.gserviceaccount.com), afin que l’indexeur en arrière-plan puisse recevoir immédiatement des permissions IAM, sans avoir besoin de cliquer dans la console pour la première fois. - Mise en place du stockage et de l’accès. Il crée le bucket Cloud Storage qui conservera le manuel d’utilisation du client et accorde
roles/storage.objectViewerà deux entités : l’agent de recherche en arrière-plan, qui en a besoin pour indexer, et votre propre compte, vous permettant ainsi d’examiner le bucket dans la console.
Copiez le script dans un terminal :
# 1. Initialize variables from active gcloud config
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
export BUCKET_NAME="${PROJECT_ID}-internal-kb"
export USER_EMAIL=$(gcloud config get-value account)
# 2. Enable all required GCP APIs
gcloud services enable \
storage-api.googleapis.com \
discoveryengine.googleapis.com \
developerknowledge.googleapis.com \
generativelanguage.googleapis.com \
--project=${PROJECT_ID}
# 3. Provision the Discovery Engine Service Identity programmatically
# (This creates the background service account without requiring a manual Console click)
gcloud beta services identity create \
--service=discoveryengine.googleapis.com \
--project=${PROJECT_ID}
# 4. Create the GCS bucket
gcloud storage buckets create gs://${BUCKET_NAME} --project=${PROJECT_ID} --location=us-central1
# 5. Grant Storage Object Viewer to the Agent Search Service Agent (for background indexing)
gcloud storage buckets add-iam-policy-binding gs://${BUCKET_NAME} \
--member="serviceAccount:@gcp-sa-discoveryengine.iam.gserviceaccount.com">service-${PROJECT_NUMBER}@gcp-sa-discoveryengine.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
# 6. Grant Storage Object Viewer to your active Console user (for frontend validation)
gcloud storage buckets add-iam-policy-binding gs://${BUCKET_NAME} \
--member="user:${USER_EMAIL}" \
--role="roles/storage.objectViewer"
echo "Setup complete! All APIs enabled and permissions configured successfully."
Les valeurs que vous pourriez vouloir ajuster :
PROJECT_IDest l’ID de votre projet. Lorsque le projet est déjà défini dans gcloud,gcloud config get-value projectle renvoie automatiquement.PROJECT_NUMBERest le numéro unique du projet.BUCKET_NAMEdonne un nom au bucket destiné aux runbooks et vaut par défaut${PROJECT_ID}-internal-kb.USER_EMAILest l’adresse du compte Google Cloud actif qui obtient un accès en tant qu’utilisateur dans la console.
Création du moteur de connaissances privé
La partie privée est un petit moteur de recherche contenant un exemple de runbook client.
Rédaction et téléchargement du runbook
Créez le fichier du runbook localement, puis copiez-le dans le bucket :
# Create the client runbook file with .txt extension
cat << 'EOF' > client-acme-runbook.txt
# Client Acme Corporation - Cloud Run Deployment Runbook
* **Client ID:** ACME-404
* **SLA Tier:** Platinum (4-hour resolution target)
* **Infrastructure Specifications:**
* Frontend Service Name: `acme-frontend-prod`
* Regional Deployment: `us-central1`
* Max Instances: `1` (Hard cap imposed due to strict budget constraints)
* CPU Allocation: `CPU allocated only during request processing`
* Backend Database: Cloud SQL PostgreSQL (`acme-db-prod`)
* Networking: Serverless VPC (Virtual Private Cloud) Access connector (`acme-vpc-connector`)
* **Deployment Constraints:**
* Deployed strictly via GitHub Actions workflow (`.github/workflows/deploy.yml`). Manual console updates are forbidden.
* Direct access to the production DB requires using the Bastion Host `acme-bastion-prod`.
* **Escalation Path:**
* Primary Contact: Sarah Connor (Lead Architect, sarah@acme-corp.com)
* Secondary Contact: PagerDuty escalation policy `acme-prod-critical`
* **Common Issues & Custom Rules:**
* **High Cold Starts:** High cold-start latency due to CPU throttling during scale-up. Do not modify the `min-instances` count without approval from Sarah Connor, as it breaches billing thresholds.
* **Database Connection Timeouts:** If timeouts occur, verify that the Serverless VPC Access connector is in a `READY` state before attempting database restarts.
EOF
# Upload the file to your bucket
gcloud storage cp client-acme-runbook.txt gs://${BUCKET_NAME}/
La commande de téléversement utilise le nom du bucket défini lors de l’étape d’installation, ${PROJECT_ID}-internal-kb, à moins que vous ne l’ayez modifié. Le guide de déploiement contient délibérément toutes les informations essentielles : la limite maximale d’une seule instance imposée par Acme, sa règle d’approbation concernant le nombre minimal d’instances, les noms de ses connexions de base de données et de VPC, sa politique exigeant un déploiement uniquement via GitHub Actions, ainsi que les contacts pour l’escalade.
Création d’un stockage de données pour la recherche des agents
C’est le stockage de données qui permet de rechercher dans le guide de déploiement. Dans la console Google Cloud :
- Ouvrez Agent Builder (également indiqué sous le nom de Vertex AI Search).
- Dans la navigation de gauche, sélectionnez Data Stores, puis Create data store.
- En tant que source, choisissez Cloud Storage.
gs://PROJECT_ID-internal-kb/*, en remplaçant PROJECT_ID par votre véritable ID de projet.acme-internal-kb.Création d’une application de recherche pour le service
Les requêtes provenant du code passent par une application de recherche (un moteur), qui agit comme couche de service au-dessus du stockage de données :
- Dans Agent Builder, ouvrez Applications.
- Cliquez sur Créer une application.
- Sous Recherche sur la page de création, cliquez sur Créer.
- Nommez l’application
acme-search-app. - Pour le nom de l’organisation externe, saisissez
Acme Corporation. - Laissez la localisation à la valeur par défaut, global, et continuez.
- À l’étape Data Stores, cochez
acme-internal-kb. - Cliquez sur Create.
- Lorsque c’est terminé, ouvrez l’application depuis la liste Apps et copiez son Engine ID (également appelé App ID) sur la page des détails. Il ressemble à
acme-search-app_1234567890.
Connexion du côté public : l’API Developer Knowledge
Cette API offre un accès direct à la documentation officielle de Google Cloud ainsi qu’aux guides pour développeurs. Les étapes suivantes permettent de créer une clé API, de la restreindre et de confirmer l’accès avec une seule requête curl.
Création d’une clé API
- Dans la console, allez dans Credentials.
- Sélectionnez Créer des identifiants, puis Clé API.
- Il est préférable de limiter l’accès à cette clé uniquement à la API Developer Knowledge.
- Stockez la clé dans une variable d’environnement :
export DEVELOPERKNOWLEDGE_API_KEY="DEVELOPERKNOWLEDGE_API_KEY
Remplacez le texte de remplacement par votre propre clé générée.
Test avec curl
Une requête rapide confirme que la clé fonctionne et que vous pouvez accéder aux documents de développement de Google :
curl -X POST "https://developerknowledge.googleapis.com/v1:answerQuery?key=$DEVELOPERKNOWLEDGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "How do I reduce Cloud Run cold starts?"}'
Une réponse JSON structurée contenant une réponse tirée de la documentation officielle indique que tout fonctionne correctement.
Le script d’orchestration
Ensuite vient un script Python qui pose la même question aux deux moteurs et transmet les résultats à Gemini pour obtenir la réponse finale.
Installation des bibliothèques
pip install google-cloud-discoveryengine google-generativeai requests
Création de l’orchestrateur
La commande suivante écrit app.py. Le script envoie le texte du ticket reçu vers les deux chemins de recherche, insère les deux résultats dans un modèle de prompt et transmet le prompt rempli à Gemini :
cat << 'EOF' > app.py
import os
import sys
import requests
import google.generativeai as genai
from google.cloud import discoveryengine_v1beta as discoveryengine
# Configuration - Replace with your details
PROJECT_ID = os.environ.get("PROJECT_ID")
ENGINE_ID = os.environ.get("ENGINE_ID")
LOCATION = "global"
MODEL_NAME = os.environ.get("GEMINI_MODEL", "gemini-3.5-flash")
# Initialize Gemini Client (Uses GEMINI_API_KEY env variable)
genai.configure(api_key=os.environ.get("GEMINI_API_KEY"))
model = genai.GenerativeModel(MODEL_NAME)
# 1. Helper to query the Private KB (Agent Search)
def query_private_kb(query_text: str):
client = discoveryengine.SearchServiceClient()
serving_config = f"projects/{PROJECT_ID}/locations/{LOCATION}/collections/default_collection/engines/{ENGINE_ID}/servingConfigs/default_search"
request = discoveryengine.SearchRequest(
serving_config=serving_config,
query=query_text,
page_size=3,
)
response = client.search(request)
results = []
for result in response.results:
doc_data = result.document.derived_struct_data
if "snippets" in doc_data:
for s in doc_data["snippets"]:
results.append(s.get("snippet", ""))
return "\n".join(results)
# 2. Helper to query the Public KB (Developer Knowledge API)
def query_public_gcp_kb(query_text: str):
api_key = os.environ.get("DEVELOPERKNOWLEDGE_API_KEY")
url = f"https://developerknowledge.googleapis.com/v1:answerQuery?key={api_key}"
headers = {"Content-Type": "application/json"}
payload = {"query": query_text}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
data = response.json()
return data.get("answer", {}).get("answerText", "No answer found.")
except Exception as e:
return f"Error querying Developer Knowledge API: {e}"
# 3. Main Orchestration
def run_support_assistant(ticket_query: str):
print(f"\n[Ticket Query]: {ticket_query}")
# Dynamically search private client specs using the ticket query
print("Searching private database…")
private_context = query_private_kb(ticket_query)
# Dynamically search public GCP advice using the ticket query
print("Searching public Google Cloud docs…")
public_context = query_public_gcp_kb(ticket_query)
# Format system prompt template
prompt = f"""
You are a Senior Cloud Support Assistant. Your task is to resolve the support ticket.
CRITICAL INSTRUCTIONS:
1. You MUST read the [Private Runbook Context] for specific client configurations, limits, and rules.
2. You MUST cross-reference all generic GCP recommendations against the client's private constraints. If a generic recommendation (e.g., increasing min-instances) violates a client limit or rule (e.g., budget caps or requiring specific manager approvals), you MUST call this out explicitly and provide the compliant fallback option.
3. Refer to specific client contacts (e.g., Sarah Connor), services, databases, and network connectors mentioned in the runbook when explaining the steps.
[Private Runbook Context]:
{private_context}
[Official GCP Guidance]:
{public_context}
[Support Ticket Query]:
{ticket_query}
Provide a precise troubleshooting guide for the support engineer.
"""
response = model.generate_content(prompt)
print("\n[AI Assistant Resolution Guide]:")
print(response.text)
if __name__ == "__main__":
if not all([PROJECT_ID, ENGINE_ID, os.environ.get("DEVELOPERKNOWLEDGE_API_KEY"), os.environ.get("GEMINI_API_KEY")]):
print("Please set your environment variables: PROJECT_ID, ENGINE_ID, DEVELOPERKNOWLEDGE_API_KEY, and GEMINI_API_KEY.")
else:
# Allow passing the ticket query as a command line argument
default_query = "Client Acme is complaining about 5-second cold start latency on their frontend service. How can we fix it?"
query = sys.argv[1] if len(sys.argv) > 1 else default_query
run_support_assistant(ticket_query=query)
EOF
C’est l’instruction qui effectue le travail réel. Elle demande au modèle de consulter le manuel interne contenant les paramètres et règles spécifiques au client, puis de comparer chaque recommandation générale à ces contraintes. Si une recommandation publique, comme augmenter le nombre minimal d’instances, enfreint une limite ou une règle d’approbation du client, le modèle doit l’indiquer clairement et proposer une solution alternative conforme. Il doit également mentionner les contacts réels du client, ses services, ses bases de données et ses connexions réseau lorsqu’il explique les étapes.
Exécution du test
Definissez les variables d’environnement et lancez le script :
export PROJECT_ID="PROJECT_ID"
export ENGINE_ID="ENGINE_ID"
export DEVELOPERKNOWLEDGE_API_KEY="DEVELOPERKNOWLEDGE_API_KEY"
export GEMINI_API_KEY="GEMINI_API_KEY"
# Run with the default query
python3 app.py
# Or pass a custom query as a command line argument
python3 app.py "Client Acme is complaining about database connection timeouts on their Cloud SQL backend."
Remplacez par des valeurs réelles :
PROJECT_IDest l’ID de votre projet.ENGINE_IDest l’ID du moteur/application de l’application Vertex AI Search.DEVELOPERKNOWLEDGE_API_KEYest la clé que vous avez générée pour l’API Developer Knowledge.GEMINI_API_KEYest une clé provenant de Google AI Studio. La documentation de Google explique comment obtenir une clé Gemini API.
À quoi ressemble la sortie
Vous devriez obtenir quelque chose de ce genre :
[AI Assistant Resolution Guide]:
### **Acme Support Ticket: Resolution & Troubleshooting Guide**
**Ticket ID:** #ACM-98431
**Service:** `acme-frontend` (Cloud Run)
**Issue:** 5-second cold start latency
**Assigned To:** Cloud Support Engineering Team
- -
### **CRITICAL COMPLIANCE WARNING (Read Before Acting)**
According to Acme's operational rules:
* **The Conflict:** Standard GCP documentation suggests setting `min_instances` (e.g., to 1 or more) to eliminate cold starts. However, **Acme operates under a strict budget cap policy that prohibits idle/always-on instances** for the frontend service without explicit business justification.
* **The Rule:** You **MUST NOT** increase `min_instances` on `acme-frontend` without prior written approval from **Sarah Connor** (Acme's Technical Lead & Budget Approver).
* **Compliant Fallback:** If Sarah Connor does not approve the budget increase for `min_instances`, you must resolve the latency strictly through **code-level optimizations, CPU adjustments, startup probes, and connection tuning** as detailed below.
- -
### **Step-by-Step Troubleshooting & Resolution Guide**
#### **Phase 1: Triage and Governance Check**
1. **Contact Sarah Connor:** Reach out to Sarah Connor to request a budget exception for setting `min_instances = 1`.
2. **Fallback implementation:** If denied, proceed strictly with the budget-compliant fallback steps below to optimize the cold start duration within the 0-instance scaling limit.
#### **Phase 2: Network & Connection Optimization**
1. **Inspect VPC Connector Latency:** `acme-frontend` routes traffic to the database via the **`acme-vpc-connector`** (Serverless VPC Access). Ensure that the connector throughput is not bottlenecked.
2. **Database Connection Lazy-Loading:** The frontend connects to **`acme-production-db`** (Cloud SQL). Do not open the connection pool until the first actual handler request arrives to minimize container boot time.
…
Ce que montre le résultat
La réponse combine les deux sources, chacune apportant quelque chose de différent :
- La documentation publique, via l’API Developer Knowledge, fournit les correctifs techniques : activer
cpu-boost, définir la concurrence à80, ou ajouter une vérification de démarrage/healthz. - La base de connaissances privée, via Agent Search, ajoute de la logique métier, des contraintes de sécurité et des détails opérationnels. Elle indique que le conseil standard (
min_instances = 1) contrevient aux règles budgétaires d’Acme. Elle mentionne Sarah Connor comme la personne qui doit approuver toute exception. Elle fait référence à la base de données réelle d’Acme ainsi qu’à son connecteur VPC.
Sans ces deux moteurs, un modèle générique se contenterait de recommander d’augmenter la valeur de min_instances. Un ingénieur support junior pourrait agir immédiatement, ce qui, dans une entreprise, pourrait dépasser les limites budgétaires, déclencher des alertes de facturation et faire l’économie de l’approbation architecturale. En ancrant le modèle dans un contexte à la fois public et privé, on obtient des solutions techniquement solides tout en respectant les politiques de l’organisation.
Rendre le modèle plus robuste pour la production
Quelques modifications seraient nécessaires avant qu’il ne puisse traiter des tickets réels :
- Gérer les secrets dans un gestionnaire de secrets. Stockez les clés API dans le Secret Manager et chargez-les en temps de exécution, plutôt que dans des variables d’environnement au format texte brut.
roles/discoveryengine.viewer et roles/storage.objectViewer, et restreignez les clés API aux APIs spécifiques ainsi qu’aux adresses réseau fiables.SearchRequest pour limiter les résultats au seul environnement d’un client.Nettoyage
Afin d’éviter des frais continus, supprimez ce que le tutoriel a créé.
Recherche dans l’application et le stockage de données
Un stockage de données ne peut pas être supprimé tant qu’une application y est associée, il faut donc d’abord supprimer l’application. Dans la console :
- Allez dans Agent Builder, puis dans Apps.
- Localisez
acme-search-app, ouvrez More actions (trois points) et sélectionnez Delete. - Saisissez le nom de l’application dans la boîte de dialogue de confirmation et confirmez.
- Ouvrez Data Stores dans le menu de navigation.
- Localisez
acme-internal-kb, ouvrez More actions, puis sélectionnez Delete. - Confirmez.
Le bucket de stockage
Supprimer le contenu du bucket se fait avec une seule commande :
export BUCKET_NAME="${PROJECT_ID}-internal-kb"
gcloud storage rm - recursive gs://${BUCKET_NAME}/
Clés API
- Allez dans APIs & Services, puis dans Credentials.
- Sous API Keys, trouvez la clé créée pour l’API Developer Knowledge.
- Cliquez sur l’icône poubelle à côté et confirmez.
Fichiers locaux
Supprimez les scripts ainsi que le guide d’exécution de exemple de votre ordinateur :
rm -f app.py app_test.py client-acme-runbook.txt