Skip to main content

Troubleshooting guide

This guide helps you diagnose and resolve common issues encountered with the YODI API.

🔍 Initial checks​

Basic checks​

Before digging deeper, perform these essential checks:

# 1. Test connectivity
curl -I https://api.yodi.tg/v1/models

# 2. Verify your API key
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://api.yodi.tg/v1/models

# 3. Test a simple request
curl -X POST https://api.yodi.tg/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "yodi-1", "messages": [{"role": "user", "content": "test"}], "max_tokens": 5}'

Required system information​

For effective debugging, collect the following information:

  • SDK version in use
  • Operating system and version
  • Programming language and version
  • Full error message with stack trace
  • Minimal reproducer code
  • Timestamp of the error
  • Request ID if available

🚹 Erreurs d'authentification​

Erreur 401 - Unauthorized​

SymptĂŽmes :

{
"message": "INVALID_API_KEY",
"error": true,
"data": null,
"status": 401
}

Causes possibles :

  1. Clé API invalide ou expirée
# Incorrect
headers = {"Authorization": "your-api-key"}

# Correct
headers = {"Authorization": "Bearer your-api-key"}
  1. Format d'authentification incorrect
// Incorrect
fetch('/api', {
headers: { 'Auth': 'Bearer key' }
});

// Correct
fetch('/api', {
headers: { 'Authorization': 'Bearer key' }
});
  1. Clé API révoquée ou suspendue

Solutions :

  1. Vérifiez votre clé API :
# Test simple de validation
curl -H "Authorization: Bearer YOUR_KEY" \
https://api.yodi.tg/v1/models
  1. Régénérez votre clé API :
  • Connectez-vous Ă  votre dashboard
  • Allez dans "API Keys"
  • CrĂ©ez une nouvelle clĂ©
  • Mettez Ă  jour votre code
  1. Script de validation :
import requests
import os

def validate_api_key(api_key):
headers = {"Authorization": f"Bearer {api_key}"}
response = requests.get(
"https://api.yodi.tg/v1/models",
headers=headers
)

if response.status_code == 200:
print(" Clé API valide")
return True
else:
print(f" Clé API invalide: {response.status_code}")
print(response.text)
return False

# Test
api_key = os.getenv("YODI_API_KEY")
validate_api_key(api_key)

Erreur 403 - Forbidden​

SymptĂŽmes :

{
"message": "INSUFFICIENT_PERMISSIONS",
"error": true,
"data": null,
"status": 403
}

Causes et solutions :

  1. Plan insuffisant pour la fonctionnalité
  • VĂ©rifiez les limites de votre plan
  • Upgradez si nĂ©cessaire
  1. ModĂšle non accessible
  • Certains modĂšles sont rĂ©servĂ©s aux plans Pro+
  • Utilisez un modĂšle compatible avec votre plan

Erreurs de limitation de taux​

Erreur 429 - Too Many Requests​

SymptĂŽmes :

{
"message": "RATE_LIMIT_EXCEEDED",
"error": true,
"data": null,
"status": 429
}

Implémentation d'un retry avec backoff :

import time
import random
import requests
from typing import Callable, Any

class RetryHandler:
def __init__(self, max_retries: int = 3, base_delay: float = 1.0):
self.max_retries = max_retries
self.base_delay = base_delay

def exponential_backoff(self, attempt: int) -> float:
"""Calcule le délai avec backoff exponentiel et jitter"""
delay = self.base_delay * (2 ** attempt)
jitter = random.uniform(0, 0.1) * delay
return delay + jitter

def retry_request(self, func: Callable, *args, **kwargs) -> Any:
"""Exécute une fonction avec retry automatique"""
last_exception = None

for attempt in range(self.max_retries + 1):
try:
response = func(*args, **kwargs)

# SuccĂšs
if response.status_code == 200:
return response

# Rate limit - on retry
elif response.status_code == 429:
if attempt < self.max_retries:
delay = self.exponential_backoff(attempt)
print(f"Rate limited. Retry {attempt + 1}/{self.max_retries} aprĂšs {delay:.2f}s")
time.sleep(delay)
continue
else:
raise Exception(f"Rate limit aprĂšs {self.max_retries} tentatives")

