[{"data":1,"prerenderedAt":3420},["ShallowReactive",2],{"note-tool-calling-agents-fr":3},{"id":4,"title":5,"body":6,"date":3408,"description":3409,"extension":3410,"meta":3411,"navigation":765,"path":3412,"seo":3413,"slug":3414,"stem":3415,"tags":3416,"__hash__":3419},"notes/notes/tool-calling-agents.fr.md","Tool Calling et Agents LLM",{"type":7,"value":8,"toc":3391},"minimark",[9,14,28,35,47,57,64,67,77,80,96,102,106,113,128,131,134,140,145,149,154,161,485,492,496,499,516,519,643,648,652,658,703,707,710,715,718,722,735,739,837,841,844,1266,1279,1283,1419,1422,1460,1465,1469,1991,2000,2004,2182,2185,2190,2195,2199,2214,2218,2222,2226,2229,2824,2828,2841,2848,2851,2939,2948,2952,2955,2959,2966,2970,2977,2983,2988,2992,2995,3001,3006,3010,3013,3023,3032,3038,3219,3224,3230,3314,3318,3321,3328,3331,3339,3344,3348,3355,3358,3375,3382,3387],[10,11,13],"h1",{"id":12},"introduction","Introduction",[15,16,17,18,22,23,27],"p",{},"Bon, aujourd'hui on va parler de ",[19,20,21],"strong",{},"tool calling"," (aussi appelé ",[24,25,26],"em",{},"function calling",", c'est pareil). C'est le mécanisme qui permet à un LLM de ne pas juste cracher du texte, mais d'interagir avec le monde extérieur : appeler une API, lire un fichier, exécuter du code, lancer une recherche web, etc.",[15,29,30,31,34],{},"Et ce qui est encore plus intéressant, c'est qu'à partir de ce mécanisme tout simple, on peut construire ce qu'on appelle des ",[19,32,33],{},"agents",". Des boucles autonomes où le LLM décide, agit, observe, et recommence jusqu'à atteindre un objectif.",[15,36,37,38,42,43,46],{},"Bref, on va décortiquer tout ça en partant de zéro, avec du code barebone, sans framework, juste avec ",[39,40,41],"code",{},"llama-cpp-python"," et un petit ",[19,44,45],{},"Qwen3-4B"," en local.",[48,49,50],"blockquote",{},[15,51,52,53,56],{},"Parce que comprendre ce qui se passe derrière la magie, c'est quand même plus satisfaisant que de juste appeler ",[39,54,55],{},"agent.run()"," sans savoir comment ça marche.",[10,58,60,61],{"id":59},"un-llm-de-base-cest-juste-texte-texte","Un LLM de base, c'est juste ",[39,62,63],{},"texte -> texte",[15,65,66],{},"Faut jamais oublier ce truc fondamental : un LLM, à la base, c'est une boîte noire qui prend du texte en entrée et qui prédit le token suivant, encore et encore, jusqu'à produire une réponse complète.",[68,69,74],"pre",{"className":70,"code":72,"language":73},[71],"language-text","prompt -> [LLM] -> réponse\n","text",[39,75,72],{"__ignoreMap":76},"",[15,78,79],{},"C'est tout. Pas d'accès à internet, pas de calculatrice, pas de mémoire externe. Juste du texte qui rentre et du texte qui sort. Le modèle ne peut pas :",[81,82,83,87,90,93],"ul",{},[84,85,86],"li",{},"Connaître la météo d'aujourd'hui",[84,88,89],{},"Faire une addition sur des grands nombres de façon fiable",[84,91,92],{},"Lire un fichier sur ton disque",[84,94,95],{},"Savoir l'heure qu'il est",[15,97,98,99,101],{},"C'est là que le ",[19,100,21],{}," entre en jeu.",[10,103,105],{"id":104},"le-tool-calling-cest-quoi","Le tool calling, c'est quoi ?",[15,107,108,109,112],{},"Le principe est simple : au lieu de répondre directement, le LLM peut décider de ",[19,110,111],{},"demander l'exécution d'une fonction externe",". Il ne l'exécute pas lui-même (il en est incapable), il émet une instruction structurée du genre :",[48,114,115],{},[15,116,117],{},[24,118,119,120,123,124,127],{},"\"Je sais pas répondre à ça tout seul, peux-tu appeler la fonction ",[39,121,122],{},"get_weather"," avec ",[39,125,126],{},"city: Paris"," ?\"",[15,129,130],{},"Et c'est ton code (le \"runtime\") qui intercepte cette demande, exécute la vraie fonction, et renvoie le résultat au modèle pour qu'il puisse continuer.",[15,132,133],{},"Le flux devient :",[68,135,138],{"className":136,"code":137,"language":73},[71],"prompt -> [LLM] -> \"je veux appeler get_weather(Paris)\"\n                         ↓\n                 appel réel à get_weather(Paris) -> \"18°C, nuageux\"\n                         ↓\nrésultat -> [LLM] -> \"Il fait 18°C à Paris avec un ciel nuageux.\"\n",[39,139,137],{"__ignoreMap":76},[48,141,142],{},[15,143,144],{},"C'est un peu comme si le LLM levait la main en disant \"je délègue, revenez vers moi avec le résultat\".",[10,146,148],{"id":147},"comment-ça-marche-techniquement","Comment ça marche techniquement ?",[150,151,153],"h2",{"id":152},"_1-on-définit-les-outils-disponibles","1. On définit les outils disponibles",[15,155,156,157,160],{},"On fournit au modèle une description des fonctions qu'il peut appeler. Le format standard, c'est du ",[19,158,159],{},"JSON Schema"," :",[68,162,166],{"className":163,"code":164,"language":165,"meta":76,"style":76},"language-json shiki shiki-themes github-dark","[\n  {\n    \"type\": \"function\",\n    \"function\": {\n      \"name\": \"get_weather\",\n      \"description\": \"Récupère la météo actuelle pour une ville donnée\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"city\": {\n            \"type\": \"string\",\n            \"description\": \"Le nom de la ville, ex: Paris, Tokyo\"\n          }\n        },\n        \"required\": [\"city\"]\n      }\n    }\n  },\n  {\n    \"type\": \"function\",\n    \"function\": {\n      \"name\": \"calculate\",\n      \"description\": \"Évalue une expression mathématique\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"expression\": {\n            \"type\": \"string\",\n            \"description\": \"L'expression mathématique à évaluer\"\n          }\n        },\n        \"required\": [\"expression\"]\n      }\n    }\n  }\n]\n","json",[39,167,168,177,183,200,209,222,235,243,256,264,272,285,296,302,308,323,329,335,341,346,357,364,376,388,395,406,413,421,432,442,447,452,464,469,474,480],{"__ignoreMap":76},[169,170,173],"span",{"class":171,"line":172},"line",1,[169,174,176],{"class":175},"s95oV","[\n",[169,178,180],{"class":171,"line":179},2,[169,181,182],{"class":175},"  {\n",[169,184,186,190,193,197],{"class":171,"line":185},3,[169,187,189],{"class":188},"sDLfK","    \"type\"",[169,191,192],{"class":175},": ",[169,194,196],{"class":195},"sU2Wk","\"function\"",[169,198,199],{"class":175},",\n",[169,201,203,206],{"class":171,"line":202},4,[169,204,205],{"class":188},"    \"function\"",[169,207,208],{"class":175},": {\n",[169,210,212,215,217,220],{"class":171,"line":211},5,[169,213,214],{"class":188},"      \"name\"",[169,216,192],{"class":175},[169,218,219],{"class":195},"\"get_weather\"",[169,221,199],{"class":175},[169,223,225,228,230,233],{"class":171,"line":224},6,[169,226,227],{"class":188},"      \"description\"",[169,229,192],{"class":175},[169,231,232],{"class":195},"\"Récupère la météo actuelle pour une ville donnée\"",[169,234,199],{"class":175},[169,236,238,241],{"class":171,"line":237},7,[169,239,240],{"class":188},"      \"parameters\"",[169,242,208],{"class":175},[169,244,246,249,251,254],{"class":171,"line":245},8,[169,247,248],{"class":188},"        \"type\"",[169,250,192],{"class":175},[169,252,253],{"class":195},"\"object\"",[169,255,199],{"class":175},[169,257,259,262],{"class":171,"line":258},9,[169,260,261],{"class":188},"        \"properties\"",[169,263,208],{"class":175},[169,265,267,270],{"class":171,"line":266},10,[169,268,269],{"class":188},"          \"city\"",[169,271,208],{"class":175},[169,273,275,278,280,283],{"class":171,"line":274},11,[169,276,277],{"class":188},"            \"type\"",[169,279,192],{"class":175},[169,281,282],{"class":195},"\"string\"",[169,284,199],{"class":175},[169,286,288,291,293],{"class":171,"line":287},12,[169,289,290],{"class":188},"            \"description\"",[169,292,192],{"class":175},[169,294,295],{"class":195},"\"Le nom de la ville, ex: Paris, Tokyo\"\n",[169,297,299],{"class":171,"line":298},13,[169,300,301],{"class":175},"          }\n",[169,303,305],{"class":171,"line":304},14,[169,306,307],{"class":175},"        },\n",[169,309,311,314,317,320],{"class":171,"line":310},15,[169,312,313],{"class":188},"        \"required\"",[169,315,316],{"class":175},": [",[169,318,319],{"class":195},"\"city\"",[169,321,322],{"class":175},"]\n",[169,324,326],{"class":171,"line":325},16,[169,327,328],{"class":175},"      }\n",[169,330,332],{"class":171,"line":331},17,[169,333,334],{"class":175},"    }\n",[169,336,338],{"class":171,"line":337},18,[169,339,340],{"class":175},"  },\n",[169,342,344],{"class":171,"line":343},19,[169,345,182],{"class":175},[169,347,349,351,353,355],{"class":171,"line":348},20,[169,350,189],{"class":188},[169,352,192],{"class":175},[169,354,196],{"class":195},[169,356,199],{"class":175},[169,358,360,362],{"class":171,"line":359},21,[169,361,205],{"class":188},[169,363,208],{"class":175},[169,365,367,369,371,374],{"class":171,"line":366},22,[169,368,214],{"class":188},[169,370,192],{"class":175},[169,372,373],{"class":195},"\"calculate\"",[169,375,199],{"class":175},[169,377,379,381,383,386],{"class":171,"line":378},23,[169,380,227],{"class":188},[169,382,192],{"class":175},[169,384,385],{"class":195},"\"Évalue une expression mathématique\"",[169,387,199],{"class":175},[169,389,391,393],{"class":171,"line":390},24,[169,392,240],{"class":188},[169,394,208],{"class":175},[169,396,398,400,402,404],{"class":171,"line":397},25,[169,399,248],{"class":188},[169,401,192],{"class":175},[169,403,253],{"class":195},[169,405,199],{"class":175},[169,407,409,411],{"class":171,"line":408},26,[169,410,261],{"class":188},[169,412,208],{"class":175},[169,414,416,419],{"class":171,"line":415},27,[169,417,418],{"class":188},"          \"expression\"",[169,420,208],{"class":175},[169,422,424,426,428,430],{"class":171,"line":423},28,[169,425,277],{"class":188},[169,427,192],{"class":175},[169,429,282],{"class":195},[169,431,199],{"class":175},[169,433,435,437,439],{"class":171,"line":434},29,[169,436,290],{"class":188},[169,438,192],{"class":175},[169,440,441],{"class":195},"\"L'expression mathématique à évaluer\"\n",[169,443,445],{"class":171,"line":444},30,[169,446,301],{"class":175},[169,448,450],{"class":171,"line":449},31,[169,451,307],{"class":175},[169,453,455,457,459,462],{"class":171,"line":454},32,[169,456,313],{"class":188},[169,458,316],{"class":175},[169,460,461],{"class":195},"\"expression\"",[169,463,322],{"class":175},[169,465,467],{"class":171,"line":466},33,[169,468,328],{"class":175},[169,470,472],{"class":171,"line":471},34,[169,473,334],{"class":175},[169,475,477],{"class":171,"line":476},35,[169,478,479],{"class":175},"  }\n",[169,481,483],{"class":171,"line":482},36,[169,484,322],{"class":175},[15,486,487,488,491],{},"Ce bloc s'appelle le ",[19,489,490],{},"tool schema",". Il est injecté dans le prompt système pour que le modèle sache ce qu'il peut faire.",[150,493,495],{"id":494},"_2-le-modèle-décide-ou-pas-dappeler-un-outil","2. Le modèle décide (ou pas) d'appeler un outil",[15,497,498],{},"Quand le modèle reçoit un message utilisateur, il a deux choix :",[81,500,501,507],{},[84,502,503,506],{},[19,504,505],{},"Répondre normalement"," avec du texte. Pas besoin d'outil, il gère tout seul.",[84,508,509,515],{},[19,510,511,512],{},"Émettre un ",[39,513,514],{},"tool_call",". Une réponse structurée qui dit \"je veux appeler telle fonction avec tels arguments\".",[15,517,518],{},"La réponse structurée ressemble à ça :",[68,520,522],{"className":163,"code":521,"language":165,"meta":76,"style":76},"{\n  \"role\": \"assistant\",\n  \"tool_calls\": [\n    {\n      \"id\": \"call_abc123\",\n      \"type\": \"function\",\n      \"function\": {\n        \"name\": \"get_weather\",\n        \"arguments\": \"{\\\"city\\\": \\\"Paris\\\"}\"\n      }\n    }\n  ]\n}\n",[39,523,524,529,541,549,554,566,577,584,595,625,629,633,638],{"__ignoreMap":76},[169,525,526],{"class":171,"line":172},[169,527,528],{"class":175},"{\n",[169,530,531,534,536,539],{"class":171,"line":179},[169,532,533],{"class":188},"  \"role\"",[169,535,192],{"class":175},[169,537,538],{"class":195},"\"assistant\"",[169,540,199],{"class":175},[169,542,543,546],{"class":171,"line":185},[169,544,545],{"class":188},"  \"tool_calls\"",[169,547,548],{"class":175},": [\n",[169,550,551],{"class":171,"line":202},[169,552,553],{"class":175},"    {\n",[169,555,556,559,561,564],{"class":171,"line":211},[169,557,558],{"class":188},"      \"id\"",[169,560,192],{"class":175},[169,562,563],{"class":195},"\"call_abc123\"",[169,565,199],{"class":175},[169,567,568,571,573,575],{"class":171,"line":224},[169,569,570],{"class":188},"      \"type\"",[169,572,192],{"class":175},[169,574,196],{"class":195},[169,576,199],{"class":175},[169,578,579,582],{"class":171,"line":237},[169,580,581],{"class":188},"      \"function\"",[169,583,208],{"class":175},[169,585,586,589,591,593],{"class":171,"line":245},[169,587,588],{"class":188},"        \"name\"",[169,590,192],{"class":175},[169,592,219],{"class":195},[169,594,199],{"class":175},[169,596,597,600,602,605,608,611,613,615,617,620,622],{"class":171,"line":258},[169,598,599],{"class":188},"        \"arguments\"",[169,601,192],{"class":175},[169,603,604],{"class":195},"\"{",[169,606,607],{"class":188},"\\\"",[169,609,610],{"class":195},"city",[169,612,607],{"class":188},[169,614,192],{"class":195},[169,616,607],{"class":188},[169,618,619],{"class":195},"Paris",[169,621,607],{"class":188},[169,623,624],{"class":195},"}\"\n",[169,626,627],{"class":171,"line":266},[169,628,328],{"class":175},[169,630,631],{"class":171,"line":274},[169,632,334],{"class":175},[169,634,635],{"class":171,"line":287},[169,636,637],{"class":175},"  ]\n",[169,639,640],{"class":171,"line":298},[169,641,642],{"class":175},"}\n",[48,644,645],{},[15,646,647],{},"Le modèle ne décide pas au hasard. Il a été fine-tuné pour reconnaître quand une demande nécessite un outil et pour formater sa réponse correctement.",[150,649,651],{"id":650},"_3-on-exécute-loutil-et-on-renvoie-le-résultat","3. On exécute l'outil et on renvoie le résultat",[15,653,654,655,657],{},"Ton code intercepte le ",[39,656,514],{},", exécute la vraie fonction, et ajoute le résultat dans l'historique de conversation :",[68,659,661],{"className":163,"code":660,"language":165,"meta":76,"style":76},"{\n  \"role\": \"tool\",\n  \"tool_call_id\": \"call_abc123\",\n  \"content\": \"Paris: 18°C, partiellement nuageux, humidité 65%\"\n}\n",[39,662,663,667,678,689,699],{"__ignoreMap":76},[169,664,665],{"class":171,"line":172},[169,666,528],{"class":175},[169,668,669,671,673,676],{"class":171,"line":179},[169,670,533],{"class":188},[169,672,192],{"class":175},[169,674,675],{"class":195},"\"tool\"",[169,677,199],{"class":175},[169,679,680,683,685,687],{"class":171,"line":185},[169,681,682],{"class":188},"  \"tool_call_id\"",[169,684,192],{"class":175},[169,686,563],{"class":195},[169,688,199],{"class":175},[169,690,691,694,696],{"class":171,"line":202},[169,692,693],{"class":188},"  \"content\"",[169,695,192],{"class":175},[169,697,698],{"class":195},"\"Paris: 18°C, partiellement nuageux, humidité 65%\"\n",[169,700,701],{"class":171,"line":211},[169,702,642],{"class":175},[150,704,706],{"id":705},"_4-le-modèle-reprend-la-main","4. Le modèle reprend la main",[15,708,709],{},"Avec le résultat de l'outil dans son contexte, le modèle génère sa réponse finale :",[48,711,712],{},[15,713,714],{},"\"Actuellement à Paris, il fait 18°C avec un ciel partiellement nuageux et une humidité de 65%.\"",[15,716,717],{},"Et voilà, c'est tout le mécanisme. Simple, non ?",[10,719,721],{"id":720},"testons-en-barebone-avec-qwen3-4b-et-llama-cpp","Testons en barebone avec Qwen3-4B et llama-cpp",[15,723,724,725,731,732,734],{},"Bon, assez de théorie. On va faire tourner ça pour de vrai, ",[19,726,727,728],{},"sans framework, sans ",[39,729,730],{},"create_chat_completion",", juste avec l'API d'inférence brute de ",[39,733,41],{},". Le but c'est de voir exactement ce qui se passe sous le capot.",[150,736,738],{"id":737},"setup-de-base","Setup de base",[68,740,744],{"className":741,"code":742,"language":743,"meta":76,"style":76},"language-python shiki shiki-themes github-dark","from llama_cpp import Llama\n\nllm = Llama(\n    model_path=\"~/models/qwen3-4b-Q4_K_M.gguf\",\n    n_ctx=4096,\n    n_gpu_layers=12,      # 12 couches sur GPU, le reste sur CPU\n    verbose=False,\n)\n","python",[39,745,746,761,767,778,791,803,820,832],{"__ignoreMap":76},[169,747,748,752,755,758],{"class":171,"line":172},[169,749,751],{"class":750},"snl16","from",[169,753,754],{"class":175}," llama_cpp ",[169,756,757],{"class":750},"import",[169,759,760],{"class":175}," Llama\n",[169,762,763],{"class":171,"line":179},[169,764,766],{"emptyLinePlaceholder":765},true,"\n",[169,768,769,772,775],{"class":171,"line":185},[169,770,771],{"class":175},"llm ",[169,773,774],{"class":750},"=",[169,776,777],{"class":175}," Llama(\n",[169,779,780,784,786,789],{"class":171,"line":202},[169,781,783],{"class":782},"s9osk","    model_path",[169,785,774],{"class":750},[169,787,788],{"class":195},"\"~/models/qwen3-4b-Q4_K_M.gguf\"",[169,790,199],{"class":175},[169,792,793,796,798,801],{"class":171,"line":211},[169,794,795],{"class":782},"    n_ctx",[169,797,774],{"class":750},[169,799,800],{"class":188},"4096",[169,802,199],{"class":175},[169,804,805,808,810,813,816],{"class":171,"line":224},[169,806,807],{"class":782},"    n_gpu_layers",[169,809,774],{"class":750},[169,811,812],{"class":188},"12",[169,814,815],{"class":175},",      ",[169,817,819],{"class":818},"sAwPA","# 12 couches sur GPU, le reste sur CPU\n",[169,821,822,825,827,830],{"class":171,"line":237},[169,823,824],{"class":782},"    verbose",[169,826,774],{"class":750},[169,828,829],{"class":188},"False",[169,831,199],{"class":175},[169,833,834],{"class":171,"line":245},[169,835,836],{"class":175},")\n",[150,838,840],{"id":839},"étape-1-formater-le-prompt-avec-les-outils","Étape 1 : formater le prompt avec les outils",[15,842,843],{},"Qwen attend un format de chat spécifique. On va construire notre prompt à la main avec le template de chat Qwen et nos définitions d'outils intégrées dans le message système.",[68,845,847],{"className":741,"code":846,"language":743,"meta":76,"style":76},"import json\n\n# Nos outils au format JSON Schema\ntools = [\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"get_weather\",\n            \"description\": \"Récupère la météo actuelle pour une ville\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"city\": {\"type\": \"string\", \"description\": \"La ville\"}\n                },\n                \"required\": [\"city\"]\n            }\n        }\n    },\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"get_time\",\n            \"description\": \"Donne l'heure actuelle\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {},\n                \"required\": []\n            }\n        }\n    }\n]\n\nSYSTEM_PROMPT = f\"\"\"Tu es un assistant utile. Tu as accès aux outils suivants.\nQuand tu as besoin d'un outil, réponds UNIQUEMENT avec un bloc JSON comme ceci :\n{{\"tool_call\": {{\"name\": \"\u003Cnom_outil>\", \"arguments\": {{...}}}} }}\n\nOutils disponibles :\n{json.dumps(tools, indent=2, ensure_ascii=False)}\"\"\"\n\nuser_query = \"Quel temps fait-il à Tokyo ? Et quelle heure est-il ?\"\n\n# Construction du prompt avec le template de chat Qwen\n# Format: \u003C|im_start|>system\\n...\u003C|im_end|>\\n\u003C|im_start|>user\\n...\u003C|im_end|>\\n\u003C|im_start|>assistant\\n\nprompt = f\"\"\"\u003C|im_start|>system\n{SYSTEM_PROMPT}\u003C|im_end|>\n\u003C|im_start|>user\n{user_query}\u003C|im_end|>\n\u003C|im_start|>assistant\n\"\"\"\n",[39,848,849,856,860,865,875,879,889,896,907,918,925,936,943,971,976,987,992,997,1002,1006,1016,1022,1033,1044,1050,1060,1067,1074,1078,1082,1086,1090,1094,1108,1113,1137,1141,1147,1182,1187,1198,1203,1209,1215,1228,1237,1243,1255,1261],{"__ignoreMap":76},[169,850,851,853],{"class":171,"line":172},[169,852,757],{"class":750},[169,854,855],{"class":175}," json\n",[169,857,858],{"class":171,"line":179},[169,859,766],{"emptyLinePlaceholder":765},[169,861,862],{"class":171,"line":185},[169,863,864],{"class":818},"# Nos outils au format JSON Schema\n",[169,866,867,870,872],{"class":171,"line":202},[169,868,869],{"class":175},"tools ",[169,871,774],{"class":750},[169,873,874],{"class":175}," [\n",[169,876,877],{"class":171,"line":211},[169,878,553],{"class":175},[169,880,881,883,885,887],{"class":171,"line":224},[169,882,248],{"class":195},[169,884,192],{"class":175},[169,886,196],{"class":195},[169,888,199],{"class":175},[169,890,891,894],{"class":171,"line":237},[169,892,893],{"class":195},"        \"function\"",[169,895,208],{"class":175},[169,897,898,901,903,905],{"class":171,"line":245},[169,899,900],{"class":195},"            \"name\"",[169,902,192],{"class":175},[169,904,219],{"class":195},[169,906,199],{"class":175},[169,908,909,911,913,916],{"class":171,"line":258},[169,910,290],{"class":195},[169,912,192],{"class":175},[169,914,915],{"class":195},"\"Récupère la météo actuelle pour une ville\"",[169,917,199],{"class":175},[169,919,920,923],{"class":171,"line":266},[169,921,922],{"class":195},"            \"parameters\"",[169,924,208],{"class":175},[169,926,927,930,932,934],{"class":171,"line":274},[169,928,929],{"class":195},"                \"type\"",[169,931,192],{"class":175},[169,933,253],{"class":195},[169,935,199],{"class":175},[169,937,938,941],{"class":171,"line":287},[169,939,940],{"class":195},"                \"properties\"",[169,942,208],{"class":175},[169,944,945,948,951,954,956,958,961,964,966,969],{"class":171,"line":298},[169,946,947],{"class":195},"                    \"city\"",[169,949,950],{"class":175},": {",[169,952,953],{"class":195},"\"type\"",[169,955,192],{"class":175},[169,957,282],{"class":195},[169,959,960],{"class":175},", ",[169,962,963],{"class":195},"\"description\"",[169,965,192],{"class":175},[169,967,968],{"class":195},"\"La ville\"",[169,970,642],{"class":175},[169,972,973],{"class":171,"line":304},[169,974,975],{"class":175},"                },\n",[169,977,978,981,983,985],{"class":171,"line":310},[169,979,980],{"class":195},"                \"required\"",[169,982,316],{"class":175},[169,984,319],{"class":195},[169,986,322],{"class":175},[169,988,989],{"class":171,"line":325},[169,990,991],{"class":175},"            }\n",[169,993,994],{"class":171,"line":331},[169,995,996],{"class":175},"        }\n",[169,998,999],{"class":171,"line":337},[169,1000,1001],{"class":175},"    },\n",[169,1003,1004],{"class":171,"line":343},[169,1005,553],{"class":175},[169,1007,1008,1010,1012,1014],{"class":171,"line":348},[169,1009,248],{"class":195},[169,1011,192],{"class":175},[169,1013,196],{"class":195},[169,1015,199],{"class":175},[169,1017,1018,1020],{"class":171,"line":359},[169,1019,893],{"class":195},[169,1021,208],{"class":175},[169,1023,1024,1026,1028,1031],{"class":171,"line":366},[169,1025,900],{"class":195},[169,1027,192],{"class":175},[169,1029,1030],{"class":195},"\"get_time\"",[169,1032,199],{"class":175},[169,1034,1035,1037,1039,1042],{"class":171,"line":378},[169,1036,290],{"class":195},[169,1038,192],{"class":175},[169,1040,1041],{"class":195},"\"Donne l'heure actuelle\"",[169,1043,199],{"class":175},[169,1045,1046,1048],{"class":171,"line":390},[169,1047,922],{"class":195},[169,1049,208],{"class":175},[169,1051,1052,1054,1056,1058],{"class":171,"line":397},[169,1053,929],{"class":195},[169,1055,192],{"class":175},[169,1057,253],{"class":195},[169,1059,199],{"class":175},[169,1061,1062,1064],{"class":171,"line":408},[169,1063,940],{"class":195},[169,1065,1066],{"class":175},": {},\n",[169,1068,1069,1071],{"class":171,"line":415},[169,1070,980],{"class":195},[169,1072,1073],{"class":175},": []\n",[169,1075,1076],{"class":171,"line":423},[169,1077,991],{"class":175},[169,1079,1080],{"class":171,"line":434},[169,1081,996],{"class":175},[169,1083,1084],{"class":171,"line":444},[169,1085,334],{"class":175},[169,1087,1088],{"class":171,"line":449},[169,1089,322],{"class":175},[169,1091,1092],{"class":171,"line":454},[169,1093,766],{"emptyLinePlaceholder":765},[169,1095,1096,1099,1102,1105],{"class":171,"line":466},[169,1097,1098],{"class":188},"SYSTEM_PROMPT",[169,1100,1101],{"class":750}," =",[169,1103,1104],{"class":750}," f",[169,1106,1107],{"class":195},"\"\"\"Tu es un assistant utile. Tu as accès aux outils suivants.\n",[169,1109,1110],{"class":171,"line":471},[169,1111,1112],{"class":195},"Quand tu as besoin d'un outil, réponds UNIQUEMENT avec un bloc JSON comme ceci :\n",[169,1114,1115,1118,1121,1123,1126,1128,1131,1134],{"class":171,"line":476},[169,1116,1117],{"class":188},"{{",[169,1119,1120],{"class":195},"\"tool_call\": ",[169,1122,1117],{"class":188},[169,1124,1125],{"class":195},"\"name\": \"\u003Cnom_outil>\", \"arguments\": ",[169,1127,1117],{"class":188},[169,1129,1130],{"class":195},"...",[169,1132,1133],{"class":188},"}}}}",[169,1135,1136],{"class":188}," }}\n",[169,1138,1139],{"class":171,"line":482},[169,1140,766],{"emptyLinePlaceholder":765},[169,1142,1144],{"class":171,"line":1143},37,[169,1145,1146],{"class":195},"Outils disponibles :\n",[169,1148,1150,1153,1156,1159,1161,1164,1166,1169,1171,1173,1176,1179],{"class":171,"line":1149},38,[169,1151,1152],{"class":188},"{",[169,1154,1155],{"class":175},"json.dumps(tools, ",[169,1157,1158],{"class":782},"indent",[169,1160,774],{"class":750},[169,1162,1163],{"class":188},"2",[169,1165,960],{"class":175},[169,1167,1168],{"class":782},"ensure_ascii",[169,1170,774],{"class":750},[169,1172,829],{"class":188},[169,1174,1175],{"class":175},")",[169,1177,1178],{"class":188},"}",[169,1180,1181],{"class":195},"\"\"\"\n",[169,1183,1185],{"class":171,"line":1184},39,[169,1186,766],{"emptyLinePlaceholder":765},[169,1188,1190,1193,1195],{"class":171,"line":1189},40,[169,1191,1192],{"class":175},"user_query ",[169,1194,774],{"class":750},[169,1196,1197],{"class":195}," \"Quel temps fait-il à Tokyo ? Et quelle heure est-il ?\"\n",[169,1199,1201],{"class":171,"line":1200},41,[169,1202,766],{"emptyLinePlaceholder":765},[169,1204,1206],{"class":171,"line":1205},42,[169,1207,1208],{"class":818},"# Construction du prompt avec le template de chat Qwen\n",[169,1210,1212],{"class":171,"line":1211},43,[169,1213,1214],{"class":818},"# Format: \u003C|im_start|>system\\n...\u003C|im_end|>\\n\u003C|im_start|>user\\n...\u003C|im_end|>\\n\u003C|im_start|>assistant\\n\n",[169,1216,1218,1221,1223,1225],{"class":171,"line":1217},44,[169,1219,1220],{"class":175},"prompt ",[169,1222,774],{"class":750},[169,1224,1104],{"class":750},[169,1226,1227],{"class":195},"\"\"\"\u003C|im_start|>system\n",[169,1229,1231,1234],{"class":171,"line":1230},45,[169,1232,1233],{"class":188},"{SYSTEM_PROMPT}",[169,1235,1236],{"class":195},"\u003C|im_end|>\n",[169,1238,1240],{"class":171,"line":1239},46,[169,1241,1242],{"class":195},"\u003C|im_start|>user\n",[169,1244,1246,1248,1251,1253],{"class":171,"line":1245},47,[169,1247,1152],{"class":188},[169,1249,1250],{"class":175},"user_query",[169,1252,1178],{"class":188},[169,1254,1236],{"class":195},[169,1256,1258],{"class":171,"line":1257},48,[169,1259,1260],{"class":195},"\u003C|im_start|>assistant\n",[169,1262,1264],{"class":171,"line":1263},49,[169,1265,1181],{"class":195},[48,1267,1268],{},[15,1269,1270,1271,1274,1275,1278],{},"On utilise le format ",[39,1272,1273],{},"\u003C|im_start|>"," / ",[39,1276,1277],{},"\u003C|im_end|>"," qui est le template natif de Qwen. Pas de magie, juste des tokens spéciaux.",[150,1280,1282],{"id":1281},"étape-2-première-inférence-brute","Étape 2 : première inférence brute",[68,1284,1286],{"className":741,"code":1285,"language":743,"meta":76,"style":76},"# Inférence brute, sans outil automatique\noutput = llm(\n    prompt,\n    max_tokens=512,\n    temperature=0.1,   # basse température pour du JSON propre\n    stop=[\"\u003C|im_end|>\", \"\u003C|im_start|>\"],\n)\n\nraw_response = output[\"choices\"][0][\"text\"].strip()\nprint(f\"Réponse brute du modèle:\\n{raw_response}\")\n",[39,1287,1288,1293,1303,1308,1320,1336,1357,1361,1365,1392],{"__ignoreMap":76},[169,1289,1290],{"class":171,"line":172},[169,1291,1292],{"class":818},"# Inférence brute, sans outil automatique\n",[169,1294,1295,1298,1300],{"class":171,"line":179},[169,1296,1297],{"class":175},"output ",[169,1299,774],{"class":750},[169,1301,1302],{"class":175}," llm(\n",[169,1304,1305],{"class":171,"line":185},[169,1306,1307],{"class":175},"    prompt,\n",[169,1309,1310,1313,1315,1318],{"class":171,"line":202},[169,1311,1312],{"class":782},"    max_tokens",[169,1314,774],{"class":750},[169,1316,1317],{"class":188},"512",[169,1319,199],{"class":175},[169,1321,1322,1325,1327,1330,1333],{"class":171,"line":211},[169,1323,1324],{"class":782},"    temperature",[169,1326,774],{"class":750},[169,1328,1329],{"class":188},"0.1",[169,1331,1332],{"class":175},",   ",[169,1334,1335],{"class":818},"# basse température pour du JSON propre\n",[169,1337,1338,1341,1343,1346,1349,1351,1354],{"class":171,"line":224},[169,1339,1340],{"class":782},"    stop",[169,1342,774],{"class":750},[169,1344,1345],{"class":175},"[",[169,1347,1348],{"class":195},"\"\u003C|im_end|>\"",[169,1350,960],{"class":175},[169,1352,1353],{"class":195},"\"\u003C|im_start|>\"",[169,1355,1356],{"class":175},"],\n",[169,1358,1359],{"class":171,"line":237},[169,1360,836],{"class":175},[169,1362,1363],{"class":171,"line":245},[169,1364,766],{"emptyLinePlaceholder":765},[169,1366,1367,1370,1372,1375,1378,1381,1384,1386,1389],{"class":171,"line":258},[169,1368,1369],{"class":175},"raw_response ",[169,1371,774],{"class":750},[169,1373,1374],{"class":175}," output[",[169,1376,1377],{"class":195},"\"choices\"",[169,1379,1380],{"class":175},"][",[169,1382,1383],{"class":188},"0",[169,1385,1380],{"class":175},[169,1387,1388],{"class":195},"\"text\"",[169,1390,1391],{"class":175},"].strip()\n",[169,1393,1394,1397,1400,1403,1406,1409,1412,1414,1417],{"class":171,"line":266},[169,1395,1396],{"class":188},"print",[169,1398,1399],{"class":175},"(",[169,1401,1402],{"class":750},"f",[169,1404,1405],{"class":195},"\"Réponse brute du modèle:",[169,1407,1408],{"class":188},"\\n{",[169,1410,1411],{"class":175},"raw_response",[169,1413,1178],{"class":188},[169,1415,1416],{"class":195},"\"",[169,1418,836],{"class":175},[15,1420,1421],{},"Le modèle va produire quelque chose comme :",[68,1423,1425],{"className":163,"code":1424,"language":165,"meta":76,"style":76},"{\"tool_call\": {\"name\": \"get_weather\", \"arguments\": {\"city\": \"Tokyo\"}}}\n",[39,1426,1427],{"__ignoreMap":76},[169,1428,1429,1431,1434,1436,1439,1441,1443,1445,1448,1450,1452,1454,1457],{"class":171,"line":172},[169,1430,1152],{"class":175},[169,1432,1433],{"class":188},"\"tool_call\"",[169,1435,950],{"class":175},[169,1437,1438],{"class":188},"\"name\"",[169,1440,192],{"class":175},[169,1442,219],{"class":195},[169,1444,960],{"class":175},[169,1446,1447],{"class":188},"\"arguments\"",[169,1449,950],{"class":175},[169,1451,319],{"class":188},[169,1453,192],{"class":175},[169,1455,1456],{"class":195},"\"Tokyo\"",[169,1458,1459],{"class":175},"}}}\n",[48,1461,1462],{},[15,1463,1464],{},"Voilà, c'est ça le tool calling en barebone. Le modèle répond avec un JSON qu'on va parser nous-mêmes. Aucun framework ne fait ça pour nous, c'est juste du texte.",[150,1466,1468],{"id":1467},"étape-3-parser-le-tool-call-et-exécuter","Étape 3 : parser le tool call et exécuter",[68,1470,1472],{"className":741,"code":1471,"language":743,"meta":76,"style":76},"import re\nfrom datetime import datetime\n\ndef parse_tool_call(text: str) -> dict | None:\n    \"\"\"Extrait un appel d'outil depuis la réponse JSON du modèle.\"\"\"\n    try:\n        # Essaie de parser du JSON direct\n        data = json.loads(text)\n        if \"tool_call\" in data:\n            return data[\"tool_call\"]\n    except json.JSONDecodeError:\n        pass\n    \n    # Fallback: cherche un bloc JSON avec regex\n    match = re.search(r'\\{.*\"tool_call\".*\\}', text, re.DOTALL)\n    if match:\n        try:\n            data = json.loads(match.group())\n            if \"tool_call\" in data:\n                return data[\"tool_call\"]\n        except json.JSONDecodeError:\n            pass\n    \n    return None\n\ndef execute_tool(name: str, arguments: dict) -> str:\n    \"\"\"Exécute l'outil demandé et retourne le résultat.\"\"\"\n    if name == \"get_weather\":\n        city = arguments.get(\"city\", \"inconnue\")\n        # Dans la vraie vie, appel d'API météo ici\n        return f\"Météo à {city}: 22°C, ensoleillé, humidité 40%\"\n    \n    elif name == \"get_time\":\n        return f\"Heure actuelle: {datetime.now().strftime('%H:%M:%S')}\"\n    \n    return f\"Erreur: outil '{name}' inconnu.\"\n\n# Parse et exécute\ntool_call = parse_tool_call(raw_response)\n\nif tool_call:\n    print(f\"Tool appelé: {tool_call['name']}({tool_call['arguments']})\")\n    result = execute_tool(tool_call[\"name\"], tool_call[\"arguments\"])\n    print(f\"Résultat: {result}\")\n",[39,1473,1474,1481,1493,1497,1527,1532,1539,1544,1554,1568,1580,1588,1593,1598,1603,1649,1657,1664,1674,1685,1696,1703,1708,1712,1720,1724,1747,1752,1767,1786,1791,1810,1814,1828,1852,1856,1875,1879,1884,1894,1898,1906,1949,1969],{"__ignoreMap":76},[169,1475,1476,1478],{"class":171,"line":172},[169,1477,757],{"class":750},[169,1479,1480],{"class":175}," re\n",[169,1482,1483,1485,1488,1490],{"class":171,"line":179},[169,1484,751],{"class":750},[169,1486,1487],{"class":175}," datetime ",[169,1489,757],{"class":750},[169,1491,1492],{"class":175}," datetime\n",[169,1494,1495],{"class":171,"line":185},[169,1496,766],{"emptyLinePlaceholder":765},[169,1498,1499,1502,1506,1509,1512,1515,1518,1521,1524],{"class":171,"line":202},[169,1500,1501],{"class":750},"def",[169,1503,1505],{"class":1504},"svObZ"," parse_tool_call",[169,1507,1508],{"class":175},"(text: ",[169,1510,1511],{"class":188},"str",[169,1513,1514],{"class":175},") -> ",[169,1516,1517],{"class":188},"dict",[169,1519,1520],{"class":750}," |",[169,1522,1523],{"class":188}," None",[169,1525,1526],{"class":175},":\n",[169,1528,1529],{"class":171,"line":211},[169,1530,1531],{"class":195},"    \"\"\"Extrait un appel d'outil depuis la réponse JSON du modèle.\"\"\"\n",[169,1533,1534,1537],{"class":171,"line":224},[169,1535,1536],{"class":750},"    try",[169,1538,1526],{"class":175},[169,1540,1541],{"class":171,"line":237},[169,1542,1543],{"class":818},"        # Essaie de parser du JSON direct\n",[169,1545,1546,1549,1551],{"class":171,"line":245},[169,1547,1548],{"class":175},"        data ",[169,1550,774],{"class":750},[169,1552,1553],{"class":175}," json.loads(text)\n",[169,1555,1556,1559,1562,1565],{"class":171,"line":258},[169,1557,1558],{"class":750},"        if",[169,1560,1561],{"class":195}," \"tool_call\"",[169,1563,1564],{"class":750}," in",[169,1566,1567],{"class":175}," data:\n",[169,1569,1570,1573,1576,1578],{"class":171,"line":266},[169,1571,1572],{"class":750},"            return",[169,1574,1575],{"class":175}," data[",[169,1577,1433],{"class":195},[169,1579,322],{"class":175},[169,1581,1582,1585],{"class":171,"line":274},[169,1583,1584],{"class":750},"    except",[169,1586,1587],{"class":175}," json.JSONDecodeError:\n",[169,1589,1590],{"class":171,"line":287},[169,1591,1592],{"class":750},"        pass\n",[169,1594,1595],{"class":171,"line":298},[169,1596,1597],{"class":175},"    \n",[169,1599,1600],{"class":171,"line":304},[169,1601,1602],{"class":818},"    # Fallback: cherche un bloc JSON avec regex\n",[169,1604,1605,1608,1610,1613,1616,1619,1623,1626,1629,1632,1634,1636,1639,1641,1644,1647],{"class":171,"line":310},[169,1606,1607],{"class":175},"    match ",[169,1609,774],{"class":750},[169,1611,1612],{"class":175}," re.search(",[169,1614,1615],{"class":750},"r",[169,1617,1618],{"class":195},"'",[169,1620,1622],{"class":1621},"sRjNt","\\{",[169,1624,1625],{"class":188},".",[169,1627,1628],{"class":750},"*",[169,1630,1433],{"class":1631},"sns5M",[169,1633,1625],{"class":188},[169,1635,1628],{"class":750},[169,1637,1638],{"class":1621},"\\}",[169,1640,1618],{"class":195},[169,1642,1643],{"class":175},", text, re.",[169,1645,1646],{"class":188},"DOTALL",[169,1648,836],{"class":175},[169,1650,1651,1654],{"class":171,"line":325},[169,1652,1653],{"class":750},"    if",[169,1655,1656],{"class":175}," match:\n",[169,1658,1659,1662],{"class":171,"line":331},[169,1660,1661],{"class":750},"        try",[169,1663,1526],{"class":175},[169,1665,1666,1669,1671],{"class":171,"line":337},[169,1667,1668],{"class":175},"            data ",[169,1670,774],{"class":750},[169,1672,1673],{"class":175}," json.loads(match.group())\n",[169,1675,1676,1679,1681,1683],{"class":171,"line":343},[169,1677,1678],{"class":750},"            if",[169,1680,1561],{"class":195},[169,1682,1564],{"class":750},[169,1684,1567],{"class":175},[169,1686,1687,1690,1692,1694],{"class":171,"line":348},[169,1688,1689],{"class":750},"                return",[169,1691,1575],{"class":175},[169,1693,1433],{"class":195},[169,1695,322],{"class":175},[169,1697,1698,1701],{"class":171,"line":359},[169,1699,1700],{"class":750},"        except",[169,1702,1587],{"class":175},[169,1704,1705],{"class":171,"line":366},[169,1706,1707],{"class":750},"            pass\n",[169,1709,1710],{"class":171,"line":378},[169,1711,1597],{"class":175},[169,1713,1714,1717],{"class":171,"line":390},[169,1715,1716],{"class":750},"    return",[169,1718,1719],{"class":188}," None\n",[169,1721,1722],{"class":171,"line":397},[169,1723,766],{"emptyLinePlaceholder":765},[169,1725,1726,1728,1731,1734,1736,1739,1741,1743,1745],{"class":171,"line":408},[169,1727,1501],{"class":750},[169,1729,1730],{"class":1504}," execute_tool",[169,1732,1733],{"class":175},"(name: ",[169,1735,1511],{"class":188},[169,1737,1738],{"class":175},", arguments: ",[169,1740,1517],{"class":188},[169,1742,1514],{"class":175},[169,1744,1511],{"class":188},[169,1746,1526],{"class":175},[169,1748,1749],{"class":171,"line":415},[169,1750,1751],{"class":195},"    \"\"\"Exécute l'outil demandé et retourne le résultat.\"\"\"\n",[169,1753,1754,1756,1759,1762,1765],{"class":171,"line":423},[169,1755,1653],{"class":750},[169,1757,1758],{"class":175}," name ",[169,1760,1761],{"class":750},"==",[169,1763,1764],{"class":195}," \"get_weather\"",[169,1766,1526],{"class":175},[169,1768,1769,1772,1774,1777,1779,1781,1784],{"class":171,"line":434},[169,1770,1771],{"class":175},"        city ",[169,1773,774],{"class":750},[169,1775,1776],{"class":175}," arguments.get(",[169,1778,319],{"class":195},[169,1780,960],{"class":175},[169,1782,1783],{"class":195},"\"inconnue\"",[169,1785,836],{"class":175},[169,1787,1788],{"class":171,"line":444},[169,1789,1790],{"class":818},"        # Dans la vraie vie, appel d'API météo ici\n",[169,1792,1793,1796,1798,1801,1803,1805,1807],{"class":171,"line":449},[169,1794,1795],{"class":750},"        return",[169,1797,1104],{"class":750},[169,1799,1800],{"class":195},"\"Météo à ",[169,1802,1152],{"class":188},[169,1804,610],{"class":175},[169,1806,1178],{"class":188},[169,1808,1809],{"class":195},": 22°C, ensoleillé, humidité 40%\"\n",[169,1811,1812],{"class":171,"line":454},[169,1813,1597],{"class":175},[169,1815,1816,1819,1821,1823,1826],{"class":171,"line":466},[169,1817,1818],{"class":750},"    elif",[169,1820,1758],{"class":175},[169,1822,1761],{"class":750},[169,1824,1825],{"class":195}," \"get_time\"",[169,1827,1526],{"class":175},[169,1829,1830,1832,1834,1837,1839,1842,1845,1847,1849],{"class":171,"line":471},[169,1831,1795],{"class":750},[169,1833,1104],{"class":750},[169,1835,1836],{"class":195},"\"Heure actuelle: ",[169,1838,1152],{"class":188},[169,1840,1841],{"class":175},"datetime.now().strftime(",[169,1843,1844],{"class":195},"'%H:%M:%S'",[169,1846,1175],{"class":175},[169,1848,1178],{"class":188},[169,1850,1851],{"class":195},"\"\n",[169,1853,1854],{"class":171,"line":476},[169,1855,1597],{"class":175},[169,1857,1858,1860,1862,1865,1867,1870,1872],{"class":171,"line":482},[169,1859,1716],{"class":750},[169,1861,1104],{"class":750},[169,1863,1864],{"class":195},"\"Erreur: outil '",[169,1866,1152],{"class":188},[169,1868,1869],{"class":175},"name",[169,1871,1178],{"class":188},[169,1873,1874],{"class":195},"' inconnu.\"\n",[169,1876,1877],{"class":171,"line":1143},[169,1878,766],{"emptyLinePlaceholder":765},[169,1880,1881],{"class":171,"line":1149},[169,1882,1883],{"class":818},"# Parse et exécute\n",[169,1885,1886,1889,1891],{"class":171,"line":1184},[169,1887,1888],{"class":175},"tool_call ",[169,1890,774],{"class":750},[169,1892,1893],{"class":175}," parse_tool_call(raw_response)\n",[169,1895,1896],{"class":171,"line":1189},[169,1897,766],{"emptyLinePlaceholder":765},[169,1899,1900,1903],{"class":171,"line":1200},[169,1901,1902],{"class":750},"if",[169,1904,1905],{"class":175}," tool_call:\n",[169,1907,1908,1911,1913,1915,1918,1920,1923,1926,1929,1931,1933,1935,1937,1940,1942,1944,1947],{"class":171,"line":1205},[169,1909,1910],{"class":188},"    print",[169,1912,1399],{"class":175},[169,1914,1402],{"class":750},[169,1916,1917],{"class":195},"\"Tool appelé: ",[169,1919,1152],{"class":188},[169,1921,1922],{"class":175},"tool_call[",[169,1924,1925],{"class":195},"'name'",[169,1927,1928],{"class":175},"]",[169,1930,1178],{"class":188},[169,1932,1399],{"class":195},[169,1934,1152],{"class":188},[169,1936,1922],{"class":175},[169,1938,1939],{"class":195},"'arguments'",[169,1941,1928],{"class":175},[169,1943,1178],{"class":188},[169,1945,1946],{"class":195},")\"",[169,1948,836],{"class":175},[169,1950,1951,1954,1956,1959,1961,1964,1966],{"class":171,"line":1211},[169,1952,1953],{"class":175},"    result ",[169,1955,774],{"class":750},[169,1957,1958],{"class":175}," execute_tool(tool_call[",[169,1960,1438],{"class":195},[169,1962,1963],{"class":175},"], tool_call[",[169,1965,1447],{"class":195},[169,1967,1968],{"class":175},"])\n",[169,1970,1971,1973,1975,1977,1980,1982,1985,1987,1989],{"class":171,"line":1217},[169,1972,1910],{"class":188},[169,1974,1399],{"class":175},[169,1976,1402],{"class":750},[169,1978,1979],{"class":195},"\"Résultat: ",[169,1981,1152],{"class":188},[169,1983,1984],{"class":175},"result",[169,1986,1178],{"class":188},[169,1988,1416],{"class":195},[169,1990,836],{"class":175},[48,1992,1993],{},[15,1994,1995,1996,1999],{},"Le parsing peut être fragile sur les petits modèles. Le JSON est parfois mal formé ou tronqué, d'où le fallback avec regex. En prod, on utiliserait le ",[24,1997,1998],{},"grammar sampling"," de llama.cpp pour forcer un JSON valide, mais on verra ça une autre fois.",[150,2001,2003],{"id":2002},"étape-4-renvoyer-le-résultat-pour-la-réponse-finale","Étape 4 : renvoyer le résultat pour la réponse finale",[68,2005,2007],{"className":741,"code":2006,"language":743,"meta":76,"style":76},"# On reconstruit le prompt avec l'historique complet\nprompt += raw_response + \"\u003C|im_end|>\\n\"\nprompt += f\"\u003C|im_start|>tool\\n{result}\u003C|im_end|>\\n\"\nprompt += \"\u003C|im_start|>assistant\\n\"\n\n# Deuxième inférence : le modèle synthétise le résultat\nfinal_output = llm(\n    prompt,\n    max_tokens=512,\n    temperature=0.7,\n    stop=[\"\u003C|im_end|>\", \"\u003C|im_start|>\"],\n)\n\nfinal_response = final_output[\"choices\"][0][\"text\"].strip()\nprint(f\"Réponse finale:\\n{final_response}\")\n",[39,2008,2009,2014,2035,2058,2071,2075,2080,2089,2093,2103,2114,2130,2134,2138,2160],{"__ignoreMap":76},[169,2010,2011],{"class":171,"line":172},[169,2012,2013],{"class":818},"# On reconstruit le prompt avec l'historique complet\n",[169,2015,2016,2018,2021,2024,2027,2030,2033],{"class":171,"line":179},[169,2017,1220],{"class":175},[169,2019,2020],{"class":750},"+=",[169,2022,2023],{"class":175}," raw_response ",[169,2025,2026],{"class":750},"+",[169,2028,2029],{"class":195}," \"\u003C|im_end|>",[169,2031,2032],{"class":188},"\\n",[169,2034,1851],{"class":195},[169,2036,2037,2039,2041,2043,2046,2048,2050,2052,2054,2056],{"class":171,"line":185},[169,2038,1220],{"class":175},[169,2040,2020],{"class":750},[169,2042,1104],{"class":750},[169,2044,2045],{"class":195},"\"\u003C|im_start|>tool",[169,2047,1408],{"class":188},[169,2049,1984],{"class":175},[169,2051,1178],{"class":188},[169,2053,1277],{"class":195},[169,2055,2032],{"class":188},[169,2057,1851],{"class":195},[169,2059,2060,2062,2064,2067,2069],{"class":171,"line":202},[169,2061,1220],{"class":175},[169,2063,2020],{"class":750},[169,2065,2066],{"class":195}," \"\u003C|im_start|>assistant",[169,2068,2032],{"class":188},[169,2070,1851],{"class":195},[169,2072,2073],{"class":171,"line":211},[169,2074,766],{"emptyLinePlaceholder":765},[169,2076,2077],{"class":171,"line":224},[169,2078,2079],{"class":818},"# Deuxième inférence : le modèle synthétise le résultat\n",[169,2081,2082,2085,2087],{"class":171,"line":237},[169,2083,2084],{"class":175},"final_output ",[169,2086,774],{"class":750},[169,2088,1302],{"class":175},[169,2090,2091],{"class":171,"line":245},[169,2092,1307],{"class":175},[169,2094,2095,2097,2099,2101],{"class":171,"line":258},[169,2096,1312],{"class":782},[169,2098,774],{"class":750},[169,2100,1317],{"class":188},[169,2102,199],{"class":175},[169,2104,2105,2107,2109,2112],{"class":171,"line":266},[169,2106,1324],{"class":782},[169,2108,774],{"class":750},[169,2110,2111],{"class":188},"0.7",[169,2113,199],{"class":175},[169,2115,2116,2118,2120,2122,2124,2126,2128],{"class":171,"line":274},[169,2117,1340],{"class":782},[169,2119,774],{"class":750},[169,2121,1345],{"class":175},[169,2123,1348],{"class":195},[169,2125,960],{"class":175},[169,2127,1353],{"class":195},[169,2129,1356],{"class":175},[169,2131,2132],{"class":171,"line":287},[169,2133,836],{"class":175},[169,2135,2136],{"class":171,"line":298},[169,2137,766],{"emptyLinePlaceholder":765},[169,2139,2140,2143,2145,2148,2150,2152,2154,2156,2158],{"class":171,"line":304},[169,2141,2142],{"class":175},"final_response ",[169,2144,774],{"class":750},[169,2146,2147],{"class":175}," final_output[",[169,2149,1377],{"class":195},[169,2151,1380],{"class":175},[169,2153,1383],{"class":188},[169,2155,1380],{"class":175},[169,2157,1388],{"class":195},[169,2159,1391],{"class":175},[169,2161,2162,2164,2166,2168,2171,2173,2176,2178,2180],{"class":171,"line":310},[169,2163,1396],{"class":188},[169,2165,1399],{"class":175},[169,2167,1402],{"class":750},[169,2169,2170],{"class":195},"\"Réponse finale:",[169,2172,1408],{"class":188},[169,2174,2175],{"class":175},"final_response",[169,2177,1178],{"class":188},[169,2179,1416],{"class":195},[169,2181,836],{"class":175},[15,2183,2184],{},"Et là, le modèle répond naturellement :",[48,2186,2187],{},[15,2188,2189],{},"\"À Tokyo, il fait 22°C avec un temps ensoleillé et 40% d'humidité. L'heure actuelle est 14:32:15.\"",[48,2191,2192],{},[15,2193,2194],{},"Et voilà. Zéro framework, zéro magie. Juste du prompt engineering, un appel d'inférence, du parsing JSON, et un deuxième appel d'inférence. C'est tout ce que font les \"gros\" frameworks comme LangChain derrière leurs abstractions.",[10,2196,2198],{"id":2197},"de-là-à-lagent-il-ny-a-quun-pas","De là à l'agent, il n'y a qu'un pas",[15,2200,2201,2202,2205,2206,2209,2210,2213],{},"Un ",[19,2203,2204],{},"agent",", c'est fondamentalement ce qu'on vient de faire, mais dans une ",[19,2207,2208],{},"boucle"," plus large. Le LLM ne se contente pas de répondre à une question, il poursuit un ",[19,2211,2212],{},"objectif"," sur plusieurs étapes, en décidant à chaque tour s'il a besoin d'un outil ou s'il peut répondre.",[150,2215,2217],{"id":2216},"la-boucle-agent","La boucle agent",[2219,2220],"mermaid-diagram",{"code":2221},"flowchart TD\n    A[🎯 Objectif utilisateur] --> B[🤖 Le LLM réfléchit]\n    B --> C{Outil nécessaire ?}\n    C -->|Oui| D[📞 Émission d'un tool_call]\n    D --> E[⚙️ Exécution de l'outil]\n    E --> F[📥 Résultat injecté dans le contexte]\n    F --> B\n    C -->|Non| G[💬 Réponse finale à l'utilisateur]\n",[150,2223,2225],{"id":2224},"implémentation-barebone-dune-boucle-agent","Implémentation barebone d'une boucle agent",[15,2227,2228],{},"Reprenons notre code d'inférence brute et emballons-le dans une boucle :",[68,2230,2232],{"className":741,"code":2231,"language":743,"meta":76,"style":76},"def agent_loop(user_query: str, tools: list, max_steps: int = 10) -> str:\n    \"\"\"\n    Boucle agent barebone.\n    Le LLM peut appeler des outils en boucle jusqu'à ce qu'il\n    décide de répondre directement à l'utilisateur.\n    \"\"\"\n    \n    system_prompt = f\"\"\"Tu es un agent autonome. Tu as accès aux outils suivants.\nPour appeler un outil, réponds UNIQUEMENT avec :\n{{\"tool_call\": {{\"name\": \"\u003Cnom>\", \"arguments\": {{...}} }} }}\nQuand tu as terminé ta mission, réponds directement à l'utilisateur SANS JSON.\n\nOutils disponibles :\n{json.dumps(tools, indent=2, ensure_ascii=False)}\"\"\"\n    \n    # Initialisation du prompt avec le template Qwen\n    prompt = f\"\u003C|im_start|>system\\n{system_prompt}\u003C|im_end|>\\n\"\n    prompt += f\"\u003C|im_start|>user\\n{user_query}\u003C|im_end|>\\n\"\n    prompt += \"\u003C|im_start|>assistant\\n\"\n    \n    step_count = 0\n    \n    while step_count \u003C max_steps:\n        step_count += 1\n        \n        # Inférence\n        output = llm(\n            prompt,\n            max_tokens=512,\n            temperature=0.1,\n            stop=[\"\u003C|im_end|>\", \"\u003C|im_start|>\"],\n        )\n        \n        response_text = output[\"choices\"][0][\"text\"].strip()\n        \n        # On essaie de parser un tool_call\n        tool_call = parse_tool_call(response_text)\n        \n        if tool_call is None:\n            # Pas d'outil demandé, l'agent a fini sa mission\n            print(f\"[Agent] Mission accomplie en {step_count} étape(s).\")\n            return response_text\n        \n        # Exécution de l'outil\n        tool_name = tool_call[\"name\"]\n        tool_args = tool_call[\"arguments\"]\n        \n        print(f\"[Step {step_count}] Appel de {tool_name}({tool_args})\")\n        tool_result = execute_tool(tool_name, tool_args)\n        \n        # On ajoute à l'historique et on continue\n        prompt += response_text + \"\u003C|im_end|>\\n\"\n        prompt += f\"\u003C|im_start|>tool\\n{tool_result}\u003C|im_end|>\\n\"\n        prompt += \"\u003C|im_start|>assistant\\n\"\n    \n    return \"[Erreur] Agent bloqué, max steps atteint.\"\n",[39,2233,2234,2269,2274,2279,2284,2289,2293,2297,2309,2314,2337,2342,2346,2350,2376,2380,2385,2410,2433,2445,2449,2459,2463,2477,2487,2492,2497,2506,2511,2522,2533,2550,2555,2559,2580,2584,2589,2599,2603,2617,2622,2646,2653,2657,2662,2676,2689,2693,2734,2744,2749,2755,2774,2798,2811,2816],{"__ignoreMap":76},[169,2235,2236,2238,2241,2244,2246,2249,2252,2255,2258,2260,2263,2265,2267],{"class":171,"line":172},[169,2237,1501],{"class":750},[169,2239,2240],{"class":1504}," agent_loop",[169,2242,2243],{"class":175},"(user_query: ",[169,2245,1511],{"class":188},[169,2247,2248],{"class":175},", tools: ",[169,2250,2251],{"class":188},"list",[169,2253,2254],{"class":175},", max_steps: ",[169,2256,2257],{"class":188},"int",[169,2259,1101],{"class":750},[169,2261,2262],{"class":188}," 10",[169,2264,1514],{"class":175},[169,2266,1511],{"class":188},[169,2268,1526],{"class":175},[169,2270,2271],{"class":171,"line":179},[169,2272,2273],{"class":195},"    \"\"\"\n",[169,2275,2276],{"class":171,"line":185},[169,2277,2278],{"class":195},"    Boucle agent barebone.\n",[169,2280,2281],{"class":171,"line":202},[169,2282,2283],{"class":195},"    Le LLM peut appeler des outils en boucle jusqu'à ce qu'il\n",[169,2285,2286],{"class":171,"line":211},[169,2287,2288],{"class":195},"    décide de répondre directement à l'utilisateur.\n",[169,2290,2291],{"class":171,"line":224},[169,2292,2273],{"class":195},[169,2294,2295],{"class":171,"line":237},[169,2296,1597],{"class":175},[169,2298,2299,2302,2304,2306],{"class":171,"line":245},[169,2300,2301],{"class":175},"    system_prompt ",[169,2303,774],{"class":750},[169,2305,1104],{"class":750},[169,2307,2308],{"class":195},"\"\"\"Tu es un agent autonome. Tu as accès aux outils suivants.\n",[169,2310,2311],{"class":171,"line":258},[169,2312,2313],{"class":195},"Pour appeler un outil, réponds UNIQUEMENT avec :\n",[169,2315,2316,2318,2320,2322,2325,2327,2329,2332,2335],{"class":171,"line":266},[169,2317,1117],{"class":188},[169,2319,1120],{"class":195},[169,2321,1117],{"class":188},[169,2323,2324],{"class":195},"\"name\": \"\u003Cnom>\", \"arguments\": ",[169,2326,1117],{"class":188},[169,2328,1130],{"class":195},[169,2330,2331],{"class":188},"}}",[169,2333,2334],{"class":188}," }}",[169,2336,1136],{"class":188},[169,2338,2339],{"class":171,"line":274},[169,2340,2341],{"class":195},"Quand tu as terminé ta mission, réponds directement à l'utilisateur SANS JSON.\n",[169,2343,2344],{"class":171,"line":287},[169,2345,766],{"emptyLinePlaceholder":765},[169,2347,2348],{"class":171,"line":298},[169,2349,1146],{"class":195},[169,2351,2352,2354,2356,2358,2360,2362,2364,2366,2368,2370,2372,2374],{"class":171,"line":304},[169,2353,1152],{"class":188},[169,2355,1155],{"class":175},[169,2357,1158],{"class":782},[169,2359,774],{"class":750},[169,2361,1163],{"class":188},[169,2363,960],{"class":175},[169,2365,1168],{"class":782},[169,2367,774],{"class":750},[169,2369,829],{"class":188},[169,2371,1175],{"class":175},[169,2373,1178],{"class":188},[169,2375,1181],{"class":195},[169,2377,2378],{"class":171,"line":310},[169,2379,1597],{"class":175},[169,2381,2382],{"class":171,"line":325},[169,2383,2384],{"class":818},"    # Initialisation du prompt avec le template Qwen\n",[169,2386,2387,2390,2392,2394,2397,2399,2402,2404,2406,2408],{"class":171,"line":331},[169,2388,2389],{"class":175},"    prompt ",[169,2391,774],{"class":750},[169,2393,1104],{"class":750},[169,2395,2396],{"class":195},"\"\u003C|im_start|>system",[169,2398,1408],{"class":188},[169,2400,2401],{"class":175},"system_prompt",[169,2403,1178],{"class":188},[169,2405,1277],{"class":195},[169,2407,2032],{"class":188},[169,2409,1851],{"class":195},[169,2411,2412,2414,2416,2418,2421,2423,2425,2427,2429,2431],{"class":171,"line":337},[169,2413,2389],{"class":175},[169,2415,2020],{"class":750},[169,2417,1104],{"class":750},[169,2419,2420],{"class":195},"\"\u003C|im_start|>user",[169,2422,1408],{"class":188},[169,2424,1250],{"class":175},[169,2426,1178],{"class":188},[169,2428,1277],{"class":195},[169,2430,2032],{"class":188},[169,2432,1851],{"class":195},[169,2434,2435,2437,2439,2441,2443],{"class":171,"line":343},[169,2436,2389],{"class":175},[169,2438,2020],{"class":750},[169,2440,2066],{"class":195},[169,2442,2032],{"class":188},[169,2444,1851],{"class":195},[169,2446,2447],{"class":171,"line":348},[169,2448,1597],{"class":175},[169,2450,2451,2454,2456],{"class":171,"line":359},[169,2452,2453],{"class":175},"    step_count ",[169,2455,774],{"class":750},[169,2457,2458],{"class":188}," 0\n",[169,2460,2461],{"class":171,"line":366},[169,2462,1597],{"class":175},[169,2464,2465,2468,2471,2474],{"class":171,"line":378},[169,2466,2467],{"class":750},"    while",[169,2469,2470],{"class":175}," step_count ",[169,2472,2473],{"class":750},"\u003C",[169,2475,2476],{"class":175}," max_steps:\n",[169,2478,2479,2482,2484],{"class":171,"line":390},[169,2480,2481],{"class":175},"        step_count ",[169,2483,2020],{"class":750},[169,2485,2486],{"class":188}," 1\n",[169,2488,2489],{"class":171,"line":397},[169,2490,2491],{"class":175},"        \n",[169,2493,2494],{"class":171,"line":408},[169,2495,2496],{"class":818},"        # Inférence\n",[169,2498,2499,2502,2504],{"class":171,"line":415},[169,2500,2501],{"class":175},"        output ",[169,2503,774],{"class":750},[169,2505,1302],{"class":175},[169,2507,2508],{"class":171,"line":423},[169,2509,2510],{"class":175},"            prompt,\n",[169,2512,2513,2516,2518,2520],{"class":171,"line":434},[169,2514,2515],{"class":782},"            max_tokens",[169,2517,774],{"class":750},[169,2519,1317],{"class":188},[169,2521,199],{"class":175},[169,2523,2524,2527,2529,2531],{"class":171,"line":444},[169,2525,2526],{"class":782},"            temperature",[169,2528,774],{"class":750},[169,2530,1329],{"class":188},[169,2532,199],{"class":175},[169,2534,2535,2538,2540,2542,2544,2546,2548],{"class":171,"line":449},[169,2536,2537],{"class":782},"            stop",[169,2539,774],{"class":750},[169,2541,1345],{"class":175},[169,2543,1348],{"class":195},[169,2545,960],{"class":175},[169,2547,1353],{"class":195},[169,2549,1356],{"class":175},[169,2551,2552],{"class":171,"line":454},[169,2553,2554],{"class":175},"        )\n",[169,2556,2557],{"class":171,"line":466},[169,2558,2491],{"class":175},[169,2560,2561,2564,2566,2568,2570,2572,2574,2576,2578],{"class":171,"line":471},[169,2562,2563],{"class":175},"        response_text ",[169,2565,774],{"class":750},[169,2567,1374],{"class":175},[169,2569,1377],{"class":195},[169,2571,1380],{"class":175},[169,2573,1383],{"class":188},[169,2575,1380],{"class":175},[169,2577,1388],{"class":195},[169,2579,1391],{"class":175},[169,2581,2582],{"class":171,"line":476},[169,2583,2491],{"class":175},[169,2585,2586],{"class":171,"line":482},[169,2587,2588],{"class":818},"        # On essaie de parser un tool_call\n",[169,2590,2591,2594,2596],{"class":171,"line":1143},[169,2592,2593],{"class":175},"        tool_call ",[169,2595,774],{"class":750},[169,2597,2598],{"class":175}," parse_tool_call(response_text)\n",[169,2600,2601],{"class":171,"line":1149},[169,2602,2491],{"class":175},[169,2604,2605,2607,2610,2613,2615],{"class":171,"line":1184},[169,2606,1558],{"class":750},[169,2608,2609],{"class":175}," tool_call ",[169,2611,2612],{"class":750},"is",[169,2614,1523],{"class":188},[169,2616,1526],{"class":175},[169,2618,2619],{"class":171,"line":1189},[169,2620,2621],{"class":818},"            # Pas d'outil demandé, l'agent a fini sa mission\n",[169,2623,2624,2627,2629,2631,2634,2636,2639,2641,2644],{"class":171,"line":1200},[169,2625,2626],{"class":188},"            print",[169,2628,1399],{"class":175},[169,2630,1402],{"class":750},[169,2632,2633],{"class":195},"\"[Agent] Mission accomplie en ",[169,2635,1152],{"class":188},[169,2637,2638],{"class":175},"step_count",[169,2640,1178],{"class":188},[169,2642,2643],{"class":195}," étape(s).\"",[169,2645,836],{"class":175},[169,2647,2648,2650],{"class":171,"line":1205},[169,2649,1572],{"class":750},[169,2651,2652],{"class":175}," response_text\n",[169,2654,2655],{"class":171,"line":1211},[169,2656,2491],{"class":175},[169,2658,2659],{"class":171,"line":1217},[169,2660,2661],{"class":818},"        # Exécution de l'outil\n",[169,2663,2664,2667,2669,2672,2674],{"class":171,"line":1230},[169,2665,2666],{"class":175},"        tool_name ",[169,2668,774],{"class":750},[169,2670,2671],{"class":175}," tool_call[",[169,2673,1438],{"class":195},[169,2675,322],{"class":175},[169,2677,2678,2681,2683,2685,2687],{"class":171,"line":1239},[169,2679,2680],{"class":175},"        tool_args ",[169,2682,774],{"class":750},[169,2684,2671],{"class":175},[169,2686,1447],{"class":195},[169,2688,322],{"class":175},[169,2690,2691],{"class":171,"line":1245},[169,2692,2491],{"class":175},[169,2694,2695,2698,2700,2702,2705,2707,2709,2711,2714,2716,2719,2721,2723,2725,2728,2730,2732],{"class":171,"line":1257},[169,2696,2697],{"class":188},"        print",[169,2699,1399],{"class":175},[169,2701,1402],{"class":750},[169,2703,2704],{"class":195},"\"[Step ",[169,2706,1152],{"class":188},[169,2708,2638],{"class":175},[169,2710,1178],{"class":188},[169,2712,2713],{"class":195},"] Appel de ",[169,2715,1152],{"class":188},[169,2717,2718],{"class":175},"tool_name",[169,2720,1178],{"class":188},[169,2722,1399],{"class":195},[169,2724,1152],{"class":188},[169,2726,2727],{"class":175},"tool_args",[169,2729,1178],{"class":188},[169,2731,1946],{"class":195},[169,2733,836],{"class":175},[169,2735,2736,2739,2741],{"class":171,"line":1263},[169,2737,2738],{"class":175},"        tool_result ",[169,2740,774],{"class":750},[169,2742,2743],{"class":175}," execute_tool(tool_name, tool_args)\n",[169,2745,2747],{"class":171,"line":2746},50,[169,2748,2491],{"class":175},[169,2750,2752],{"class":171,"line":2751},51,[169,2753,2754],{"class":818},"        # On ajoute à l'historique et on continue\n",[169,2756,2758,2761,2763,2766,2768,2770,2772],{"class":171,"line":2757},52,[169,2759,2760],{"class":175},"        prompt ",[169,2762,2020],{"class":750},[169,2764,2765],{"class":175}," response_text ",[169,2767,2026],{"class":750},[169,2769,2029],{"class":195},[169,2771,2032],{"class":188},[169,2773,1851],{"class":195},[169,2775,2777,2779,2781,2783,2785,2787,2790,2792,2794,2796],{"class":171,"line":2776},53,[169,2778,2760],{"class":175},[169,2780,2020],{"class":750},[169,2782,1104],{"class":750},[169,2784,2045],{"class":195},[169,2786,1408],{"class":188},[169,2788,2789],{"class":175},"tool_result",[169,2791,1178],{"class":188},[169,2793,1277],{"class":195},[169,2795,2032],{"class":188},[169,2797,1851],{"class":195},[169,2799,2801,2803,2805,2807,2809],{"class":171,"line":2800},54,[169,2802,2760],{"class":175},[169,2804,2020],{"class":750},[169,2806,2066],{"class":195},[169,2808,2032],{"class":188},[169,2810,1851],{"class":195},[169,2812,2814],{"class":171,"line":2813},55,[169,2815,1597],{"class":175},[169,2817,2819,2821],{"class":171,"line":2818},56,[169,2820,1716],{"class":750},[169,2822,2823],{"class":195}," \"[Erreur] Agent bloqué, max steps atteint.\"\n",[150,2825,2827],{"id":2826},"exemple-dexécution","Exemple d'exécution",[15,2829,2830,2831,960,2834,960,2837,2840],{},"Imaginons qu'on donne à l'agent des outils comme ",[39,2832,2833],{},"search_web",[39,2835,2836],{},"read_page",[39,2838,2839],{},"execute_python",", et la tâche :",[48,2842,2843],{},[15,2844,2845],{},[24,2846,2847],{},"\"Trouve les 3 dernières news sur Qwen, résume-les en une phrase chacune, et écris une mini synthèse.\"",[15,2849,2850],{},"La boucle pourrait faire :",[2852,2853,2854,2870],"table",{},[2855,2856,2857],"thead",{},[2858,2859,2860,2864,2867],"tr",{},[2861,2862,2863],"th",{},"Step",[2861,2865,2866],{},"Action du LLM",[2861,2868,2869],{},"Résultat",[2871,2872,2873,2887,2899,2912,2925],"tbody",{},[2858,2874,2875,2879,2884],{},[2876,2877,2878],"td",{},"1",[2876,2880,2881],{},[39,2882,2883],{},"search_web(\"Qwen latest news 2026\")",[2876,2885,2886],{},"5 URLs trouvées",[2858,2888,2889,2891,2896],{},[2876,2890,1163],{},[2876,2892,2893],{},[39,2894,2895],{},"read_page(url_1)",[2876,2897,2898],{},"Contenu de l'article 1",[2858,2900,2901,2904,2909],{},[2876,2902,2903],{},"3",[2876,2905,2906],{},[39,2907,2908],{},"read_page(url_2)",[2876,2910,2911],{},"Contenu de l'article 2",[2858,2913,2914,2917,2922],{},[2876,2915,2916],{},"4",[2876,2918,2919],{},[39,2920,2921],{},"read_page(url_3)",[2876,2923,2924],{},"Contenu de l'article 3",[2858,2926,2927,2930,2936],{},[2876,2928,2929],{},"5",[2876,2931,2932,2935],{},[24,2933,2934],{},"(pas de tool_call)"," → réponse finale",[2876,2937,2938],{},"Synthèse des 3 articles",[48,2940,2941],{},[15,2942,2943,2944,2947],{},"L'agent a pris 5 étapes pour accomplir sa mission. À chaque tour, il observe le résultat avant de décider de la suite. C'est ça la magie de la boucle agent. Et c'est juste une boucle ",[39,2945,2946],{},"while"," avec des appels d'inférence.",[10,2949,2951],{"id":2950},"les-différents-types-dagents","Les différents types d'agents",[15,2953,2954],{},"À partir de cette boucle de base, on peut construire des trucs plus élaborés :",[150,2956,2958],{"id":2957},"agent-simple-react","Agent simple (ReAct)",[15,2960,2961,2962,2965],{},"C'est ce qu'on vient de faire. ",[19,2963,2964],{},"Reasoning + Acting",". Le modèle alterne entre penser et agir. Simple, direct, efficace. Mais ça peut partir en vrille sur des tâches complexes où il faut planifier à l'avance.",[150,2967,2969],{"id":2968},"agent-planificateur-plan-and-execute","Agent planificateur (Plan-and-Execute)",[15,2971,2972,2973,2976],{},"Le LLM commence par établir un ",[19,2974,2975],{},"plan complet",", puis l'exécute étape par étape. C'est plus structuré.",[68,2978,2981],{"className":2979,"code":2980,"language":73},[71],"User: \"Fais une analyse du marché des GPU en 2026\"\n\nPlan:\n  1. Rechercher les parts de marché NVIDIA/AMD/Intel\n  2. Récupérer les prix moyens par gamme\n  3. Chercher les annonces récentes de nouveaux modèles\n  4. Synthétiser les informations\n  5. Rédiger l'analyse\n\nExécution: 1 -> 2 -> 3 -> 4 -> 5\n",[39,2982,2980],{"__ignoreMap":76},[48,2984,2985],{},[15,2986,2987],{},"C'est plus robuste, mais moins flexible si le plan se révèle mauvais en cours de route. L'agent peut pas vraiment improviser.",[150,2989,2991],{"id":2990},"multi-agent","Multi-agent",[15,2993,2994],{},"Plusieurs agents spécialisés qui collaborent, orchestrés par un agent \"chef\". Chacun a ses propres outils et sa propre \"personnalité\".",[68,2996,2999],{"className":2997,"code":2998,"language":73},[71],"Agent Orchestrateur\n  ├── Agent Recherche -> search_web, scrape_page\n  ├── Agent Code      -> execute_python, read_file\n  └── Agent Rédaction -> format_output, save_file\n",[39,3000,2998],{"__ignoreMap":76},[48,3002,3003],{},[15,3004,3005],{},"C'est là que ça devient vraiment puissant, mais aussi plus complexe à debugger. Chaque agent fait tourner sa propre boucle, et ils communiquent entre eux via le chef d'orchestre.",[10,3007,3009],{"id":3008},"ce-qui-peut-foirer-et-ça-foire-souvent","Ce qui peut foirer (et ça foire souvent)",[15,3011,3012],{},"Le tool calling et les agents, c'est puissant mais c'est loin d'être infaillible. Quelques pièges classiques :",[15,3014,3015,3018,3019,3022],{},[19,3016,3017],{},"Boucle infinie."," L'agent appelle des outils en boucle sans jamais s'arrêter. D'où le ",[39,3020,3021],{},"max_steps"," dans le code plus haut. Mets toujours une limite, crois-moi.",[15,3024,3025,3028,3029,3031],{},[19,3026,3027],{},"Hallucination d'outils."," Le modèle invente une fonction qui n'existe pas, ou appelle ",[39,3030,122],{}," avec des paramètres qui n'ont aucun sens. Sur Qwen3-4B, ça arrive, surtout si le tool schema est complexe.",[15,3033,3034,3037],{},[19,3035,3036],{},"JSON mal formé."," Le modèle sort un JSON invalide ou tronqué. C'est LE problème numéro 1 avec les petits modèles. Solutions :",[68,3039,3041],{"className":741,"code":3040,"language":743,"meta":76,"style":76},"# Option 1 : Grammar sampling avec llama.cpp\n# Force le modèle à ne générer que du JSON valide\ngrammar = llama_cpp.LlamaGrammar.from_json_schema(\n    json.dumps({\n        \"type\": \"object\",\n        \"properties\": {\n            \"tool_call\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"name\": {\"type\": \"string\"},\n                    \"arguments\": {\"type\": \"object\"}\n                },\n                \"required\": [\"name\", \"arguments\"]\n            }\n        },\n        \"required\": [\"tool_call\"]\n    })\n)\n\noutput = llm(prompt, max_tokens=256, grammar=grammar)\n# Là, t'es GARANTI d'avoir du JSON valide.\n",[39,3042,3043,3048,3053,3063,3068,3078,3084,3091,3101,3107,3123,3138,3142,3156,3160,3164,3174,3179,3183,3187,3214],{"__ignoreMap":76},[169,3044,3045],{"class":171,"line":172},[169,3046,3047],{"class":818},"# Option 1 : Grammar sampling avec llama.cpp\n",[169,3049,3050],{"class":171,"line":179},[169,3051,3052],{"class":818},"# Force le modèle à ne générer que du JSON valide\n",[169,3054,3055,3058,3060],{"class":171,"line":185},[169,3056,3057],{"class":175},"grammar ",[169,3059,774],{"class":750},[169,3061,3062],{"class":175}," llama_cpp.LlamaGrammar.from_json_schema(\n",[169,3064,3065],{"class":171,"line":202},[169,3066,3067],{"class":175},"    json.dumps({\n",[169,3069,3070,3072,3074,3076],{"class":171,"line":211},[169,3071,248],{"class":195},[169,3073,192],{"class":175},[169,3075,253],{"class":195},[169,3077,199],{"class":175},[169,3079,3080,3082],{"class":171,"line":224},[169,3081,261],{"class":195},[169,3083,208],{"class":175},[169,3085,3086,3089],{"class":171,"line":237},[169,3087,3088],{"class":195},"            \"tool_call\"",[169,3090,208],{"class":175},[169,3092,3093,3095,3097,3099],{"class":171,"line":245},[169,3094,929],{"class":195},[169,3096,192],{"class":175},[169,3098,253],{"class":195},[169,3100,199],{"class":175},[169,3102,3103,3105],{"class":171,"line":258},[169,3104,940],{"class":195},[169,3106,208],{"class":175},[169,3108,3109,3112,3114,3116,3118,3120],{"class":171,"line":266},[169,3110,3111],{"class":195},"                    \"name\"",[169,3113,950],{"class":175},[169,3115,953],{"class":195},[169,3117,192],{"class":175},[169,3119,282],{"class":195},[169,3121,3122],{"class":175},"},\n",[169,3124,3125,3128,3130,3132,3134,3136],{"class":171,"line":274},[169,3126,3127],{"class":195},"                    \"arguments\"",[169,3129,950],{"class":175},[169,3131,953],{"class":195},[169,3133,192],{"class":175},[169,3135,253],{"class":195},[169,3137,642],{"class":175},[169,3139,3140],{"class":171,"line":287},[169,3141,975],{"class":175},[169,3143,3144,3146,3148,3150,3152,3154],{"class":171,"line":298},[169,3145,980],{"class":195},[169,3147,316],{"class":175},[169,3149,1438],{"class":195},[169,3151,960],{"class":175},[169,3153,1447],{"class":195},[169,3155,322],{"class":175},[169,3157,3158],{"class":171,"line":304},[169,3159,991],{"class":175},[169,3161,3162],{"class":171,"line":310},[169,3163,307],{"class":175},[169,3165,3166,3168,3170,3172],{"class":171,"line":325},[169,3167,313],{"class":195},[169,3169,316],{"class":175},[169,3171,1433],{"class":195},[169,3173,322],{"class":175},[169,3175,3176],{"class":171,"line":331},[169,3177,3178],{"class":175},"    })\n",[169,3180,3181],{"class":171,"line":337},[169,3182,836],{"class":175},[169,3184,3185],{"class":171,"line":343},[169,3186,766],{"emptyLinePlaceholder":765},[169,3188,3189,3191,3193,3196,3199,3201,3204,3206,3209,3211],{"class":171,"line":348},[169,3190,1297],{"class":175},[169,3192,774],{"class":750},[169,3194,3195],{"class":175}," llm(prompt, ",[169,3197,3198],{"class":782},"max_tokens",[169,3200,774],{"class":750},[169,3202,3203],{"class":188},"256",[169,3205,960],{"class":175},[169,3207,3208],{"class":782},"grammar",[169,3210,774],{"class":750},[169,3212,3213],{"class":175},"grammar)\n",[169,3215,3216],{"class":171,"line":359},[169,3217,3218],{"class":818},"# Là, t'es GARANTI d'avoir du JSON valide.\n",[48,3220,3221],{},[15,3222,3223],{},"Le grammar sampling, c'est un cheat code. Tu restreins les tokens que le modèle a le droit de générer pour qu'ils respectent un schéma. Zéro parsing, zéro regex, zéro erreur. C'est utilisable avec n'importe quel modèle GGUF.",[15,3225,3226,3229],{},[19,3227,3228],{},"Explosion du contexte."," Chaque appel d'outil ajoute du texte dans l'historique. Sur 20 étapes, le contexte peut facilement dépasser la taille max du modèle. Il faut parfois élaguer l'historique :",[68,3231,3233],{"className":741,"code":3232,"language":743,"meta":76,"style":76},"MAX_HISTORY = 15\n\nif step_count > MAX_HISTORY:\n    # On garde le système + les derniers messages\n    # Attention, c'est simplifié, dans la vraie vie faut\n    # gérer les paires tool_call / tool_result ensemble\n    messages = messages[:2] + messages[-(MAX_HISTORY - 2):]\n",[39,3234,3235,3245,3249,3263,3268,3273,3278],{"__ignoreMap":76},[169,3236,3237,3240,3242],{"class":171,"line":172},[169,3238,3239],{"class":188},"MAX_HISTORY",[169,3241,1101],{"class":750},[169,3243,3244],{"class":188}," 15\n",[169,3246,3247],{"class":171,"line":179},[169,3248,766],{"emptyLinePlaceholder":765},[169,3250,3251,3253,3255,3258,3261],{"class":171,"line":185},[169,3252,1902],{"class":750},[169,3254,2470],{"class":175},[169,3256,3257],{"class":750},">",[169,3259,3260],{"class":188}," MAX_HISTORY",[169,3262,1526],{"class":175},[169,3264,3265],{"class":171,"line":202},[169,3266,3267],{"class":818},"    # On garde le système + les derniers messages\n",[169,3269,3270],{"class":171,"line":211},[169,3271,3272],{"class":818},"    # Attention, c'est simplifié, dans la vraie vie faut\n",[169,3274,3275],{"class":171,"line":224},[169,3276,3277],{"class":818},"    # gérer les paires tool_call / tool_result ensemble\n",[169,3279,3280,3283,3285,3288,3290,3293,3295,3298,3301,3303,3305,3308,3311],{"class":171,"line":237},[169,3281,3282],{"class":175},"    messages ",[169,3284,774],{"class":750},[169,3286,3287],{"class":175}," messages[:",[169,3289,1163],{"class":188},[169,3291,3292],{"class":175},"] ",[169,3294,2026],{"class":750},[169,3296,3297],{"class":175}," messages[",[169,3299,3300],{"class":750},"-",[169,3302,1399],{"class":175},[169,3304,3239],{"class":188},[169,3306,3307],{"class":750}," -",[169,3309,3310],{"class":188}," 2",[169,3312,3313],{"class":175},"):]\n",[10,3315,3317],{"id":3316},"pourquoi-jutilise-qwen-pour-tester-ça","Pourquoi j'utilise Qwen pour tester ça",[15,3319,3320],{},"Alors honnêtement, Qwen3-4B est pas le meilleur modèle du monde pour le tool calling. Sur des tâches complexes, il galère, le JSON est souvent mal formé, et il peut partir en vrille rapidement. Les gros modèles (GPT-4, Claude, DeepSeek-V3) sont bien meilleurs sur ce terrain.",[15,3322,3323,3324,3327],{},"Mais le truc, c'est que Qwen3-4B a un énorme avantage : ",[19,3325,3326],{},"il est petit et il tourne sur ma machine",". En Q4_K_M, c'est environ 3 Go de RAM, et ça tourne à 30-40 tokens/seconde sur une RTX 5060. Donc je peux prototyper, itérer, casser des trucs, recommencer, sans payer un centime d'API et sans latence réseau.",[15,3329,3330],{},"Et franchement, pour apprendre comment le tool calling fonctionne, c'est parfait. Les erreurs de parsing, les hallucinations, le JSON mal formé... tu les vois en vrai, tu dois les gérer toi-même, et c'est comme ça que tu comprends vraiment le mécanisme. Si tout marchait du premier coup avec un modèle parfait, t'apprendrais rien.",[15,3332,3333,3334,1274,3336,3338],{},"Le template de chat de Qwen (",[39,3335,1273],{},[39,3337,1277],{},") est simple et prévisible, ce qui rend le prompt engineering facile à debugger. Et le grammar sampling de llama.cpp compense une bonne partie des lacunes du modèle sur la génération de JSON structuré.",[48,3340,3341],{},[15,3342,3343],{},"Bref, Qwen3-4B c'est pas le plus fort en tool calling, mais c'est le plus accessible pour tester et apprendre. Et pour du prototypage, c'est largement suffisant.",[10,3345,3347],{"id":3346},"conclusion","Conclusion",[15,3349,3350,3351,3354],{},"Le tool calling, c'est le mécanisme fondamental qui transforme un LLM de simple générateur de texte en un vrai ",[19,3352,3353],{},"agent autonome"," capable d'interagir avec le monde. Y'a rien de magique : le modèle émet une demande structurée, ton code l'exécute, et le modèle synthétise le résultat. C'est juste une boucle.",[15,3356,3357],{},"Et une fois que t'as pigé ça, les possibilités sont énormes :",[81,3359,3360,3363,3366,3369,3372],{},[84,3361,3362],{},"Un agent qui lit tes mails et rédige des brouillons de réponse",[84,3364,3365],{},"Un agent qui explore une codebase et suggère des refactors",[84,3367,3368],{},"Un agent qui fait de la revue de code automatique",[84,3370,3371],{},"Un agent qui navigue sur le web pour faire de la recherche",[84,3373,3374],{},"Un agent qui contrôle ton PC avec des commandes shell",[15,3376,3377,3378,3381],{},"Et tout ça, ça tourne ",[19,3379,3380],{},"en local",", sur ta machine, sans rien envoyer à personne. Franchement, c'est le pied.",[48,3383,3384],{},[15,3385,3386],{},"Prochaine étape : construire un vrai agent multi-outils avec du RAG, de la mémoire persistante, et du grammar sampling pour des appels d'outils fiables à 100%. Stay tuned.",[3388,3389,3390],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sRjNt, html code.shiki .sRjNt{--shiki-default:#85E89D;--shiki-default-font-weight:bold}html pre.shiki code .sns5M, html code.shiki .sns5M{--shiki-default:#DBEDFF}",{"title":76,"searchDepth":179,"depth":179,"links":3392},[3393,3394,3395,3396,3397,3398,3399,3400,3401,3402,3403,3404,3405,3406,3407],{"id":152,"depth":179,"text":153},{"id":494,"depth":179,"text":495},{"id":650,"depth":179,"text":651},{"id":705,"depth":179,"text":706},{"id":737,"depth":179,"text":738},{"id":839,"depth":179,"text":840},{"id":1281,"depth":179,"text":1282},{"id":1467,"depth":179,"text":1468},{"id":2002,"depth":179,"text":2003},{"id":2216,"depth":179,"text":2217},{"id":2224,"depth":179,"text":2225},{"id":2826,"depth":179,"text":2827},{"id":2957,"depth":179,"text":2958},{"id":2968,"depth":179,"text":2969},{"id":2990,"depth":179,"text":2991},"2026-07-15","Comprendre comment un LLM peut interagir avec des outils externes et comment on construit un agent à partir de ça.","md",{},"/notes/tool-calling-agents.fr",{"title":5,"description":3409},"tool-calling-agents","notes/tool-calling-agents.fr",[3417,3418],"lab","ml","8SAybRKzsRsfW7YjFCKQXmi0macyjokw6Sf9DiNnExc",1784274066508]