Remove build artifacts from tracking and update retrieval doc
- Add .gitignore for build artifacts - Update intelligent-document-retrieval.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
80f26aea54
commit
f6f4813d8f
11
.gitignore
vendored
11
.gitignore
vendored
@ -62,4 +62,13 @@ Thumbs.db
|
||||
*.mov
|
||||
*.wmv
|
||||
*.mp3
|
||||
*.wav
|
||||
*.wavbuild/
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.dll
|
||||
build/
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.dll
|
||||
|
||||
@ -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<int>();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// 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<ToolResult> 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<std::string, std::unique_ptr<IToolExecutor>> tools;
|
||||
|
||||
public:
|
||||
// Enregistrer un tool
|
||||
void registerTool(std::unique_ptr<IToolExecutor> 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<ListDocumentsTool>());
|
||||
toolRegistry.registerTool(std::make_unique<ReadDocumentTool>());
|
||||
toolRegistry.registerTool(std::make_unique<SearchDocumentsTool>());
|
||||
toolRegistry.registerTool(std::make_unique<WeatherTool>());
|
||||
toolRegistry.registerTool(std::make_unique<WebSearchTool>());
|
||||
// ... 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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user