# Autres erreurs - pas de retry
else:
raise Exception(f"HTTP {response.status_code}: {response.text}")

except requests.exceptions.RequestException as e:
last_exception = e
if attempt < self.max_retries:
delay = self.exponential_backoff(attempt)
print(f"Erreur réseau. Retry {attempt + 1}/{self.max_retries} aprÚs {delay:.2f}s")
time.sleep(delay)
else:
raise last_exception

raise last_exception

# Utilisation
retry_handler = RetryHandler(max_retries=3)

def make_api_call():
return requests.post(
"https://api.yodi.tg/v1/chat/completions",
headers={"Authorization": f"Bearer {api_key}"},
json={
"model": "yodi-1",
"messages": [{"role": "user", "content": "Hello"}]
}
)

# Appel avec retry automatique
response = retry_handler.retry_request(make_api_call)

Optimisation des requĂȘtes :

import asyncio
import aiohttp
from asyncio import Semaphore

class RateLimitedClient:
def __init__(self, api_key: str, max_concurrent: int = 5):
self.api_key = api_key
self.semaphore = Semaphore(max_concurrent)
self.session = None

async def __aenter__(self):
self.session = aiohttp.ClientSession(
headers={"Authorization": f"Bearer {self.api_key}"}
)
return self

async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.session.close()

async def make_request(self, prompt: str):
async with self.semaphore: # Limite la concurrence
async with self.session.post(
"https://api.yodi.tg/v1/chat/completions",
json={
"model": "yodi-1",
"messages": [{"role": "user", "content": prompt}]
}
) as response:
if response.status == 429:
# Attendre avant de retry
await asyncio.sleep(1)
return await self.make_request(prompt)

return await response.json()

# Utilisation pour traiter plusieurs requĂȘtes
async def process_batch(prompts):
async with RateLimitedClient(api_key) as client:
tasks = [client.make_request(prompt) for prompt in prompts]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results

🔧 Erreurs de validation​

Erreur 400 - Bad Request​

SymptĂŽmes :

{
"message": "INVALID_FORMAT",
"error": true,
"data": null,
"status": 400
}

Validation du format de requĂȘte :

import json
from typing import Dict, List, Any

class RequestValidator:
@staticmethod
def validate_chat_request(data: Dict[str, Any]) -> tuple[bool, str]:
"""Valide une requĂȘte de chat completion"""

# Champs requis
required_fields = ['model', 'messages']
for field in required_fields:
if field not in data:
return False, f"Champ requis manquant: {field}"

# Validation du modĂšle
valid_models = ['yodi-1', 'yodi-1-32k', 'yodi-instruct', 'yodi-code']
if data['model'] not in valid_models:
return False, f"ModĂšle invalide: {data['model']}"

# Validation des messages
if not isinstance(data['messages'], list) or len(data['messages']) == 0:
return False, "Messages doit ĂȘtre une liste non vide"

for i, message in enumerate(data['messages']):
if not isinstance(message, dict):
return False, f"Message {i} doit ĂȘtre un objet"

if 'role' not in message or 'content' not in message:
return False, f"Message {i} manque 'role' ou 'content'"

if message['role'] not in ['system', 'user', 'assistant']:
return False, f"RĂŽle invalide dans message {i}: {message['role']}"

# Validation des paramĂštres optionnels
if 'temperature' in data:
temp = data['temperature']
if not isinstance(temp, (int, float)) or temp < 0 or temp > 2:
return False, "Temperature doit ĂȘtre entre 0 et 2"

if 'max_tokens' in data:
max_tokens = data['max_tokens']
if not isinstance(max_tokens, int) or max_tokens < 1:
return False, "max_tokens doit ĂȘtre un entier positif"

return True, "RequĂȘte valide"

@staticmethod
def validate_embedding_request(data: Dict[str, Any]) -> tuple[bool, str]:
"""Valide une requĂȘte d'embedding"""

if 'model' not in data:
return False, "Champ 'model' requis"

if data['model'] != 'yodi-embed':
return False, f"ModĂšle invalide pour embeddings: {data['model']}"

if 'input' not in data:
return False, "Champ 'input' requis"

