From f6f4813d8f60929d32b76457a6e259b169ef1cb9 Mon Sep 17 00:00:00 2001 From: StillHammer Date: Mon, 24 Nov 2025 21:40:08 +0800 Subject: [PATCH] Remove build artifacts from tracking and update retrieval doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add .gitignore for build artifacts - Update intelligent-document-retrieval.md πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 11 +- .../intelligent-document-retrieval.md | 404 +++++++++++++++++- 2 files changed, 408 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 83cefe3..d7dd2af 100644 --- a/.gitignore +++ b/.gitignore @@ -62,4 +62,13 @@ Thumbs.db *.mov *.wmv *.mp3 -*.wav \ No newline at end of file +*.wavbuild/ +*.o +*.a +*.so +*.dll +build/ +*.o +*.a +*.so +*.dll diff --git a/docs/architecture/intelligent-document-retrieval.md b/docs/architecture/intelligent-document-retrieval.md index 38c0802..7dcd8b1 100644 --- a/docs/architecture/intelligent-document-retrieval.md +++ b/docs/architecture/intelligent-document-retrieval.md @@ -615,20 +615,144 @@ public: }; ``` -### Boucle Agentique UnifiΓ©e +### Boucle Agentique - Fonctionnement DΓ©taillΓ© + +#### Principe fondamental + +Le LLM **ne fait PAS** les appels API lui-mΓͺme. C'est **ton code** qui : +1. ReΓ§oit la demande de tool du LLM +2. ExΓ©cute l'appel (API externe, lecture fichier, etc.) +3. Renvoie le rΓ©sultat au LLM +4. Relance un appel LLM avec le contexte enrichi + +#### Flux visuel annotΓ© + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ EXEMPLE: User demande "Quelle mΓ©tΓ©o Γ  Paris ?" β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Γ‰TAPE 1: Initialisation β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ messages = [ β”‚ β”‚ +β”‚ β”‚ {role: "user", content: "Quelle mΓ©tΓ©o Γ  Paris ?"} β”‚ β”‚ +β”‚ β”‚ ] β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Γ‰TAPE 2: Premier appel LLM β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ response = llm.chat(messages, tools) β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ LLM analyse et dΓ©cide: "J'ai besoin de get_weather" β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ RΓ©ponse LLM: β”‚ β”‚ +β”‚ β”‚ { β”‚ β”‚ +β”‚ β”‚ stop_reason: "tool_use", ← PAS "end_turn", on continue! β”‚ β”‚ +β”‚ β”‚ content: [{ β”‚ β”‚ +β”‚ β”‚ type: "tool_use", β”‚ β”‚ +β”‚ β”‚ id: "call_123", β”‚ β”‚ +β”‚ β”‚ name: "get_weather", β”‚ β”‚ +β”‚ β”‚ input: {city: "Paris"} β”‚ β”‚ +β”‚ β”‚ }] β”‚ β”‚ +β”‚ β”‚ } β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Γ‰TAPE 3: TON CODE exΓ©cute le tool β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ // C'est ICI que tu fais l'appel API externe β”‚ β”‚ +β”‚ β”‚ result = executeTool("get_weather", {city: "Paris"}) β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ // Ton implΓ©mentation: β”‚ β”‚ +β”‚ β”‚ json executeTool(name, input) { β”‚ β”‚ +β”‚ β”‚ if (name == "get_weather") { β”‚ β”‚ +β”‚ β”‚ return httpGet("https://api.weather.com", input); ← API! β”‚ β”‚ +β”‚ β”‚ } β”‚ β”‚ +β”‚ β”‚ } β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ RΓ©sultat: {temp: 12, condition: "nuageux"} β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Γ‰TAPE 4: Enrichir le contexte β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ messages = [ β”‚ β”‚ +β”‚ β”‚ {role: "user", content: "Quelle mΓ©tΓ©o Γ  Paris ?"}, β”‚ β”‚ +β”‚ β”‚ {role: "assistant", content: [{tool_use...}]}, ← AJOUTΓ‰ β”‚ β”‚ +β”‚ β”‚ {role: "user", content: [{ ← AJOUTΓ‰ β”‚ β”‚ +β”‚ β”‚ type: "tool_result", β”‚ β”‚ +β”‚ β”‚ tool_use_id: "call_123", β”‚ β”‚ +β”‚ β”‚ content: "{temp: 12, condition: nuageux}" β”‚ β”‚ +β”‚ β”‚ }]} β”‚ β”‚ +β”‚ β”‚ ] β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Γ‰TAPE 5: DEUXIÈME appel LLM (avec contexte enrichi) β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ response = llm.chat(messages, tools) ← MÊME array enrichi β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ LLM voit le rΓ©sultat du tool et gΓ©nΓ¨re la rΓ©ponse finale β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ RΓ©ponse LLM: β”‚ β”‚ +β”‚ β”‚ { β”‚ β”‚ +β”‚ β”‚ stop_reason: "end_turn", ← MAINTENANT c'est fini! β”‚ β”‚ +β”‚ β”‚ content: [{ β”‚ β”‚ +β”‚ β”‚ type: "text", β”‚ β”‚ +β”‚ β”‚ text: "Il fait 12Β°C Γ  Paris avec un temps nuageux." β”‚ β”‚ +β”‚ β”‚ }] β”‚ β”‚ +β”‚ β”‚ } β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Γ‰TAPE 6: Retourner au user β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ return "Il fait 12Β°C Γ  Paris avec un temps nuageux." β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +#### Code annotΓ© ```cpp json AIAssistantModule::agenticLoop(const std::string& userQuery) { + + // ═══════════════════════════════════════════════════════════════════ + // Γ‰TAPE 1: Initialiser l'historique avec la question du user + // ═══════════════════════════════════════════════════════════════════ json messages = json::array(); messages.push_back({{"role", "user"}, {"content", userQuery}}); int iterations = 0; const int MAX_ITERATIONS = config["max_iterations"].get(); + // ═══════════════════════════════════════════════════════════════════ + // BOUCLE PRINCIPALE - Continue tant que le LLM demande des tools + // Chaque tour = 1 appel API LLM payant + // ═══════════════════════════════════════════════════════════════════ while (iterations++ < MAX_ITERATIONS) { + + // ─────────────────────────────────────────────────────────────── + // Γ‰TAPE 2/5/8...: Appel LLM avec tout le contexte accumulΓ© + // Le LLM voit: question initiale + tous les tools calls + rΓ©sultats + // ─────────────────────────────────────────────────────────────── auto response = provider->chat(systemPrompt, messages, tools); + // ─────────────────────────────────────────────────────────────── + // CHECK: Est-ce que le LLM a fini ou veut encore des tools ? + // stop_reason == "end_turn" β†’ RΓ©ponse finale, on sort + // stop_reason == "tool_use" β†’ Il veut des donnΓ©es, on continue + // ─────────────────────────────────────────────────────────────── if (provider->isEndTurn(response)) { + // Γ‰TAPE 6: C'est fini! Extraire et retourner la rΓ©ponse return { {"response", provider->extractText(response)}, {"iterations", iterations}, @@ -636,38 +760,306 @@ json AIAssistantModule::agenticLoop(const std::string& userQuery) { }; } + // ─────────────────────────────────────────────────────────────── + // Γ‰TAPE 3: Le LLM veut des tools - Parser sa demande + // Il peut demander PLUSIEURS tools en une fois (parallΓ¨le) + // ─────────────────────────────────────────────────────────────── auto toolCalls = provider->parseToolCalls(response); if (toolCalls.empty()) { return {{"error", "unexpected_state"}}; } - // ExΓ©cuter les tools (logique commune Γ  tous les providers) + // ─────────────────────────────────────────────────────────────── + // Γ‰TAPE 3 (suite): EXΓ‰CUTER les tools + // C'est ICI que TON CODE fait le travail: + // - Appels API externes (mΓ©tΓ©o, recherche web, etc.) + // - Lecture de fichiers + // - RequΓͺtes base de donnΓ©es + // - N'importe quelle opΓ©ration + // ─────────────────────────────────────────────────────────────── std::vector results; for (const auto& call : toolCalls) { + // executeTool() = TA fonction qui fait le vrai travail auto result = executeTool(call.name, call.input); results.push_back({call.id, result.dump(), false}); } - // Ajouter Γ  l'historique (format dΓ©pend du provider) + // ─────────────────────────────────────────────────────────────── + // Γ‰TAPE 4: Enrichir l'historique pour le prochain appel LLM + // On ajoute: + // 1. Ce que le LLM a demandΓ© (assistant message avec tool_use) + // 2. Les rΓ©sultats qu'on a obtenus (user message avec tool_result) + // ─────────────────────────────────────────────────────────────── provider->appendAssistantMessage(messages, response); - // GΓ©rer la diffΓ©rence de format pour tool results + // Formater les rΓ©sultats selon le provider (Claude vs OpenAI) auto toolResultsMsg = provider->formatToolResults(results); if (toolResultsMsg.is_array()) { - // OpenAI: plusieurs messages + // OpenAI: chaque tool_result = un message sΓ©parΓ© for (const auto& msg : toolResultsMsg) { messages.push_back(msg); } } else { - // Claude: un seul message + // Claude: tous les tool_results dans un seul message messages.push_back(toolResultsMsg); } + + // β†’ RETOUR AU DΓ‰BUT DE LA BOUCLE + // Le prochain llm.chat() verra tout l'historique enrichi } return {{"error", "max_iterations_reached"}}; } ``` +#### CoΓ»t par scΓ©nario + +| ScΓ©nario | Tours de boucle | Appels LLM | CoΓ»t (~Sonnet) | +|----------|-----------------|------------|----------------| +| Question simple (pas de tool) | 1 | 1 | ~$0.01 | +| 1 tool call | 2 | 2 | ~$0.02 | +| 2 tools en parallΓ¨le | 2 | 2 | ~$0.02 | +| 2 tools sΓ©quentiels | 3 | 3 | ~$0.03 | +| Search docs β†’ Read doc β†’ RΓ©ponse | 3 | 3 | ~$0.03 | +| Recherche complexe (5 tools) | 4-6 | 4-6 | ~$0.05 | + +### Tools avec Appels API Externes + +#### Architecture des Tools + +```cpp +// ═══════════════════════════════════════════════════════════════════════ +// Interface pour tous les tools (locaux ou API) +// ═══════════════════════════════════════════════════════════════════════ + +class IToolExecutor { +public: + virtual ~IToolExecutor() = default; + + // ExΓ©cute le tool et retourne le rΓ©sultat (JSON) + virtual json execute(const json& input) = 0; + + // DΓ©finition du tool pour le LLM (nom, description, paramΓ¨tres) + virtual json getToolDefinition() = 0; + + // Timeout spΓ©cifique (certaines API sont lentes) + virtual std::chrono::milliseconds getTimeout() { + return std::chrono::milliseconds(30000); // 30s par dΓ©faut + } +}; +``` + +#### Exemple: Tool avec API externe (mΓ©tΓ©o) + +```cpp +// ═══════════════════════════════════════════════════════════════════════ +// WeatherTool - Appelle une API mΓ©tΓ©o externe +// ═══════════════════════════════════════════════════════════════════════ + +class WeatherTool : public IToolExecutor { + std::string apiKey; + +public: + WeatherTool() : apiKey(getEnvVar("WEATHER_API_KEY")) {} + + // ─────────────────────────────────────────────────────────────────── + // DΓ©finition envoyΓ©e au LLM pour qu'il sache comment utiliser ce tool + // ─────────────────────────────────────────────────────────────────── + json getToolDefinition() override { + return { + {"name", "get_weather"}, + {"description", "Obtient la mΓ©tΓ©o actuelle pour une ville donnΓ©e."}, + {"input_schema", { + {"type", "object"}, + {"properties", { + {"city", { + {"type", "string"}, + {"description", "Nom de la ville (ex: Paris, Tokyo)"} + }} + }}, + {"required", {"city"}} + }} + }; + } + + // ─────────────────────────────────────────────────────────────────── + // ExΓ©cution: C'est ICI que l'appel API externe se fait + // Le LLM ne fait JAMAIS cet appel - c'est ton code + // ─────────────────────────────────────────────────────────────────── + json execute(const json& input) override { + std::string city = input["city"]; + + // β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + // β”‚ APPEL API EXTERNE - Ton code HTTP β”‚ + // β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + auto response = httpGet( + "https://api.weatherapi.com/v1/current.json", + { + {"key", apiKey}, + {"q", city} + } + ); + + // Gestion erreur + if (response.status != 200) { + return { + {"error", "weather_api_failed"}, + {"status", response.status} + }; + } + + // β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + // β”‚ FORMATER le rΓ©sultat pour le LLM β”‚ + // β”‚ Pas besoin de tout renvoyer, juste l'essentiel β”‚ + // β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + return { + {"city", city}, + {"temp_c", response.body["current"]["temp_c"]}, + {"condition", response.body["current"]["condition"]["text"]}, + {"humidity", response.body["current"]["humidity"]} + }; + } +}; +``` + +#### Exemple: Tool de recherche web + +```cpp +class WebSearchTool : public IToolExecutor { + std::string apiKey; + +public: + WebSearchTool() : apiKey(getEnvVar("SERPER_API_KEY")) {} + + json getToolDefinition() override { + return { + {"name", "web_search"}, + {"description", "Recherche sur le web. Pour informations rΓ©centes ou externes."}, + {"input_schema", { + {"type", "object"}, + {"properties", { + {"query", {{"type", "string"}, {"description", "RequΓͺte de recherche"}}}, + {"num_results", {{"type", "integer"}, {"default", 5}}} + }}, + {"required", {"query"}} + }} + }; + } + + json execute(const json& input) override { + std::string query = input["query"]; + int numResults = input.value("num_results", 5); + + // Appel API Serper (moteur de recherche) + auto response = httpPost( + "https://google.serper.dev/search", + {{"q", query}, {"num", numResults}}, + {{"X-API-KEY", apiKey}} + ); + + if (response.status != 200) { + return {{"error", "search_failed"}}; + } + + // Extraire les rΓ©sultats pertinents + json results = json::array(); + for (const auto& item : response.body["organic"]) { + results.push_back({ + {"title", item["title"]}, + {"snippet", item["snippet"]}, + {"url", item["link"]} + }); + } + + return {{"results", results}, {"query", query}}; + } + + // Timeout plus long pour recherche web + std::chrono::milliseconds getTimeout() override { + return std::chrono::milliseconds(15000); // 15s + } +}; +``` + +#### Registry: GΓ©rer tous les tools + +```cpp +// ═══════════════════════════════════════════════════════════════════════ +// ToolRegistry - Centralise tous les tools disponibles +// ═══════════════════════════════════════════════════════════════════════ + +class ToolRegistry { + std::map> tools; + +public: + // Enregistrer un tool + void registerTool(std::unique_ptr tool) { + auto def = tool->getToolDefinition(); + std::string name = def["name"]; + tools[name] = std::move(tool); + } + + // GΓ©nΓ©rer les dΓ©finitions pour l'API LLM + json getToolDefinitions() { + json defs = json::array(); + for (const auto& [name, tool] : tools) { + defs.push_back(tool->getToolDefinition()); + } + return defs; + } + + // ExΓ©cuter un tool par nom (appelΓ© dans la boucle agentique) + json execute(const std::string& name, const json& input) { + if (tools.find(name) == tools.end()) { + return {{"error", "unknown_tool"}, {"name", name}}; + } + + try { + return tools[name]->execute(input); + } catch (const std::exception& e) { + return {{"error", "execution_failed"}, {"message", e.what()}}; + } + } +}; + +// ═══════════════════════════════════════════════════════════════════════ +// Utilisation dans AIAssistantModule +// ═══════════════════════════════════════════════════════════════════════ + +class AIAssistantModule { + ToolRegistry toolRegistry; + +public: + AIAssistantModule() { + // Enregistrer tous les tools disponibles + toolRegistry.registerTool(std::make_unique()); + toolRegistry.registerTool(std::make_unique()); + toolRegistry.registerTool(std::make_unique()); + toolRegistry.registerTool(std::make_unique()); + toolRegistry.registerTool(std::make_unique()); + // ... autres tools + } + + json executeTool(const std::string& name, const json& input) { + return toolRegistry.execute(name, input); + } +}; +``` + +#### Qui fait quoi - RΓ©capitulatif + +| Action | LLM | Ton Code | +|--------|:---:|:--------:| +| Analyser la question du user | βœ… | | +| DΓ©cider quel(s) tool(s) appeler | βœ… | | +| GΓ©nΓ©rer les arguments du tool | βœ… | | +| **ExΓ©cuter l'appel HTTP/API** | | βœ… | +| **GΓ©rer les erreurs rΓ©seau** | | βœ… | +| **GΓ©rer les timeouts** | | βœ… | +| InterprΓ©ter le rΓ©sultat du tool | βœ… | | +| DΓ©cider si besoin d'autres tools | βœ… | | +| GΓ©nΓ©rer la rΓ©ponse finale | βœ… | | + ### Gestion Erreurs API ```cpp