Passer au contenu principal
La plupart des outils de sécurité statiques détectent les bugs de manière isolée. Ils analysent un fichier, listent les problèmes et passent au suivant. Le problème, c’est que les vulnérabilités les plus dommageables dans les bases de code modernes sont rarement un bug unique. Ce sont une chaîne : une clé de signature codée en dur plus une vérification d’autorisation manquante plus une injection SQL qui, prises isolément, semblent toutes gérables. Ensemble, elles forment un chemin de prise de contrôle de compte. C’est exactement le type de raisonnement transversal pour lequel les LLM sont efficaces, à condition de leur fournir la bonne structure. Dans cet article, nous allons construire un réviseur de sécurité de code à deux agents en utilisant Python et l’API Venice AI. À la fin, vous disposerez d’une CLI que vous pourrez pointer sur n’importe quelle base de code Python afin de produire un rapport Markdown avec des résultats atomiques et des chaînes d’exploitation. Intéressé par l’implémentation complète du code ? Consultez le dépôt GitHub. Avant de continuer, vous aurez besoin d’une clé API Venice. Exportez-la en tant que variable d’environnement :

Ce que nous allons construire

Le réviseur est un petit projet Python comportant quelques parties bien distinctes : Le flux ressemble à ceci :
  1. Parcourir le répertoire cible à la recherche de fichiers .py.
  2. Construire une carte de dépôt déterministe (imports, symboles publics, signatures).
  3. Pour chaque fichier, envoyer au Scanner sa source plus une tranche de voisinage par fichier de la carte et collecter les résultats atomiques.
  4. Envoyer l’union des résultats plus la carte de dépôt condensée au Chainer et collecter les chaînes d’exploitation.
  5. Supprimer toute chaîne qui référence un ID de résultat que le Scanner n’a pas produit, ou qui nomme un fichier dont aucun des résultats référencés ne provient réellement.
  6. Écrire un rapport Markdown.
Deux décisions de conception méritent d’être soulignées avant de commencer à écrire du code. La première est pourquoi deux agents au lieu d’un. Un scanner à agent unique qui tente de tout faire en un seul prompt doit trouver un équilibre entre être exhaustif sur les bugs par fichier et être astucieux sur le raisonnement combinatoire. Diviser le travail signifie que le Scanner peut être impitoyable et bruyant, et que le Chainer peut être sélectif et discret. Ajouter un appel LLM supplémentaire dédié à la combinaison des résultats déverrouille toute une catégorie de bugs pour très peu de code supplémentaire. La seconde est pourquoi une carte de dépôt. Les bases de code réelles s’étendent sur de nombreux fichiers. Un bug du type « le validateur s’exécute mais ne s’applique pas par itération dans le fetcher, et la réponse du fetcher se retrouve dans le rendu » est invisible pour un scanner par fichier. Avant tout appel LLM, nous parcourons l’arborescence cible avec ast de Python et construisons une carte structurelle. Le Scanner voit un voisinage par fichier (qui importe depuis ce fichier, ce que ce fichier importe, les signatures de ces symboles externes). Le Chainer voit une carte complète condensée (chaque module, chaque symbole public, chaque arête d’import, sans source). C’est la plus petite quantité d’ingénierie de contexte que nous ayons trouvée qui permet au Chainer de construire des chaînes dont le flux de données traverse les frontières de modules, sans payer le coût en tokens consistant à insérer la totalité de la base de code dans chaque prompt.

Prérequis

  • Python 3.12+
  • Une clé API Venice depuis venice.ai
  • Une familiarité de base avec Pydantic, le module ast de Python et le SDK Python d’OpenAI
Le dépôt de référence utilise uv pour la gestion des dépendances, mais un environnement virtuel classique fonctionne tout aussi bien.

Mise en place du projet