input_data = data['input']
if isinstance(input_data, str):
if len(input_data.strip()) == 0:
return False, "Input ne peut pas ĂȘtre vide"
elif isinstance(input_data, list):
if len(input_data) == 0:
return False, "Input ne peut pas ĂȘtre une liste vide"
for item in input_data:
if not isinstance(item, str) or len(item.strip()) == 0:
return False, "Tous les Ă©lĂ©ments de input doivent ĂȘtre des chaĂźnes non vides"
else:
return False, "Input doit ĂȘtre une chaĂźne ou une liste de chaĂźnes"

return True, "RequĂȘte valide"

# Utilisation
def safe_api_call(endpoint: str, data: Dict[str, Any]):
# Validation selon l'endpoint
if endpoint.endswith('/chat/completions'):
is_valid, message = RequestValidator.validate_chat_request(data)
elif endpoint.endswith('/embeddings'):
is_valid, message = RequestValidator.validate_embedding_request(data)
else:
is_valid, message = True, "Endpoint non validé"

if not is_valid:
print(f" Erreur de validation: {message}")
return None

# Faire l'appel API
response = requests.post(endpoint, json=data, headers=headers)
return response

Validation des tokens :

def estimate_tokens(text: str) -> int:
"""Estimation approximative du nombre de tokens"""
# Rùgle approximative : 1 token ≈ 3-4 caractùres pour le français
return len(text) // 3

def validate_token_limits(messages: List[Dict], model: str, max_tokens: int = None) -> bool:
"""Valide que la requĂȘte ne dĂ©passe pas les limites de tokens"""

model_limits = {
'yodi-1': 4096,
'yodi-1-32k': 32768,
'yodi-instruct': 4096,
'yodi-code': 8192
}

if model not in model_limits:
return False, f"ModĂšle inconnu: {model}"

# Calculer les tokens d'entrée
input_tokens = sum(estimate_tokens(msg['content']) for msg in messages)

# Ajouter les tokens de sortie demandés
output_tokens = max_tokens or 500 # Défaut si non spécifié
total_tokens = input_tokens + output_tokens

limit = model_limits[model]

if total_tokens > limit:
return False, f"Total tokens ({total_tokens}) dépasse la limite ({limit})"

return True, "Tokens OK"

🌐 Erreurs de connectivité​

Timeouts et erreurs rĂ©seau​

SymptĂŽmes courants :

  • ConnectionTimeout
  • ReadTimeout
  • ConnectionError
  • SSL/TLS errors

Configuration robuste des timeouts :

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

class RobustYodiClient:
def __init__(self, api_key: str):
self.api_key = api_key
self.session = self._create_session()

def _create_session(self):
"""Crée une session avec retry et timeout configurés"""
session = requests.Session()

# Configuration des retry
retry_strategy = Retry(
total=3, # Nombre total de retry
status_forcelist=[429, 500, 502, 503, 504], # Codes HTTP Ă  retry
backoff_factor=1, # Délai entre retry (1s, 2s, 4s...)
respect_retry_after_header=True # Respecter les headers Retry-After
)

adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)

# Headers par défaut
session.headers.update({
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
"User-Agent": "YodiClient/1.0"
})

return session

def chat_completion(self, **kwargs):
"""Appel de chat completion avec gestion robuste des erreurs"""
try:
response = self.session.post(
"https://api.yodi.tg/v1/chat/completions",
json=kwargs,
timeout=(30, 60) # (connection_timeout, read_timeout)
)
response.raise_for_status()
return response.json()

except requests.exceptions.Timeout:
print(" Timeout - Le serveur met trop de temps à répondre")
raise
except requests.exceptions.ConnectionError:
print(" Erreur de connexion - Vérifiez votre réseau")
raise
except requests.exceptions.HTTPError as e:
print(f" Erreur HTTP {e.response.status_code}: {e.response.text}")
raise
except Exception as e:
print(f" Erreur inattendue: {e}")
raise

# Test de connectivité
def test_connectivity():
"""Test complet de la connectivité à l'API"""

tests = [
("DNS Resolution", lambda: socket.gethostbyname("api.yodi.tg")),
("HTTPS Connection", lambda: requests.get("https://api.yodi.tg", timeout=10)),
("API Authentication", lambda: requests.get(
"https://api.yodi.tg/v1/models",
headers={"Authorization": f"Bearer {api_key}"},
timeout=10
))
]

