Contrôler l'IA avec les Function calling (Mistral/OpenAI)
Mistral Function callings TypeScript
Vous avez déjà utilisé des API d’intelligence artificielle ? Si vous avez déjà intégré OpenAI API ou Mistral API, vous savez que les réponses de l’IA sont très imprévisibles, même si notre prompt demande un format JSON, cela ne fonctionne pas à tous les coups.
Pour résoudre ce problème, nous allons utiliser le concept de function calling, qui est disponible dans la plus part des API d’IA. Cela va plutôt dépendre du model.
Les function calling, c’est quoi ?
Les function calling sont des fonctions qui sont appelées par l’IA. On va donc pouvoir exécuter des fonctions de notre choix pendant la réponse ou alors … Formater la réponse avec des clés prédéfinies.
Dans ce tutoriel, je ne vais pas explorer l’exécution de fonctions externe, mais plutôt je vais utiliser les fonctions callings pour formater la réponse, ce qui va nous permettre de récupérer des données structurées de la même façon à tout les coups.
Documentation function calling Mistral
Mistral
Si vous ne le savez pas, Mistral est une IA open source, développée par des français ! Mistral
Elle dispose de plusieurs models, qui permettent différentes utilisations. Cela dépend donc de vos besoins ! Il existe même un modèle de vision qui permet d’extraire les données d’une image !
Est-ce qu’on ne ferait pas un petit workflow avec deux modèles ?
Projet
Bon, pour avoir un petit exemple, je vais coder rapidement une API qui va nous permettre l’upload d’une image d’un excel de commande de pièce. Avec cette image nous allons utiliser le modèle de vision de Mistral pour extraire les données de la pièce et ensuite le model de texte avec une function calling pour formater la réponse.
J’utiliserai l’image suivante pour mes tests :
Start
Pour le développement rapidement de cette API, je vais utiliser Bun avec Hono pour avoir une API rapidement et simplement.
Si vous voulez découvrir comment débuter avec Bun et Hono, j’ai réalisé récemment un article sur le sujet : Découverte de Bun
Upload de fichier
Voici mes deux fichiers qui me permettent d’upload une image et de récupérer le base64 de l’image qui permettra l’envoie à l’api de mistral.
import { Hono } from 'hono';import { ExtractorFunction } from './controllers/ExtractorImage';
const app = new Hono();
app.get('/status', (c) => { return c.text('Ready 🔥🔥🔥')});
app.post('/upload', ...ExtractorFunction);
export default { port: 4000, fetch: app.fetch}
import { createFactory } from "hono/factory";import { HTTPException } from "hono/http-exception";
const factory = createFactory();
interface UploadedFile { name: string; size: number; type: string;}
export async function extractFile(request: any){ const formData = await request.req.formData(); const file = formData.get("file") as UploadedFile; if (!(file instanceof File)) { throw new HTTPException(400, { message: "No file uploaded" }); } const arrayBuffer = await file.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); const base64Image = buffer.toString("base64"); return base64Image;}
export const ExtractorFunction = factory.createHandlers(async (c) => { const base64Image = await extractFile(c); return c.text(`File uploaded : ${base64Image}`);});
Configuration Postman :
Mistral API
Bien, maintenant que nous avons l’upload, rajoutons une étape d’analyse qui nous permettras de récupérer le texte d’une image.
Pour voir les models disponibles, voici un lien très utile : https://docs.mistral.ai/getting-started/models/models_overview/
Et le lien de la documentation de Mistral : Documentation
En premier, nous allons installer le SDK de mistral AI pour TypeScript. SDK
bun add @mistralai/mistralai
Ensuite dans mon fichier ExtractorImage.ts, je vais ajouter le client Mistral et utiliser le model de vision pixtral-12b-2409
.
import { Mistral } from '@mistralai/mistralai';
const apiKey = Bun.env.MISTRAL_AI_API_KEY;const client = new Mistral({ apiKey: apiKey });
interface ImageResponse { choices?: { message: { content: string; }; }[];}
async function analyzeImage(base64Image: string) { try { const imageResponse = await client.chat.complete({ model: "pixtral-12b-2409", messages: [ { role: "user", content: [ { type: "text", text: "Please analyze the provided image. If it contains a table, extract each row and convert it into a structured JSON format. For each row, use the exact values without filling in missing fields with data from other rows. If a field is empty, leave it empty. Ensure that each JSON object represents exactly one row of the table. Thank you!" }, { type: "image_url", imageUrl: `data:image/jpeg;base64,${base64Image}`, }, ], }, ], }) as ImageResponse; return imageResponse?.choices?.[0]?.message?.content; } catch (error) { throw new HTTPException(500, { message: `Error analyzing image : ${error}` }); }}
Et du coup, on ajoute cette fonction dans notre controller HTTP.
export const ExtractorFunction = factory.createHandlers(async (c) => { const base64Image = await extractFile(c); const analyzeResult = await analyzeImage(base64Image); return c.text(`File uploaded and analyzed : ${analyzeResult}`);});
Maintenant, si on reteste notre POST d’image dans postman, nous devrions avoir le texte de l’image grâce à l’IA !
Nous voyons que l’IA nous donne le texte de l’image, mais la structure n’est pas parfaite, il y a du texte, JSON etc. Ce qui n’est pas idéal pour traiter notre sortie de façon automatique, pour intéragir avec d’autres partie de notre code.
Function calling
Nous arrivons enfin au sujet principal de cet article, les function calling, nous allons structurer la réponse d’analyse d’IA pour que nous n’ayons que un JSON avec des clés prédéfinies.
Pour cela, nous devrons utiliser un autre modèle qui prend en charge les function calling : mistral-small-latest
.
Toujours dans le ExtractorImage.ts, nous allons ajouter du code :
const tools: any[] = [ { "type": "function", "function": { "name": "retrieve_product_information", "description": "Retrieve detailed information about a product including manufacturer, description, quantity, and supplier.", "parameters": { "type": "object", "properties": { "product_name": { "type": "string", "description": "The name of the product to retrieve information about.", }, "manufacture": { "type": "string", "description": "The name of the manufacturer or brand of the product.", }, "description": { "type": "string", "description": "A brief description of the product, including its features and specifications.", }, "fabricant": { "type": "string", "description": "The company responsible for producing the product.", }, "qty": { "type": "integer", "description": "The quantity of the product available in stock.", } }, "required": ["product_name","manufacture", "description", "fabricant", "qty"], }, }, }];
async function resultFunctionCalling(text: string){ const result = await client.chat.complete({ model: "mistral-large-latest", messages: [{ role: "user", content: `Extract all information from the provided text and format the response using the tools in JSON format. Do not reply with any text; use only the provided tools to format the response. The text: ${text}` }], tools: tools, tool_choice: "auto", } as any); console.log("(1)", result); console.log("(2)", result?.choices?.[0]?.message); console.log("(3)", result?.choices?.[0]?.message?.content); const results = []; //Execute les functions calling si elles existent if (result?.choices?.[0]?.message?.toolCalls) { for (const call of result.choices[0].message.toolCalls) { // Récupère le nom de la fonction et les arguments const functionName = call.function.name; const resultFunction = JSON.parse(call.function.arguments as string); console.log("The function name :", functionName); console.log("The function result :", resultFunction); // Push du résultat de la fonction dans les résultats results.push(resultFunction) } console.log("results", results) return results; } else { return result?.choices?.[0]?.message?.content; }}
Puis dans le même fichier, je remodifie le controller HTTP pour appeler la nouvelle fonction.
export const ExtractorFunction = factory.createHandlers(async (c) => { const base64Image = await extractFile(c); const analyzeResult = await analyzeImage(base64Image); const result = await resultFunctionCalling(analyzeResult as string) as Array<JSON>; return c.json(result);});
On va refaire un test postman !
Résultat :
[ { "product_name": "STMAV340TTR", "manufacture": "", "description": "SiC - MOSFET Silicon Carbide (SiC) MOSFET - ElseSiC_13 mohm, 1200 V, MJS, TO-247-4L Silicon Carbide (SiC) MOSFET - ElseSiC_13 mohm, 1200 V, MJS, TO-247-4L", "fabricant": "MULTICOMP PRO", "qty": 254 }, { "product_name": "ERA3AR8203V", "manufacture": "", "description": "Résistance à puce CMS, 47 kohm, ± 1%, 125 mW, 0805 [2012 Metric], Couche épaisse", "fabricant": "", "qty": 570 }]
À présent vous savez utiliser l’IA pour retourner des données structurées, faites plusieurs upload, vous verrez que vous aurez toujours votre JSON structuré de la même façon ! 🚀
Conclusion
Maintenant que vous avez appris à utiliser les function calling, vous pouvez intégrer facilement l’IA en contrôlant son output dans votre code, j’espère que ce tutoriel vous a aidé pour comprendre ce concept et vous donnera des idées pour vos projets 🙃
Réseaux sociaux
Comme d’habitude, n’hésitez pas à me rejoindre sur les réseaux sociaux pour plus de contenu !
-
Youtube ➡️ https://www.youtube.com/@civilisationit
-
X(Ancien Twitter) ➡️ https://x.com/Ninapepite_
-
LinkedIn ➡️ https://www.linkedin.com/in/killian-stein-4465b81a2/