Créez un nouveau projet et installez les dépendances :
Si vous préférez pip, créez plutôt un environnement virtuel :
Créez un fichier .env pour le développement local :
Nous allons disposer le code source sous src/venice_security_reviewer/ pour qu’il puisse être importé comme un package, avec les prompts sous prompts/ à la racine du dépôt afin qu’ils puissent être révisés et comparés comme tout autre artefact source :

Mise en place du client Venice

Venice est compatible OpenAI, nous pouvons donc utiliser le SDK Python officiel d’OpenAI et pointer son base_url vers Venice. Centraliser la construction du client dans un seul fichier signifie que le reste du code n’a jamais à savoir avec quel fournisseur il communique : changer de backend ne toucherait que ce module. Créez src/venice_security_reviewer/client.py :
Quelques points à noter :
  • Nous utilisons par défaut zai-org-glm-5 parce que c’est un modèle Venice polyvalent et performant, mais vous pouvez le remplacer via la variable d’environnement VENICE_MODEL. Pour des bases de code plus volumineuses ou plus nuancées, choisir un modèle plus puissant peut rendre le Chainer nettement meilleur en qualité narrative.
  • build_client retourne le client et l’ID du modèle, afin que les appelants n’aient pas à lire les variables d’environnement eux-mêmes et que les tests puissent injecter une fausse configuration sans monkeypatching.

Définition des modèles de données

Tout l’intérêt d’utiliser Pydantic ici, plutôt que de faire circuler des dicts bruts, est que nous obtenons une frontière de validation stricte entre le LLM et le reste du programme. Si le modèle renvoie du JSON malformé ou invente un ID de résultat qui n’existe pas, le parsing échoue bruyamment et nous ne propageons jamais l’hallucination dans le rapport. Créez src/venice_security_reviewer/models.py :
Les contraintes font ici un vrai travail :
  • Finding.id et Chain.id sont contraints à une regex du type F-001, C-001. Si le modèle fait preuve de créativité sur le format, la validation échoue.
  • Chain.findings exige au moins deux entrées : une « chaîne » d’un seul résultat n’est qu’un résultat.
  • Chain.severity est restreint à high ou critical. Une combinaison de résultats qui n’élève pas l’impact au-dessus de la sévérité individuelle la plus élevée n’est pas une chaîne digne d’être rapportée.
  • Evidence impose que end_line >= start_line afin que le modèle ne puisse pas retourner des plages de lignes absurdes.
C’est la validation de forme. Nous avons également besoin d’une validation de références croisées : une chaîne qui référence un ID de résultat que le Scanner n’a jamais produit n’a aucun sens. Ajoutez cette fonction à models.py :
C’est le garde-fou déterministe qui maintient la sincérité du Chainer. Il ne peut référencer que les résultats que le Scanner a réellement produits, et il ne peut prétendre que des fichiers impliqués dans la chaîne d’où provient effectivement l’un de ces résultats. Renvoyer les chaînes supprimées plutôt que de les filtrer silencieusement permet à la CLI d’émettre un avertissement quand le modèle essaie d’inventer quelque chose.

Construction de la carte de dépôt AST