for test_name, test_func in tests:
try:
result = test_func()
print(f" {test_name}: OK")
except Exception as e:
print(f" {test_name}: {e}")
return False

return True

Problùmes de proxy et firewall​

Configuration proxy :

import os
import requests

# Configuration automatique du proxy
def get_proxy_config():
"""Détecte et configure automatiquement les proxies"""
proxy_config = {}

# Variables d'environnement standard
http_proxy = os.getenv('HTTP_PROXY') or os.getenv('http_proxy')
https_proxy = os.getenv('HTTPS_PROXY') or os.getenv('https_proxy')

if http_proxy:
proxy_config['http'] = http_proxy
if https_proxy:
proxy_config['https'] = https_proxy

return proxy_config

# Client avec support proxy
class ProxyAwareClient:
def __init__(self, api_key: str):
self.api_key = api_key
self.proxies = get_proxy_config()

def make_request(self, **kwargs):
response = requests.post(
"https://api.yodi.tg/v1/chat/completions",
headers={"Authorization": f"Bearer {self.api_key}"},
json=kwargs,
proxies=self.proxies,
verify=True # Garde la vĂ©rification SSL mĂȘme avec proxy
)
return response.json()

Whitelist pour firewalls d'entreprise :

Demandez à votre équipe IT d'autoriser ces domaines :

  • api.yodi.tg (port 443)
  • *.yodi.tg (port 443)
  • Certificats SSL de Let's Encrypt ou DigiCert

📊 ProblĂšmes de qualitĂ© de rĂ©ponse​

RĂ©ponses incohĂ©rentes​

Diagnostic des prompts :

class PromptDiagnostic:
@staticmethod
def analyze_prompt_quality(prompt: str) -> Dict[str, Any]:
"""Analyse la qualité d'un prompt"""
analysis = {
'length': len(prompt),
'word_count': len(prompt.split()),
'has_context': False,
'has_examples': False,
'has_constraints': False,
'clarity_score': 0,
'suggestions': []
}

# Vérifier la présence de contexte
context_indicators = ['contexte', 'background', 'situation', 'dans le cadre']
analysis['has_context'] = any(indicator in prompt.lower() for indicator in context_indicators)

# Vérifier la présence d'exemples
example_indicators = ['exemple', 'par exemple', 'comme', 'tel que']
analysis['has_examples'] = any(indicator in prompt.lower() for indicator in example_indicators)

# Vérifier les contraintes
constraint_indicators = ['doit', 'ne pas', 'maximum', 'minimum', 'format']
analysis['has_constraints'] = any(indicator in prompt.lower() for indicator in constraint_indicators)

# Score de clarté basique
clarity_score = 0
if analysis['has_context']: clarity_score += 25
if analysis['has_examples']: clarity_score += 25
if analysis['has_constraints']: clarity_score += 25
if 50 <= analysis['word_count'] <= 200: clarity_score += 25

analysis['clarity_score'] = clarity_score

# Suggestions d'amélioration
if not analysis['has_context']:
analysis['suggestions'].append("Ajoutez du contexte pour clarifier la situation")

if not analysis['has_examples']:
analysis['suggestions'].append("Incluez des exemples pour illustrer le format attendu")

if not analysis['has_constraints']:
analysis['suggestions'].append("Spécifiez des contraintes (longueur, format, style)")

if analysis['word_count'] < 20:
analysis['suggestions'].append("Prompt trop court - ajoutez plus de détails")

if analysis['word_count'] > 500:
analysis['suggestions'].append("Prompt trop long - condensez les informations essentielles")

return analysis

# Test d'amélioration de prompt
def improve_prompt_iteratively(original_prompt: str, test_cases: List[str]):
"""Teste et améliore un prompt de maniÚre itérative"""

current_prompt = original_prompt
best_score = 0
best_prompt = original_prompt

for iteration in range(3): # 3 itérations d'amélioration
print(f"\n=== Itération {iteration + 1} ===")

