Skip to content

Construire l'outil de recherche sémantique

L’outil de recherche sémantique reprend directement la technique du module 9 : encoder du texte libre en vecteurs, puis mesurer une similarité cosinus par simple produit scalaire.

def company_culture_tool(souhait_candidat, top_k=None):
"""Classe TOUTES les entreprises par similarite semantique. top_k=None
(par defaut) retourne le classement complet, pour pouvoir noter
n'importe quelle offre, pas seulement les mieux classees."""
embedder = _get_embedder()
emb = embedder.encode(souhait_candidat, normalize_embeddings=True)
similarites = _embeddings_entreprises @ emb
ordre = np.argsort(-similarites)
if top_k is not None:
ordre = ordre[:top_k]
return [(ids[i], noms[i], float(similarites[i])) for i in ordre]

Un piège réel : ne classer que les meilleures ne suffit pas

Section titled “Un piège réel : ne classer que les meilleures ne suffit pas”

Une première version de cet outil ne renvoyait que les 3 entreprises les plus proches de l’envie exprimée (top_k=3). Problème découvert en testant l’agent complet : certaines offres retenues par l’outil SQL appartenaient à des entreprises hors de ce top 3, et se retrouvaient donc sans aucun score d’affinité (NaN) une fois les deux résultats combinés.

# Ce test documente exactement ce piège, comme garde-fou permanent :
def test_agent_never_leaves_an_offer_without_a_cultural_score(conn):
resultats, _ = job_search_agent(...)
assert resultats["affinite_culture"].notna().all()

La correction : faire de top_k=None (classement complet) le comportement par défaut de l’outil, et ne tronquer qu’au moment de l’affichage si nécessaire — jamais avant d’avoir combiné les résultats des deux outils. Une fonction utilitaire réutilisée par deux composants différents (ici : booster l’affichage d’un côté, noter des offres arbitraires de l’autre) doit rester complète par défaut ; c’est à l’appelant de tronquer s’il en a besoin, pas à l’outil de décider à sa place.

👉 Le cœur de l’agent : relâchement de contraintes


Junior TSAFACK – 12/09/2026