La carte de dépôt est le squelette structurel d’une base de code Python : la surface publique de chaque module, chaque arête d’import et un index inverse de « module M » vers « modules qui importent depuis M ». Elle est construite une fois par exécution de scan avec ast de Python, jamais par exécution, donc il est sûr de l’exécuter sur du code adversarial : le parseur n’importe ni n’invoque rien depuis l’arbre analysé. Nous consommerons la carte sous deux formes. Le Scanner reçoit une tranche de voisinage par fichier afin que ses prompts restent de taille limitée. Le Chainer reçoit une carte complète condensée afin qu’il puisse construire des chaînes à travers les fichiers. Créez src/venice_security_reviewer/repo_map.py et commencez par les modèles Pydantic qui décrivent la carte :
Maintenant, l’aide qui parcourt l’arborescence et saute les répertoires que nous ne devrions pas indexer :
Pour chaque fichier, nous voulons trois choses de l’AST : les symboles de niveau supérieur qu’il définit, les arêtes d’import et une liste __all__ explicite si elle est présente. Les signatures de fonctions et les en-têtes de classes sont rendus sous forme de chaînes compactes que le LLM peut lire directement :
Le _SIGNATURE_CHAR_CAP de 200 préserve les signatures réelles typiques (y compris les hints de type) tout en empêchant les cas pathologiques comme une union typée de 200 lignes qui ferait exploser le prompt. Ensuite, l’extracteur qui tire les données structurelles d’un module parsé. Nous gérons ast.FunctionDef, ast.ClassDef, les ast.Assign et ast.AnnAssign de niveau supérieur pour les constantes, et à la fois ast.Import et ast.ImportFrom pour les arêtes d’import. Les imports relatifs sont résolus dans leur forme pointée absolue afin que le Chainer puisse les apparier ultérieurement aux noms de modules :
La logique d’extraction complète parcourt tree.body et émet des entrées SymbolDef et ImportEdge pour chaque nœud de niveau supérieur. La fonction _extract du dépôt de référence dans repo_map.py couvre l’implémentation complète. La forme qui en ressort est une liste d’objets ModuleEntry, un par fichier. La partie intéressante est ce que nous faisons avec ces entrées. Encapsulez-les dans une RepoMap avec deux méthodes orientées consommateur :
neighborhood(path) est ce que le Scanner appelle pour chaque fichier. Cela retourne un objet ModuleNeighborhood contenant le module lui-même, tout autre module qui importe depuis lui, et chaque symbole intra-dépôt qu’il importe d’ailleurs (avec leurs signatures résolues). Cela donne au Scanner suffisamment de contexte pour signaler des résultats qui ne sont évidents que dans un contexte transversal aux fichiers, sans traîner toute la base de code dans le prompt. condensed_dict() est ce que le Chainer reçoit. Les extraits et les signatures sont supprimés ; seuls les chemins, les noms de modules, les exports publics et les arêtes d’import demeurent. C’est la plus petite représentation qui permette encore au Chainer de raisonner sur le flux de données entre modules. Enfin, le point d’entrée qui construit le tout :
Les fichiers que nous ne pouvons pas lire ou qui ne parviennent pas à être parsés sont journalisés et sautés. Nous retournons une carte partielle plutôt que de faire échouer toute l’exécution ; au pire, un appel au Scanner ne verra aucun voisinage pour un fichier, ce qui reste un scan fonctionnel.

Écriture de l’agent Scanner

Le Scanner parcourt un chemin cible, récupère les fichiers source Python et demande à Venice d’identifier les vulnérabilités atomiques un fichier à la fois. L’analyse par fichier maintient le prompt à une taille réduite et isole les défaillances : un mauvais fichier ne fait pas tomber toute l’exécution. Nous garderons le prompt lui-même dans un fichier séparé afin qu’il puisse être révisé et comparé comme tout autre artefact source. Créez prompts/scanner.md :
Le prompt complet dans le dépôt de référence contient également une section « What to look for » listant les classes courantes de vulnérabilités (secrets codés en dur, injection SQL, injection de commandes, SSRF, désérialisation non sécurisée, etc.) et une section « How to use the neighborhood » expliquant comment le modèle doit consommer le contexte transversal aux fichiers. Quelques notes sur la conception du prompt :
  • Nous indiquons au modèle d’émettre uniquement du JSON, sans prose ni clôtures. Le SDK d’OpenAI prend en charge un paramètre response_format={"type": "json_object"} qui impose cela côté API, mais le renforcer dans le prompt réduit les cas limites.
  • Nous interdisons explicitement au Scanner de produire des chaînes transversales aux fichiers. Les chaînes sont la mission du Chainer, et demander au Scanner de faire les deux brouille la responsabilité.
  • Nous exigeons que l’extrait soit copié textuellement. Cela signifie que le rapport peut citer les octets exacts que le modèle prétend avoir vus, et qu’un réviseur peut vérifier ponctuellement un résultat en comparant l’extrait à la source.