# Analyser le prompt actuel
analysis = PromptDiagnostic.analyze_prompt_quality(current_prompt)
print(f"Score de clarté: {analysis['clarity_score']}/100")
print(f"Suggestions: {analysis['suggestions']}")

# Tester avec des cas d'usage
responses = []
for test_case in test_cases:
response = client.chat.completions.create(
model="yodi-1",
messages=[
{"role": "system", "content": current_prompt},
{"role": "user", "content": test_case}
],
temperature=0.3
)
responses.append(response.choices[0].message.content)

# Évaluer la qualitĂ© des rĂ©ponses
consistency_score = evaluate_response_consistency(responses)
total_score = (analysis['clarity_score'] + consistency_score) / 2

print(f"Score de consistance: {consistency_score}/100")
print(f"Score total: {total_score}/100")

if total_score > best_score:
best_score = total_score
best_prompt = current_prompt

# Améliorer le prompt pour la prochaine itération
current_prompt = apply_prompt_improvements(current_prompt, analysis['suggestions'])

return best_prompt, best_score

def evaluate_response_consistency(responses: List[str]) -> int:
"""Évalue la consistance des rĂ©ponses"""
if len(responses) < 2:
return 100

# Calcul simple basé sur la longueur et la structure
lengths = [len(r) for r in responses]
length_variance = np.var(lengths) / np.mean(lengths) if np.mean(lengths) > 0 else 1

# Score basé sur la variance (moins de variance = plus consistant)
consistency_score = max(0, 100 - (length_variance * 100))
return int(consistency_score)

Optimisation des paramĂštres :

def find_optimal_parameters(prompt: str, test_cases: List[str]):
"""Trouve les paramÚtres optimaux pour un prompt donné"""

parameter_grid = {
'temperature': [0.1, 0.3, 0.5, 0.7, 0.9],
'max_tokens': [100, 200, 300, 500],
'top_p': [0.8, 0.9, 1.0]
}

best_config = {}
best_score = 0
results = []

# Test de toutes les combinaisons
for temp in parameter_grid['temperature']:
for max_tok in parameter_grid['max_tokens']:
for top_p in parameter_grid['top_p']:

config = {
'temperature': temp,
'max_tokens': max_tok,
'top_p': top_p
}

# Tester avec tous les cas d'usage
responses = []
for test_case in test_cases:
response = client.chat.completions.create(
model="yodi-1",
messages=[{"role": "user", "content": f"{prompt}\n\n{test_case}"}],
**config
)
responses.append(response.choices[0].message.content)

# Évaluer la qualitĂ©
score = evaluate_response_quality(responses, test_cases)

results.append({
'config': config,
'score': score,
'responses': responses
})

if score > best_score:
best_score = score
best_config = config

print(f"Config {config}: Score {score:.2f}")

return best_config, best_score, results

def evaluate_response_quality(responses: List[str], test_cases: List[str]) -> float:
"""Évalue la qualitĂ© globale des rĂ©ponses"""
scores = []

for response, test_case in zip(responses, test_cases):
# CritÚres d'évaluation
relevance_score = evaluate_relevance(response, test_case)
completeness_score = evaluate_completeness(response)
clarity_score = evaluate_clarity(response)

total_score = (relevance_score + completeness_score + clarity_score) / 3
scores.append(total_score)

return sum(scores) / len(scores)

def evaluate_relevance(response: str, test_case: str) -> float:
"""Évalue la pertinence de la rĂ©ponse"""
# Analyse simple basée sur les mots-clés communs
response_words = set(response.lower().split())
test_words = set(test_case.lower().split())

common_words = response_words.intersection(test_words)
relevance = len(common_words) / len(test_words) if test_words else 0

return min(100, relevance * 100)

def evaluate_completeness(response: str) -> float:
"""Évalue la complĂ©tude de la rĂ©ponse"""
# CritĂšres simples
min_length = 50
ideal_length = 200

length = len(response)

if length < min_length:
return (length / min_length) * 100
elif length <= ideal_length:
return 100
else:
# Pénalité légÚre pour les réponses trop longues
return max(80, 100 - ((length - ideal_length) / ideal_length) * 20)

def evaluate_clarity(response: str) -> float:
"""Évalue la clartĂ© de la rĂ©ponse"""
# Analyse basée sur la structure
sentences = response.split('.')
avg_sentence_length = sum(len(s.split()) for s in sentences) / len(sentences)

