Aller au contenu

Blog

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 :

Excel de commande de pièce

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.

src/index.ts
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
}
src/controllers/ExtractorImage.ts
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 :

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

Fenêtre de terminal
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.

src/controllers/ExtractorImage.ts
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 !

Mistral AI analyse d'image

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 :

src/controllers/ExtractorImage.ts
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
}
]

Résultat formaté par la fonction calling

À 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

Lien du projet sur Github

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 !

Sources

Mistral AI

Mistral AI Documentation

Mistral AI Function calling

Mistral AI Models

Mistral AI SDK

Bun

Hono