Maintenant, le code de l’agent. Créez src/venice_security_reviewer/scanner.py et commencez par le parcoureur de fichiers et le chargeur de prompt :
MAX_FILE_BYTES est un plafond de sécurité. Au-delà d’environ 200 Ko, nous sautons plutôt que d’envoyer un prompt énorme qui sera probablement à la fois coûteux et de faible qualité. Le morceau suivant est le constructeur de prompt. Le modèle utilise {filename}, {source} et {neighborhood} comme espaces réservés ; nous utilisons str.replace plutôt que .format() parce que le modèle contient des exemples JSON avec des accolades littérales :
Maintenant le parseur. Nous désérialisons le JSON, validons chaque résultat via Pydantic, et écartons les résultats malformés individuels plutôt que de faire échouer tout le fichier. Un mauvais résultat ne devrait pas nous faire perdre les bons :
Le Scanner émet des IDs comme F-001 par fichier, mais le Chainer a besoin de référencer les résultats à travers tout le dépôt. Nous réattribuons les IDs par rapport à un compteur monotone afin qu’ils soient globalement uniques :
L’appel de scan d’un seul fichier combine tout cela. Nous lisons le fichier, construisons le prompt, l’envoyons à Venice avec response_format={"type": "json_object"} et une température basse, et parsons le résultat :
Deux détails méritent d’être soulignés :
  • Nous corrigeons le chemin du fichier de preuve pour qu’il soit relatif à repo_root après le parsing, puisque le modèle renvoie en écho le nom de fichier que nous lui avons donné, mais nous voulons une forme canonique unique dans tout le rapport.
  • temperature=0.1 est intentionnellement basse. Nous voulons que le Scanner soit conservateur et cohérent entre les exécutions ; la créativité est la mission du Chainer.
Enfin, l’orchestrateur qui scanne chaque fichier éligible sous la racine :
La carte du dépôt est construite une seule fois par l’appelant et réutilisée pour chaque fichier, de sorte que le Scanner voit une structure globale cohérente même lorsque des fichiers individuels échouent à être parsés ou sont sautés.

Écriture de l’agent Chainer

Le Chainer prend l’union des résultats du Scanner plus la carte de dépôt condensée et demande à Venice si l’un des sous-ensembles de résultats se combine en une véritable chaîne d’exploitation. Deux garde-fous déterministes se trouvent entre le LLM et le rapport :
  1. Chaque chaîne doit référencer uniquement des IDs de résultats que le Scanner a produits.
  2. Chaque chaîne doit revendiquer uniquement les fichiers que la preuve d’au moins un résultat référencé touche.