# Score basé sur la longueur moyenne des phrases (8-20 mots = optimal)
if 8 <= avg_sentence_length <= 20:
return 100
elif avg_sentence_length < 8:
return 70 # Phrases trop courtes
else:
return max(50, 100 - (avg_sentence_length - 20) * 2) # Phrases trop longues

🔧 Outils de diagnostic​

Script de diagnostic complet​

#!/bin/bash
# diagnostic-yodi.sh - Script de diagnostic complet pour l'API YODI

echo "🔍 DIAGNOSTIC API YODI"
echo "====================="

# Variables
API_KEY="${YODI_API_KEY}"
BASE_URL="https://api.yodi.tg/v1"

if [ -z "$API_KEY" ]; then
echo " Variable YODI_API_KEY non définie"
echo " Exportez votre clé API: export YODI_API_KEY='your-key'"
exit 1
fi

echo " Clé API détectée: ${API_KEY:0:8}..."

# Test 1: Connectivité réseau
echo -e "\n📡 Test de connectivitĂ© rĂ©seau..."
if ping -c 1 api.yodi.tg > /dev/null 2>&1; then
echo " DNS et connectivité OK"
else
echo " ProblÚme de connectivité réseau"
echo " Vérifiez votre connexion internet et vos paramÚtres de proxy"
fi

# Test 2: Certificat SSL
echo -e "\n🔒 Test du certificat SSL..."
SSL_CHECK=$(echo | openssl s_client -connect api.yodi.tg:443 -servername api.yodi.tg 2>/dev/null | openssl x509 -noout -dates 2>/dev/null)
if [ $? -eq 0 ]; then
echo " Certificat SSL valide"
echo " $SSL_CHECK"
else
echo " ProblĂšme de certificat SSL"
fi

# Test 3: Authentification
echo -e "\n🔑 Test d'authentification..."
AUTH_RESPONSE=$(curl -s -w "%{http_code}" -o /tmp/auth_test.json \
-H "Authorization: Bearer $API_KEY" \
"$BASE_URL/models")

HTTP_CODE="${AUTH_RESPONSE: -3}"
if [ "$HTTP_CODE" = "200" ]; then
echo " Authentification réussie"
MODELS_COUNT=$(jq '.data | length' /tmp/auth_test.json 2>/dev/null || echo "N/A")
echo " ModĂšles disponibles: $MODELS_COUNT"
else
echo " Échec d'authentification (HTTP $HTTP_CODE)"
cat /tmp/auth_test.json 2>/dev/null
fi

# Test 4: Test de requĂȘte basique
echo -e "\n💬 Test de requĂȘte chat..."
CHAT_RESPONSE=$(curl -s -w "%{http_code}" -o /tmp/chat_test.json \
-X POST "$BASE_URL/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "yodi-1",
"messages": [{"role": "user", "content": "Test de diagnostic"}],
"max_tokens": 10
}')

HTTP_CODE="${CHAT_RESPONSE: -3}"
if [ "$HTTP_CODE" = "200" ]; then
echo " RequĂȘte chat rĂ©ussie"
RESPONSE_TEXT=$(jq -r '.choices[0].message.content' /tmp/chat_test.json 2>/dev/null)
echo " Réponse: $RESPONSE_TEXT"
else
echo " Échec de requĂȘte chat (HTTP $HTTP_CODE)"
cat /tmp/chat_test.json 2>/dev/null
fi

# Test 5: Test d'embeddings
echo -e "\n🔍 Test d'embeddings..."
EMBED_RESPONSE=$(curl -s -w "%{http_code}" -o /tmp/embed_test.json \
-X POST "$BASE_URL/embeddings" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "yodi-embed",
"input": "Test embedding"
}')

HTTP_CODE="${EMBED_RESPONSE: -3}"
if [ "$HTTP_CODE" = "200" ]; then
echo " RequĂȘte embedding rĂ©ussie"
EMBED_DIM=$(jq '.data[0].embedding | length' /tmp/embed_test.json 2>/dev/null)
echo " Dimension: $EMBED_DIM"
else
echo " Échec de requĂȘte embedding (HTTP $HTTP_CODE)"
cat /tmp/embed_test.json 2>/dev/null
fi

