Rechercher des contacts
search_personssearch_persons retrouve des contacts : plein texte déterministe sur tous les champs descriptifs de la fiche — nom, notes, email, téléphone, adresse — combiné à des filtres sur le graphe relationnel : restriction par kind, et par le rôle porté par un contact lié (with_role / without_role). Il ne crée jamais rien.
Comment le matching fonctionne
La requête s'exécute en recherche plein texte PostgreSQL sur les champs descriptifs indexés du contact. Le matching est insensible aux accents (« francois » trouve « François »), par mots entiers avec racinisation, pas par sous-chaîne. Les filtres de rôle s'exécutent dans la même fonction base de données, restreinte par visibilité, que le matching texte.
- L'index plein texte couvre exactement les champs descriptifs : nom, notes, email, téléphone et adresse.
- Les numéros d'immatriculation et de TVA ne sont pas dans l'index plein texte : lisez-les sur la fiche elle-même (get_person_context) plutôt que de les chercher via query.
- Insensible aux accents dans les deux sens : une requête sans accents matche le texte stocké accentué, et une requête accentuée matche le texte sans accents.
- Un rôle matche le label d'une relation (lu dans les deux sens) OU un tag porté par le contact lié : with_role=fournisseur trouve les contacts reliés à quelqu'un étiqueté fournisseur ou tagué fournisseur.
- without_role est un anti-join exécuté côté serveur : il ne garde que les contacts sans AUCUN lien correspondant au rôle, évalué sur toute la base de contacts — pas un filtre côté client sur une première page de résultats.
- La visibilité s'applique aux contacts renvoyés ET aux deux extrémités de chaque relation considérée : un lien vers un contact que vous ne pouvez pas voir ne matche aucun rôle et n'apparaît pas dans links.
- Les réponses sont plafonnées à limit (au plus 50) contacts triés par nom d'affichage, sans curseur de pagination : si une recherche risque de dépasser le plafond, resserrez-la avec query, kind ou un filtre de rôle.
Paramètres
Tout ce que l'outil accepte, directement depuis l'inputSchema en vigueur. Tous les paramètres sont facultatifs.
| Paramètre | Type | Description |
|---|---|---|
| query | string (<= 200) | Recherche plein texte sur le nom, les notes, l'email, le téléphone et l'adresse du contact. Insensible aux accents, par mots entiers avec racinisation, pas de sous-chaîne. |
| kind | "individual" | "company" | Restreint aux personnes physiques (individual) ou aux entreprises (company). |
| with_role | string (<= 100) | Uniquement les contacts ayant un contact lié dans ce rôle — matché sur le label de la relation (dans les deux sens) ou sur un tag du contact lié. |
| without_role | string (<= 100) | Uniquement les contacts sans AUCUN contact lié dans ce rôle. Exemple : kind=company avec without_role=commercial liste les entreprises sans commercial. |
| limit | number | Nombre maximal de résultats. 20 par défaut, plafonné à 50. |
Réponse
Chaque contact revient sous forme compacte : identité, téléphone et email, un aperçu des notes, les tags, et ses liens relationnels avec leurs labels orientés. Récupérez la fiche complète avec get_person_context.
{
"contacts": [
{
"id": "b2ce0d4f-7e19-4c2b-8f3a-5d6e7f8a9b0c",
"display_name": "François Martin",
"kind": "individual",
"phone": "+33 6 12 34 56 78",
"email": "francois@boulangerie-martin.fr",
"notes": "Prefers morning deliveries - ring the back door...",
"tags": [
"supplier"
],
"links": [
{
"id": "0a1b2c3d-4e5f-4789-8bcd-ef0123456789",
"name": "Boulangerie Martin",
"kind": "company",
"relationship": "manager",
"note": null
}
]
}
]
}{
"contacts": []
}{
"type": "object",
"properties": {
"contacts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"display_name": {
"type": "string"
},
"kind": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"phone": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"email": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"notes": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"links": {
"type": "array",
"items": {
"type": "object",
"properties": {
"relationship": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"additionalProperties": {}
}
}
},
"required": [
"id",
"display_name",
"kind",
"phone",
"email",
"notes",
"tags",
"links"
],
"additionalProperties": false
}
}
},
"required": [
"contacts"
],
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false
}Exemples d'utilisation
Retrouver un contact par un détail descriptif
{
"query": "morning deliveries"
}{
"contacts": [
{
"id": "b2ce0d4f-...",
"display_name": "François Martin",
"kind": "individual",
"notes": "Prefers morning deliveries - ring the back door...",
"tags": [
"supplier"
]
}
]
}Chercher malgré un accent manquant
{
"query": "francois"
}{
"contacts": [
{
"id": "b2ce0d4f-...",
"display_name": "François Martin",
"kind": "individual",
"email": "francois@boulangerie-martin.fr",
"tags": [
"supplier"
]
}
]
}Les entreprises ayant un contact dans un rôle
{
"kind": "company",
"with_role": "sales"
}{
"contacts": [
{
"id": "7d8e9f0a-...",
"display_name": "Atelier Lemaire",
"kind": "company",
"tags": [
"client"
],
"links": [
{
"id": "3c4d5e6f-...",
"name": "Claire Lemaire",
"kind": "individual",
"relationship": "sales",
"note": null
}
]
}
]
}Les entreprises sans un rôle
{
"kind": "company",
"without_role": "sales"
}{
"contacts": [
{
"id": "9e0f1a2b-...",
"display_name": "Garage Petit",
"kind": "company",
"tags": [
"prospect"
],
"links": []
}
]
}Un rôle peut venir d'un label ou d'un tag
{
"with_role": "supplier"
}{
"contacts": [
{
"id": "b2ce0d4f-...",
"display_name": "François Martin",
"kind": "individual",
"tags": [],
"links": [
{
"id": "0a1b2c3d-...",
"name": "Boulangerie Martin",
"kind": "company",
"relationship": "supplier",
"note": null
}
]
},
{
"id": "5f6a7b8c-...",
"display_name": "Vergers Rousseau",
"kind": "company",
"tags": [
"supplier"
],
"links": []
}
]
}Garanties et limites
- Les deux recherches sont des recherches plein texte PostgreSQL déterministes : même requête, mêmes données, mêmes résultats. Rien de sémantique, pas de vecteurs — la requête matche des mots, pas des sens.
- La visibilité est résolue avant le matching : le serveur restreint d'abord la requête aux enregistrements que l'appelant a le droit de voir, puis seulement cherche des correspondances.
- Un enregistrement invisible est indistinguable d'un enregistrement inexistant : la recherche ne confirme jamais l'existence de données masquées.
- Les index sont recalculés dans la même transaction que chaque écriture : une entrée est cherchable à l'instant où elle est créée, sans indexeur en arrière-plan ni fenêtre d'incohérence.
- Historis n'exécute aucun modèle d'IA pour retrouver les résultats. Votre agent décide quoi chercher puis classe et interprète ce qui revient ; le matching lui-même est une simple recherche en base.
Deux recherches, deux métiers
- search_events
- Cherche dans la timeline : ce qui s'est passé, ce qui est dû, ce qui a changé. Compose le plein texte avec des filtres métier et temporels, paginé par curseur keyset.
- search_persons
- Cherche dans les fiches contact et le graphe relationnel : qui est quelqu'un, comment il est relié, quel rôle porte le lien.