Les chaînes qui violent l’une ou l’autre règle sont écartées au moment du parsing. Cela empêche le modèle d’halluciner des chaînes « au cas où » et de prétendre qu’une chaîne s’étend sur des fichiers pour lesquels il n’a aucune preuve. Le prompt du Chainer se trouve dans prompts/chainer.md. Son cœur ressemble à ceci :
Le prompt complet dans le dépôt de référence explique également comment lire la carte du dépôt, comment décider ce qui figure dans files_involved, et surtout, quand ne pas enchaîner. Dire au modèle « il est correct et attendu pour de nombreuses bases de code d’avoir des résultats qui ne s’enchaînent pas » est ce qui l’empêche d’inventer des chaînes pour paraître productif. Maintenant le code de l’agent. Créez src/venice_security_reviewer/chainer.py :
MAX_REPO_MAP_CHARS = 8000 est un plafond souple pour le bloc de carte de dépôt rendu en JSON dans le prompt du Chainer. À environ 4 caractères par token, cela représente environ 2 000 tokens, ce qui tient confortablement dans la fenêtre de contexte de n’importe quel modèle Venice, même avec les résultats et le budget narratif en sus. Nous sérialisons les résultats dans un bloc JSON compact. Notez que nous supprimons délibérément le snippet des preuves ici : le Chainer n’a pas besoin des octets bruts pour décider si deux résultats se combinent, et les inclure double approximativement le coût en tokens sur de vraies bases de code :
Pour les bases de code plus volumineuses, la carte de dépôt condensée complète peut dépasser notre budget de caractères. Lorsque cela se produit, nous élaguons pour ne conserver que les modules porteurs de résultats plus leurs voisins directs. Cela préserve suffisamment de structure pour que le Chainer puisse raisonner sur les chaînes pour lesquelles nous avons des preuves, et écarte le reste :
La stratégie d’élagage est intentionnellement simple : on conserve les modules dans lesquels vivent nos résultats, et on conserve leurs voisins directs dans le graphe d’imports. Tout ce qui est plus éloigné n’a aucun rôle plausible dans une chaîne pour laquelle nous avons actuellement des preuves, et peut donc être écarté sans perdre de puissance de raisonnement. Nous annotons aussi le payload avec les marqueurs _pruned, _kept et _total, afin que le prompt du Chainer puisse avertir le modèle quand la carte a été réduite. Le parsing de la réponse a la même forme que celui du Scanner : désérialisation, validation de chaque chaîne via Pydantic, suppression des entrées malformées :
Puis l’agent lui-même :
Quelques points à souligner :
  • Nous abandonnons avant d’appeler le modèle lorsqu’il y a moins de deux résultats. On ne peut pas enchaîner un seul résultat, et éviter l’appel signifie que nous ne brûlons pas de tokens sur un résultat garanti vide.
  • temperature=0.2 est légèrement plus élevée que les 0.1 du Scanner. Le Chainer bénéficie d’un peu plus de créativité pour repérer des combinaisons non évidentes, mais nous voulons toujours qu’il soit ancré dans les résultats et la carte qui lui ont été donnés.
  • Après le parsing, validate_chain_references exécute la vérification déterministe des références croisées que nous avons écrite plus tôt. Tout ce qui survit peut être rendu en toute sécurité ; tout ce qui ne survit pas est journalisé afin que l’opérateur sache que le modèle a essayé d’inventer quelque chose.
Cette vérification des références croisées est la pièce la plus importante de tout le projet. C’est la frontière entre « outil de sécurité utile » et « rapport d’IA parfois confiant à tort ». Avec cette vérification en place, même si le modèle hallucine, la mauvaise chaîne n’atteint jamais le rapport.

Rendu du rapport Markdown

Garder le rendu séparé de la logique de l’agent signifie que les mêmes objets Finding et Chain peuvent ensuite être alimentés dans d’autres formats (JSON, SARIF, HTML) sans toucher au Scanner ou au Chainer. Nous utiliserons Jinja2 avec un petit fichier de modèle. Créez src/venice_security_reviewer/templates/report.md.j2 :
Puis le renderer dans src/venice_security_reviewer/report.py :
L’autoescape reste désactivé pour le modèle Markdown (Markdown n’est pas du HTML), mais nous le laissons activé pour tous les futurs modèles .html par extension.

Câblage de la CLI

La CLI est l’orchestrateur : construire la carte du dépôt, scanner, enchaîner, restituer. Nous utiliserons Typer pour gérer le parsing des arguments et Rich pour afficher un joli tableau de résumé. Créez src/venice_security_reviewer/cli.py :
Ajoutez le point d’entrée du script à pyproject.toml :
C’est l’ensemble du pipeline câblé.

Tester les garde-fous