# Test 6: Performance et latence
echo -e "\n Test de performance..."
START_TIME=$(date +%s.%N)
PERF_RESPONSE=$(curl -s -w "%{http_code}" -o /dev/null \
-X POST "$BASE_URL/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "yodi-1",
"messages": [{"role": "user", "content": "Performance test"}],
"max_tokens": 5
}')
END_TIME=$(date +%s.%N)

LATENCY=$(echo "$END_TIME - $START_TIME" | bc)
HTTP_CODE="${PERF_RESPONSE: -3}"

if [ "$HTTP_CODE" = "200" ]; then
echo " Test de performance réussi"
echo " Latence: ${LATENCY}s"

if (( $(echo "$LATENCY < 2.0" | bc -l) )); then
echo " 🚀 Performance excellente"
elif (( $(echo "$LATENCY < 5.0" | bc -l) )); then
echo " Performance acceptable"
else
echo " 🐌 Performance lente"
fi
else
echo " Échec du test de performance"
fi

# Nettoyage
rm -f /tmp/auth_test.json /tmp/chat_test.json /tmp/embed_test.json

echo -e "\n🎯 RÉSUMÉ DU DIAGNOSTIC"
echo "======================="
echo "Si tous les tests sont , votre configuration est correcte."
echo "Si certains tests échouent , consultez le guide de dépannage."
echo ""
echo "Pour un support technique: support@yodi.tg"

Dashboard de monitoring​

import time
import json
import threading
from datetime import datetime, timedelta
from typing import Dict, List
import matplotlib.pyplot as plt
import pandas as pd

class YodiMonitor:
def __init__(self, api_key: str):
self.api_key = api_key
self.metrics = {
'requests': [],
'latencies': [],
'errors': [],
'tokens_used': []
}
self.monitoring = False

def start_monitoring(self, interval: int = 60):
"""Démarre le monitoring en arriÚre-plan"""
self.monitoring = True

def monitor_loop():
while self.monitoring:
self._collect_metrics()
time.sleep(interval)

monitor_thread = threading.Thread(target=monitor_loop)
monitor_thread.daemon = True
monitor_thread.start()

print(f" Monitoring démarré (intervalle: {interval}s)")

def stop_monitoring(self):
"""ArrĂȘte le monitoring"""
self.monitoring = False
print("🛑 Monitoring arrĂȘtĂ©")

def _collect_metrics(self):
"""Collecte les métriques de performance"""
timestamp = datetime.now()

try:
# Test de latence
start_time = time.time()
response = requests.post(
"https://api.yodi.tg/v1/chat/completions",
headers={"Authorization": f"Bearer {self.api_key}"},
json={
"model": "yodi-1",
"messages": [{"role": "user", "content": "ping"}],
"max_tokens": 1
},
timeout=30
)
latency = (time.time() - start_time) * 1000 # en ms

# Enregistrer les métriques
self.metrics['requests'].append({
'timestamp': timestamp,
'status_code': response.status_code,
'success': response.status_code == 200
})

self.metrics['latencies'].append({
'timestamp': timestamp,
'latency_ms': latency
})

if response.status_code != 200:
self.metrics['errors'].append({
'timestamp': timestamp,
'error_code': response.status_code,
'error_message': response.text
})

except Exception as e:
self.metrics['errors'].append({
'timestamp': timestamp,
'error_code': 'NETWORK_ERROR',
'error_message': str(e)
})

def generate_report(self, hours_back: int = 24) -> Dict:
"""GénÚre un rapport de performance"""
cutoff_time = datetime.now() - timedelta(hours=hours_back)

# Filtrer les données récentes
recent_requests = [
r for r in self.metrics['requests']
if r['timestamp'] > cutoff_time
]

recent_latencies = [
l for l in self.metrics['latencies']
if l['timestamp'] > cutoff_time
]

recent_errors = [
e for e in self.metrics['errors']
if e['timestamp'] > cutoff_time
]

# Calculer les statistiques
total_requests = len(recent_requests)
successful_requests = sum(1 for r in recent_requests if r['success'])
success_rate = (successful_requests / total_requests * 100) if total_requests > 0 else 0

