'Non identifié — veuillez vous connecter.']); exit; } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['error' => 'Méthode non autorisée']); exit; } const ANTHROPIC_MODEL = 'claude-sonnet-5'; const ANTHROPIC_API_URL = 'https://api.anthropic.com/v1/messages'; const ANTHROPIC_VERSION = '2023-06-01'; const ANTHROPIC_MAX_TOKENS = 1024; $data = json_decode(file_get_contents('php://input'), true) ?? []; $agnIncr = (int) ($data['AGN_Incr'] ?? 0); $messages = $data['messages'] ?? []; if ($agnIncr <= 0) { http_response_code(400); echo json_encode(['error' => 'AGN_Incr manquant ou invalide']); exit; } if (!is_array($messages) || !$messages) { http_response_code(400); echo json_encode(['error' => 'Aucun message à transmettre']); exit; } // trim() : même précaution que pour OPENAI_API_KEY (contact_ia_interprete.php) // — un espace resté collé en copiant-collant la clé dans docker-compose/.env // suffit à faire échouer l'authentification avec un message qui ne le laisse // pas deviner. $apiKey = trim((string) getenv('ANTHROPIC_API_KEY')); if (!$apiKey) { http_response_code(500); echo json_encode(['error' => "Clé ANTHROPIC_API_KEY absente côté serveur — à ajouter dans l'environnement du conteneur avant de pouvoir tester."]); exit; } // ANTHROPIC_WORKSPACE_ID (optionnelle) — ajoutée le 10 septembre 2026 : // premier essai réel de Jean-Marc en erreur HTTP 400, message Anthropic sans // ambiguïté : « This API key is not scoped to a workspace, so this request // must include the anthropic-workspace-id header ». Arrive avec une clé // personnelle/de type "organisation" (pas liée à un espace de travail en // particulier dans la Console) plutôt qu'avec une clé créée directement DANS // un espace de travail (celles-ci n'ont pas besoin de cet en-tête). Les deux // façons de résoudre, au choix de Jean-Marc : // 1. Recréer la clé depuis platform.claude.com/settings/keys en choisissant // explicitement un espace de travail précis (pas "Toute l'organisation") // au moment de la création — alors cette variable n'est pas nécessaire. // 2. Garder la clé actuelle et poser ANTHROPIC_WORKSPACE_ID (format // wrkspc_..., visible dans les réglages de l'espace de travail concerné // sur platform.claude.com) à côté d'ANTHROPIC_API_KEY dans l'environnement // du conteneur. // Header ajouté seulement si la variable est posée, pour ne rien changer au // comportement le jour où Jean-Marc bascule sur une clé déjà liée à un espace // de travail (option 1). $workspaceId = trim((string) getenv('ANTHROPIC_WORKSPACE_ID')); try { $pdo = getDbConnection(); } catch (PDOException $e) { http_response_code(500); echo json_encode(['error' => 'Connexion base de données impossible']); exit; } try { $stmt = $pdo->prepare( 'SELECT AGN_Prenom, AGN_Fonction, AGN_Identite, AGN_Experience FROM ASP_Agents WHERE AGN_Incr = ?' ); $stmt->execute([$agnIncr]); $collaborateur = $stmt->fetch(); } catch (PDOException $e) { http_response_code(500); echo json_encode(['error' => 'Erreur base de données : ' . $e->getMessage()]); exit; } if (!$collaborateur) { http_response_code(404); echo json_encode(['error' => 'Collaborateur introuvable']); exit; } $prenom = $collaborateur['AGN_Prenom'] ?: 'ce collaborateur'; $fonction = $collaborateur['AGN_Fonction'] ?: ''; $identite = trim((string) ($collaborateur['AGN_Identite'] ?? '')); $experience = trim((string) ($collaborateur['AGN_Experience'] ?? '')); $blocIdentite = $identite !== '' ? $identite : "(Aucune identité détaillée n'a encore été écrite pour {$prenom} — tiens-toi en à son rôle : {$fonction}.)"; // Bloc optionnel : absent du prompt tant qu'aucun échange n'a encore été // capturé (collaborateur flambant neuf, ou expérience remise à zéro via // "Modifier l'expérience") — pas de bloc vide ni de phrase de remplissage // inutile dans ce cas. $blocExperience = $experience !== '' ? "\n\nVoici ce que tu retiens de vos échanges précédents avec Jean-Marc (essais de dialogue passés) :\n\n{$experience}" : ''; // -------- Prompt système -------- // Distinction "collègue de travail" / "assistant IA" / "compagnon virtuel" // et règle de non-divulgation d'AGN_Identite : voir commentaire d'en-tête, // issues directement de l'échange du 10 septembre 2026 avec Jean-Marc. $promptSysteme = << $role, 'content' => $contenu]; } if (!$historique) { http_response_code(400); echo json_encode(['error' => 'Aucun message valide à transmettre']); exit; } // -------- Appel Anthropic -------- // system en tableau de blocs + cache_control (11 septembre 2026, voir // journal_ia.php) : ce prompt (identité + expérience) ne change pas d'un // tour à l'autre d'une même session de test, mais était jusqu'ici refacturé // en entier à CHAQUE appel — le cache Anthropic permet de ne payer le tarif // plein qu'une fois, puis un tarif réduit (~1/10ᵉ) tant que rien ne change. // ⚠️ Comme pour le reste de ce fichier, mécanisme suivant la documentation // Anthropic mais jamais éprouvé en conditions réelles avant ce soir. $corpsRequete = [ 'model' => ANTHROPIC_MODEL, 'max_tokens' => ANTHROPIC_MAX_TOKENS, 'system' => [ ['type' => 'text', 'text' => $promptSysteme, 'cache_control' => ['type' => 'ephemeral']], ], 'messages' => $historique, ]; $entetes = [ 'Content-Type: application/json', 'anthropic-version: ' . ANTHROPIC_VERSION, 'X-Api-Key: ' . $apiKey, ]; // Voir commentaire ANTHROPIC_WORKSPACE_ID plus haut — en-tête ajouté // uniquement si la variable est posée. if ($workspaceId) { $entetes[] = 'anthropic-workspace-id: ' . $workspaceId; } $ch = curl_init(ANTHROPIC_API_URL); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($corpsRequete), CURLOPT_HTTPHEADER => $entetes, CURLOPT_TIMEOUT => 60, CURLOPT_CONNECTTIMEOUT => 10, ]); $reponseBrute = curl_exec($ch); $erreurCurl = curl_error($ch); $codeHttp = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($reponseBrute === false) { http_response_code(502); echo json_encode(['error' => "Appel à l'API Anthropic impossible : " . $erreurCurl]); exit; } $reponseDecodee = json_decode($reponseBrute, true); if ($codeHttp !== 200) { http_response_code(502); echo json_encode([ 'error' => "L'API Anthropic a renvoyé une erreur (HTTP $codeHttp) : " . ($reponseDecodee['error']['message'] ?? $reponseBrute), ]); exit; } // Réponse Messages API : content est un tableau de blocs ({"type":"text", // "text":"..."} pour une réponse texte simple, seul cas utilisé ici — pas // d'appel d'outil configuré). $texteReponse = ''; foreach ($reponseDecodee['content'] ?? [] as $bloc) { if (($bloc['type'] ?? '') === 'text') { $texteReponse .= $bloc['text']; } } if ($texteReponse === '') { http_response_code(502); echo json_encode(['error' => "Réponse Anthropic inattendue (pas de contenu texte) : " . $reponseBrute]); exit; } // Journalisation du coût réel (11 septembre 2026, voir journal_ia.php) — // jamais bloquant, jamais silencieux côté serveur en cas d'échec. journaliserAppelIA($pdo, 'collaborateur_dialogue', $agnIncr, null, ANTHROPIC_MODEL, $reponseDecodee['usage'] ?? []); echo json_encode(['reponse' => $texteReponse]);