Nous avons largement insisté sur une idée tout au long de cette construction : les garde-fous déterministes sont ce qui distingue un outil de sécurité utile d’un outil confiant à tort. Cette affirmation ne vaut la peine d’être faite que si nous pouvons prouver que les garde-fous tiennent réellement, donc les tests les plus précieux dans ce projet n’appellent pas Venice du tout. Ils verrouillent la frontière Pydantic et la plomberie d’assemblage du prompt, ce qui signifie qu’ils s’exécutent hors ligne, en quelques millisecondes, sans clé API et sans coût en tokens. Ajoutez d’abord les dépendances de développement :
La première chose qu’il vaut la peine de tester est la frontière du modèle elle-même. Ces tests affirment que les résultats et chaînes malformés sont rejetés au moment de la construction, avant qu’ils ne puissent atteindre un rapport. Créez tests/test_models.py :
Chacun de ces tests reflète une contrainte que nous avons placée sur les modèles plus tôt : une plage de lignes inversée, un ID qui ne correspond pas au pattern F-### et une « chaîne » d’un seul résultat. Si l’un d’eux cesse de lever une exception, toute une catégorie d’hallucinations est silencieusement redevenue possible. Le test le plus important couvre le validateur de références croisées, puisque c’est la fonction qui supprime réellement les chaînes inventées :
F-999 n’a jamais été produit par le Scanner, donc la chaîne qui le référence se retrouve dans dropped et n’atteint jamais le rapport. Le test compagnon dans le dépôt de référence, test_validate_chain_references_drops_unknown_files, fait la même chose pour une chaîne qui prétend qu’un fichier ne provient d’aucun de ses résultats. La deuxième chose qu’il vaut la peine de tester est la plomberie qui alimente le Chainer. Il est facile de refactoriser l’assemblage du prompt et d’arrêter silencieusement de passer le contexte transversal aux fichiers, après quoi le Chainer continuerait de fonctionner mais s’aggraverait silencieusement. Ce test construit un fixture à deux modules, rend le prompt et affirme que les informations transversales aux fichiers sont effectivement présentes, là encore sans aller-retour Venice. Créez tests/test_cross_file_chain.py :
Si ce test passe, le Chainer reçoit un prompt qui contient à la fois les deux résultats, les deux chemins de fichiers et l’arête d’import entre eux. Que le modèle utilise bien cette information est une évaluation séparée et hors-bande ; ce test ne protège que la plomberie qui fait entrer l’information dans le prompt en premier lieu. Exécutez l’ensemble de la suite, plus le linter et le vérificateur de types, avec :
Comme aucun de ces tests ne touche au réseau, ils peuvent être exécutés sans risque à chaque commit et en CI sans brûler de tokens ni avoir besoin d’une clé Venice. Le dépôt de référence inclut également tests/test_scanner_parse.py, tests/test_chainer_parse.py et tests/test_repo_map.py, qui couvrent les cas limites de parsing JSON (les entrées malformées sont écartées plutôt que de faire planter l’exécution) et le constructeur de carte de dépôt AST.

Exécution du projet

Pour l’essayer sur une vraie base de code, pointez la CLI sur un répertoire de code source Python :
Ou installez-le dans votre virtualenv avec pip install -e . et exécutez venice-security-reviewer scan path/to/your/code. La sortie ressemble approximativement à ceci :
Le rapport Markdown affiche chaque chaîne en haut avec son récit, puis chaque résultat individuel en dessous avec la sévérité, le CWE, l’emplacement du fichier, la description et l’extrait textuel que le modèle prétend avoir lu. Le dépôt de référence est aussi livré avec quatre cibles de démonstration incluses qui exercent chacune une forme différente de raisonnement que le Chainer doit effectuer :
  • examples/vulnerable_app — une application Flask multi-fichiers avec trois résultats « low », dont deux se combinent en une chaîne critique d’élévation de privilèges à travers les fichiers. Teste si le Chainer est sélectif sur ce qu’il combine.
  • examples/url_preview — un récupérateur d’URL multi-fichiers avec une liste blanche défensive qui ne s’applique pas par itération. Teste le raisonnement sur le flux de données transversal aux fichiers combiné à la topologie de déploiement (les IP link-local sont des passerelles vers les identifiants cloud).
  • examples/csv_query — un filtre CSV à fichier unique avec une évasion de bac à sable eval via __class__.__base__.__subclasses__(). Teste le raisonnement au niveau du langage plutôt que le flux HTTP.
  • examples/webhook_handler — un vérificateur HMAC à fichier unique avec une vulnérabilité de différentielle d’analyseur JSON. Teste le raisonnement sur plusieurs spécifications.