latencies = [l['latency_ms'] for l in recent_latencies]
avg_latency = sum(latencies) / len(latencies) if latencies else 0
p95_latency = np.percentile(latencies, 95) if latencies else 0

# Analyse des erreurs
error_distribution = {}
for error in recent_errors:
code = error['error_code']
error_distribution[code] = error_distribution.get(code, 0) + 1

return {
'period': f"DerniĂšres {hours_back} heures",
'total_requests': total_requests,
'success_rate': f"{success_rate:.2f}%",
'avg_latency_ms': f"{avg_latency:.2f}",
'p95_latency_ms': f"{p95_latency:.2f}",
'error_count': len(recent_errors),
'error_distribution': error_distribution,
'recommendations': self._generate_recommendations(
success_rate, avg_latency, recent_errors
)
}

def _generate_recommendations(self, success_rate: float, avg_latency: float,
errors: List[Dict]) -> List[str]:
"""GénÚre des recommandations basées sur les métriques"""
recommendations = []

if success_rate < 95:
recommendations.append("Taux de succÚs faible - Vérifiez votre implémentation des retry")

if avg_latency > 2000:
recommendations.append("Latence élevée - Optimisez vos prompts et réduisez max_tokens")

error_codes = [e['error_code'] for e in errors]
if error_codes.count('429') > len(errors) * 0.1:
recommendations.append("Beaucoup d'erreurs 429 - Implémentez un rate limiting")

if error_codes.count('500') > 0:
recommendations.append("Erreurs serveur détectées - Contactez le support si persistant")

if not recommendations:
recommendations.append("Performance normale - Continuez le monitoring")

return recommendations

def plot_metrics(self, hours_back: int = 24):
"""GénÚre des graphiques de performance"""
cutoff_time = datetime.now() - timedelta(hours=hours_back)

# Préparer les données
latency_data = [
(l['timestamp'], l['latency_ms'])
for l in self.metrics['latencies']
if l['timestamp'] > cutoff_time
]

if not latency_data:
print("Pas assez de données pour générer des graphiques")
return

# Créer les graphiques
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))

# Graphique de latence
timestamps, latencies = zip(*latency_data)
ax1.plot(timestamps, latencies, 'b-', alpha=0.7)
ax1.set_title('Latence de l\'API YODI')
ax1.set_ylabel('Latence (ms)')
ax1.grid(True)

# Graphique de taux de succĂšs (par heure)
success_by_hour = {}
for request in self.metrics['requests']:
if request['timestamp'] > cutoff_time:
hour = request['timestamp'].replace(minute=0, second=0, microsecond=0)
if hour not in success_by_hour:
success_by_hour[hour] = {'total': 0, 'success': 0}
success_by_hour[hour]['total'] += 1
if request['success']:
success_by_hour[hour]['success'] += 1

hours = sorted(success_by_hour.keys())
success_rates = [
(success_by_hour[h]['success'] / success_by_hour[h]['total'] * 100)
for h in hours
]

ax2.bar(hours, success_rates, alpha=0.7, color='green')
ax2.set_title('Taux de succĂšs par heure')
ax2.set_ylabel('Taux de succĂšs (%)')
ax2.set_ylim(0, 100)

plt.tight_layout()
plt.show()

# Utilisation du monitoring
def setup_monitoring():
monitor = YodiMonitor(api_key)

# Démarrer le monitoring
monitor.start_monitoring(interval=30) # Toutes les 30 secondes

# Laisser tourner pendant quelques minutes
time.sleep(300) # 5 minutes

# Générer un rapport
report = monitor.generate_report(hours_back=1)
print("\n📊 RAPPORT DE PERFORMANCE")
print("========================")
for key, value in report.items():
if key != 'recommendations':
print(f"{key}: {value}")

print("\n💡 RECOMMANDATIONS:")
for rec in report['recommendations']:
print(f"- {rec}")

# ArrĂȘter le monitoring
monitor.stop_monitoring()

# setup_monitoring()

Pour plus d'aide :

Ce guide est mis à jour réguliÚrement. N'hésitez pas à nous faire part de vos retours pour l'améliorer.