Essayez-les avec :
Si vous voyez un jour la CLI journaliser chainer referenced N unknown finding id(s) or file(s); chains dropped, c’est le validateur de références croisées qui prend le modèle en flagrant délit d’invention d’une chaîne. Les chaînes supprimées n’arrivent jamais dans le rapport ; vous obtenez simplement un avertissement que vous pouvez utiliser pour ajuster le prompt ou échantillonner des exécutions supplémentaires du Chainer.

Étendre cet exemple

La forme à deux agents se généralise bien. Quelques directions qu’il vaut la peine d’explorer :
  • Plus de langages. Le Scanner est indépendant du langage au niveau du prompt ; c’est le constructeur d’AST qui est spécifique à Python. Remplacez-le par tree-sitter et vous pouvez construire les mêmes formes de voisinage/carte condensée pour TypeScript, Go ou Rust.
  • Un troisième agent pour les correctifs. Une fois que vous avez une chaîne, demander à un agent Patcher de rédiger un diff unifié qui désamorce l’un des résultats constitutifs est un petit pas. Validez le diff via Pydantic par rapport au même ensemble de fichiers de preuve et vous obtenez gratuitement la même protection contre les hallucinations.
  • Formats de sortie. render_report est le seul endroit qui connaît Markdown. Ajoutez un renderer SARIF et les mêmes résultats peuvent être déversés dans le code scanning de GitHub. Ajoutez un renderer JSON et vous pouvez acheminer les résultats dans un système en aval.
  • Mise en cache par hachage de fichier. Les appels par fichier du Scanner sont indépendants et idempotents. Mettre en cache par (file_hash, prompt_hash, model) signifie que re-scanner un dépôt dans lequel un fichier a changé ne réexécute le Scanner que sur ce seul fichier.
  • Échantillonnage pour le Chainer. Pour les exécutions à enjeux élevés, appelez le Chainer N fois à une température légèrement plus élevée et croisez les résultats. Les chaînes que le modèle trouve de manière cohérente sont plus susceptibles d’être réelles ; les chaînes qu’il trouve une fois et jamais plus sont probablement du bruit.
  • Modèles plus puissants. zai-org-glm-5 est la valeur par défaut parce qu’il offre un bon équilibre entre coût et qualité pour le raisonnement combinatoire, mais pour des bases de code plus difficiles, choisir un modèle Venice plus puissant (défini via VENICE_MODEL) peut rendre les récits du Chainer notablement plus serrés.

Pour conclure

Merci de votre lecture ! Espérons que cela vous a aidé à comprendre comment structurer un outil de sécurité IA réellement digne de confiance. Le motif que nous avons utilisé ici se généralise au-delà de la sécurité aussi : chaque fois que vous voulez qu’un LLM raisonne à travers des fichiers d’une manière qui doit s’ancrer dans des preuves réelles, la recette est la même. Construisez une carte structurelle déterministe, fournissez au modèle une tranche qui tient dans le contexte, validez les références du modèle par rapport à la structure et supprimez tout ce qu’il ne peut pas ancrer. En utilisant Python avec l’API Venice AI, nous pouvons construire des agents qui combinent le raisonnement LLM avec des frontières de validation strictes, et livrer quelque chose qui donne une réponse utile au lieu d’une réponse qui sonne juste avec assurance.