Your project uses non-strict array lookups
- Read doc
- Reliability
- Major
More information: https://insight.symfony.com/what-we-analyse/php.strict_array_lookup
- */
- public static function checkExecEnabled(): bool
- {
- $disabled = explode(', ', ini_get('disable_functions'));
- return !in_array('exec', $disabled);
- }
- /**
- * Checks if Composer is installed and accessible in the current environment.
- *
Your project should use dedicated PHP string functions 154
- Read doc
- Productivity
- Info
More information: https://insight.symfony.com/what-we-analyse/php.use_string_function
- {
- $query = strtolower($query);
- $detected = [];
- foreach (self::$temporalConnectors as $connector) {
- if (strpos($query, $connector) !== false) {
- $detected[] = $connector;
- }
- }
- return $detected;
- public static function hasFinancialMetric(string $query): bool
- {
- $query = strtolower($query);
- foreach (self::$financialMetrics as $metric) {
- if (strpos($query, $metric) !== false) {
- return true;
- }
- }
- return false;
- public static function hasTemporalConnector(string $query): bool
- {
- $query = strtolower($query);
- foreach (self::$temporalConnectors as $connector) {
- if (strpos($query, $connector) !== false) {
- return true;
- }
- }
- return false;
- if (strlen($word) < 2) {
- return $word;
- }
- // Words ending in "y" -> "ies" (e.g., "category" -> "categories")
- if (substr($word, -1) === 'y') {
- return substr($word, 0, -1) . 'ies';
- }
- // Words ending in "s", "x", "z", "ch", "sh" -> add "es"
- if (preg_match('/(s|x|z|ch|sh)$/i', $word)) {
- if (strlen($word) < 2) {
- return $word;
- }
- // Words ending in "ies" -> "y" (e.g., "categories" -> "category")
- if (substr($word, -3) === 'ies') {
- return substr($word, 0, -3) . 'y';
- }
- // Words ending in "es" -> remove "es" (e.g., "boxes" -> "box")
- if (substr($word, -2) === 'es') {
- if (isset($specialCases[$entityType])) {
- return $specialCases[$entityType];
- }
- // Handle compound words with underscores (e.g., "return_orders" -> "return_order")
- if (strpos($entityType, '_') !== false) {
- $parts = explode('_', $entityType);
- $lastPart = array_pop($parts);
- // Apply singularization to the last part only
- $singularLastPart = self::singularizeWord($lastPart);
- if (substr($word, -2) === 'es') {
- return substr($word, 0, -2);
- }
- // Words ending in "s" -> remove "s" (e.g., "products" -> "product")
- if (substr($word, -1) === 's') {
- return substr($word, 0, -1);
- }
- // No change needed
- return $word;
- if (isset($specialCases[$entityType])) {
- return $specialCases[$entityType];
- }
- // Handle compound words with underscores
- if (strpos($entityType, '_') !== false) {
- $parts = explode('_', $entityType);
- $lastPart = array_pop($parts);
- // Apply pluralization to the last part only
- $pluralLastPart = self::pluralizeWord($lastPart);
- if (substr($word, -3) === 'ies') {
- return substr($word, 0, -3) . 'y';
- }
- // Words ending in "es" -> remove "es" (e.g., "boxes" -> "box")
- if (substr($word, -2) === 'es') {
- return substr($word, 0, -2);
- }
- // Words ending in "s" -> remove "s" (e.g., "products" -> "product")
- if (substr($word, -1) === 's') {
- $errorMessage .= " (estimated tokens: {$estimatedTokens}, chunk size: {$token_length})";
- }
- error_log($errorMessage);
- if (strpos($e->getMessage(), 'maximum context length') !== false && $token_length > 200) {
- error_log("Retrying with smaller chunk size...");
- return self::createEmbedding($path_file_upload, $text_description, (int)($token_length / 2));
- }
- return null;
- */
- private static function checkApiKeys(string $model): bool
- {
- if (strpos($model, 'gpt') === 0) {
- return !empty(CLICSHOPPING_APP_CHATGPT_CH_API_KEY);
- } elseif (strpos($model, 'mistral') === 0) {
- return !empty(CLICSHOPPING_APP_CHATGPT_CH_API_KEY_MISTRAL);
- } elseif (strpos($model, 'voyage') === 0) {
- return !empty(CLICSHOPPING_APP_CHATGPT_RA_API_KEY_VOYAGE_AI);
- } elseif (strpos($model, 'ollama') === 0) {
- return true;
- private static function getModelProvider(string $model): string
- {
- if (strpos($model, 'gpt') === 0) return 'openai';
- if (strpos($model, 'mistral') === 0) return 'mistral';
- if (strpos($model, 'voyage') === 0) return 'voyageai';
- if (strpos($model, 'nomic') === 0) return 'ollama';
- return 'unknown';
- }
- //*********************
- return new OpenAI3LargeEmbeddingGenerator($config);
- } elseif (strpos($model, 'gpt-medium') === 0) {
- $config = new OpenAIConfig();
- $config->apiKey = $api_key;
- return new OpenAI3SmallEmbeddingGenerator($config);
- } elseif (strpos($model, 'mistral') === 0) {
- $config = new OpenAIConfig();
- $config->apiKey = $api_key;
- return new MistralEmbeddingGenerator($config);
- } elseif (strpos($model, 'voyage3-large') === 0) {
- $config = new VoyageAIConfig();
- return null;
- }
- $api_key = self::getApiKey();
- if (strpos($model, 'gpt-large') === 0) {
- $config = new OpenAIConfig();
- $config->apiKey = $api_key;
- return new OpenAI3LargeEmbeddingGenerator($config);
- } elseif (strpos($model, 'gpt-medium') === 0) {
- $config = new OpenAIConfig();
- return new Voyage3LargeEmbeddingGenerator($config);
- } elseif (strpos($model, 'voyage3-lite') === 0) {
- $config = new VoyageAIConfig();
- $config->apiKey = $api_key;
- return new Voyage3LiteEmbeddingGenerator($config);
- } elseif (strpos($model, 'voyage3') === 0) {
- $config = new VoyageAIConfig();
- $config->apiKey = $api_key;
- return new Voyage3EmbeddingGenerator($config);
- } else {
- return new OllamaEmbeddingGenerator($model);
- return new OpenAI3SmallEmbeddingGenerator($config);
- } elseif (strpos($model, 'mistral') === 0) {
- $config = new OpenAIConfig();
- $config->apiKey = $api_key;
- return new MistralEmbeddingGenerator($config);
- } elseif (strpos($model, 'voyage3-large') === 0) {
- $config = new VoyageAIConfig();
- $config->apiKey = $api_key;
- return new Voyage3LargeEmbeddingGenerator($config);
- } elseif (strpos($model, 'voyage3-lite') === 0) {
- $config = new VoyageAIConfig();
- {
- if (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'gpt-large') === 0) {
- return 3072;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'gpt-medium') === 0) {
- return 1536;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'mistral') === 0) {
- return 1024;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage3-large') === 0) {
- return 4096;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage3-lite') === 0) {
- return 384;
- 'voyage3-lite' => 300,
- 'nomic-embed-text' => 800,
- ];
- foreach ($chunkSizes as $modelPrefix => $size) {
- if (strpos($model, $modelPrefix) === 0) {
- return $size;
- }
- }
- return 500;
- *
- * @return int The embedding length in dimensions for the selected model
- */
- public static function getEmbeddingLength(): int
- {
- if (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'gpt-large') === 0) {
- return 3072;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'gpt-medium') === 0) {
- return 1536;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'mistral') === 0) {
- return 1024;
- return 1536;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'mistral') === 0) {
- return 1024;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage3-large') === 0) {
- return 4096;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage3-lite') === 0) {
- return 384;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage3') === 0) {
- return 1024;
- } else {
- return 1536;
- 'voyage3-lite' => 4000,
- 'nomic-embed-text' => 8192,
- ];
- foreach ($contextLengths as $modelPrefix => $length) {
- if (strpos($model, $modelPrefix) === 0) {
- return $length;
- }
- }
- return 4096;
- return new MistralEmbeddingGenerator($config);
- } elseif (strpos($model, 'voyage3-large') === 0) {
- $config = new VoyageAIConfig();
- $config->apiKey = $api_key;
- return new Voyage3LargeEmbeddingGenerator($config);
- } elseif (strpos($model, 'voyage3-lite') === 0) {
- $config = new VoyageAIConfig();
- $config->apiKey = $api_key;
- return new Voyage3LiteEmbeddingGenerator($config);
- } elseif (strpos($model, 'voyage3') === 0) {
- $config = new VoyageAIConfig();
- */
- private static function getApiKey(): string
- {
- $api_key = CLICSHOPPING_APP_CHATGPT_CH_API_KEY;
- if (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'mistral') === 0) {
- $api_key = CLICSHOPPING_APP_CHATGPT_CH_API_KEY_MISTRAL;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage') === 0) {
- $api_key = CLICSHOPPING_APP_CHATGPT_RA_API_KEY_VOYAGE_AI;
- }
- {
- if (strpos($model, 'gpt') === 0) {
- return !empty(CLICSHOPPING_APP_CHATGPT_CH_API_KEY);
- } elseif (strpos($model, 'mistral') === 0) {
- return !empty(CLICSHOPPING_APP_CHATGPT_CH_API_KEY_MISTRAL);
- } elseif (strpos($model, 'voyage') === 0) {
- return !empty(CLICSHOPPING_APP_CHATGPT_RA_API_KEY_VOYAGE_AI);
- } elseif (strpos($model, 'ollama') === 0) {
- return true;
- }
- return 1024;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage3-large') === 0) {
- return 4096;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage3-lite') === 0) {
- return 384;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage3') === 0) {
- return 1024;
- } else {
- return 1536;
- }
- }
- if (strpos($model, 'gpt-large') === 0) {
- $config = new OpenAIConfig();
- $config->apiKey = $api_key;
- return new OpenAI3LargeEmbeddingGenerator($config);
- } elseif (strpos($model, 'gpt-medium') === 0) {
- $config = new OpenAIConfig();
- $config->apiKey = $api_key;
- return new OpenAI3SmallEmbeddingGenerator($config);
- } elseif (strpos($model, 'mistral') === 0) {
- $config = new OpenAIConfig();
- */
- public static function getEmbeddingLength(): int
- {
- if (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'gpt-large') === 0) {
- return 3072;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'gpt-medium') === 0) {
- return 1536;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'mistral') === 0) {
- return 1024;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage3-large') === 0) {
- return 4096;
- * @param string $model
- * @return string
- */
- private static function getModelProvider(string $model): string
- {
- if (strpos($model, 'gpt') === 0) return 'openai';
- if (strpos($model, 'mistral') === 0) return 'mistral';
- if (strpos($model, 'voyage') === 0) return 'voyageai';
- if (strpos($model, 'nomic') === 0) return 'ollama';
- return 'unknown';
- return !empty(CLICSHOPPING_APP_CHATGPT_CH_API_KEY);
- } elseif (strpos($model, 'mistral') === 0) {
- return !empty(CLICSHOPPING_APP_CHATGPT_CH_API_KEY_MISTRAL);
- } elseif (strpos($model, 'voyage') === 0) {
- return !empty(CLICSHOPPING_APP_CHATGPT_RA_API_KEY_VOYAGE_AI);
- } elseif (strpos($model, 'ollama') === 0) {
- return true;
- }
- return false;
- }
- {
- $api_key = CLICSHOPPING_APP_CHATGPT_CH_API_KEY;
- if (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'mistral') === 0) {
- $api_key = CLICSHOPPING_APP_CHATGPT_CH_API_KEY_MISTRAL;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage') === 0) {
- $api_key = CLICSHOPPING_APP_CHATGPT_RA_API_KEY_VOYAGE_AI;
- }
- return $api_key;
- }
- * @return string
- */
- private static function getModelProvider(string $model): string
- {
- if (strpos($model, 'gpt') === 0) return 'openai';
- if (strpos($model, 'mistral') === 0) return 'mistral';
- if (strpos($model, 'voyage') === 0) return 'voyageai';
- if (strpos($model, 'nomic') === 0) return 'ollama';
- return 'unknown';
- }
- */
- private static function estimateTokenCount(string $text): int
- {
- $model = CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL;
- if (strpos($model, 'gpt') === 0 || strpos($model, 'voyage') === 0) {
- $avgCharsPerToken = 3.5;
- return (int)ceil(strlen($text) / $avgCharsPerToken);
- }
- return (int)ceil(strlen($text) / 4);
- */
- private static function getModelProvider(string $model): string
- {
- if (strpos($model, 'gpt') === 0) return 'openai';
- if (strpos($model, 'mistral') === 0) return 'mistral';
- if (strpos($model, 'voyage') === 0) return 'voyageai';
- if (strpos($model, 'nomic') === 0) return 'ollama';
- return 'unknown';
- }
- * @param string $model The embedding model to check
- * @return bool True if API keys are available, false otherwise
- */
- private static function checkApiKeys(string $model): bool
- {
- if (strpos($model, 'gpt') === 0) {
- return !empty(CLICSHOPPING_APP_CHATGPT_CH_API_KEY);
- } elseif (strpos($model, 'mistral') === 0) {
- return !empty(CLICSHOPPING_APP_CHATGPT_CH_API_KEY_MISTRAL);
- } elseif (strpos($model, 'voyage') === 0) {
- return !empty(CLICSHOPPING_APP_CHATGPT_RA_API_KEY_VOYAGE_AI);
- return 3072;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'gpt-medium') === 0) {
- return 1536;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'mistral') === 0) {
- return 1024;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage3-large') === 0) {
- return 4096;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage3-lite') === 0) {
- return 384;
- } elseif (strpos(CLICSHOPPING_APP_CHATGPT_RA_EMBEDDING_MODEL, 'voyage3') === 0) {
- return 1024;
- $oldFiles = array_merge(
- glob($oldCacheDir . 'Rag_Embedding_*.cache'),
- glob($oldCacheDir . 'embedding_*.cache')
- );
- $oldFiles = array_filter($oldFiles, function($file) {
- return strpos(basename($file), 'embedding_search_') !== 0;
- });
- $filesBefore += count($oldFiles);
- foreach ($oldFiles as $file) {
- @unlink($file);
- }
- $oldFilesAfter = array_merge(
- glob($oldCacheDir . 'Rag_Embedding_*.cache'),
- glob($oldCacheDir . 'embedding_*.cache')
- );
- $oldFilesAfter = array_filter($oldFilesAfter, function($file) {
- return strpos(basename($file), 'embedding_search_') !== 0;
- });
- $filesAfter += count($oldFilesAfter);
- }
- $results['embeddings'] = $filesBefore - $filesAfter;
- $oldFiles = array_merge(
- glob($oldCacheDir . 'Rag_Embedding_*.cache'),
- glob($oldCacheDir . 'embedding_*.cache')
- );
- $oldFiles = array_filter($oldFiles, function($file) {
- return strpos(basename($file), 'embedding_search_') !== 0;
- });
- $filesAfter += count($oldFiles);
- }
- $results['embedding'] = $filesBefore - $filesAfter;
- $supplierBonus = 0;
- $manufacturerBonus = 0;
- foreach ($supplierIndicators as $indicator) {
- if (strpos($query, $indicator) !== false) {
- $supplierBonus += 1.0;
- }
- }
- foreach ($manufacturerIndicators as $indicator) {
- if (abs($reviewScore - $sentimentScore) < 0.5) {
- $sentimentIndicators = ['sentiment', 'feeling', 'positive', 'negative', 'emotion', 'analysis'];
- $sentimentBonus = 0;
- foreach ($sentimentIndicators as $indicator) {
- if (strpos($query, $indicator) !== false) {
- $sentimentBonus += 0.5;
- }
- }
- if ($sentimentBonus > 0) {
- $supplierBonus += 1.0;
- }
- }
- foreach ($manufacturerIndicators as $indicator) {
- if (strpos($query, $indicator) !== false) {
- $manufacturerBonus += 1.0;
- }
- }
- $scores['suppliers']['score'] += $supplierBonus;
- foreach ($termGroups as $groupType => $terms) {
- $weight = $this->termWeights[$groupType] ?? 0.5;
- foreach ($terms as $term) {
- if (strpos($query, $term) !== false) {
- $domainScore += $weight;
- $matchedTerms[] = [
- 'term' => $term,
- 'type' => $groupType,
- 'weight' => $weight,
- return $pricing;
- }
- }
- // Détection par provider
- if (strpos($model, 'gpt') !== false) {
- return self::PRICING['gpt-3.5-turbo']; // Défaut OpenAI
- }
- if (strpos($model, 'claude') !== false) {
- return self::PRICING['claude-3-haiku']; // Défaut Anthropic (le moins cher)
- }
- return self::PRICING[$model];
- }
- // Match partiel (ex: "gpt-4-0613" match "gpt-4")
- foreach (self::PRICING as $knownModel => $pricing) {
- if (strpos($model, $knownModel) !== false) {
- return $pricing;
- }
- }
- // Détection par provider
- return self::PRICING['claude-3-haiku']; // Défaut Anthropic (le moins cher)
- }
- if (strpos($model, 'mistral') !== false) {
- return self::PRICING['mistral-small']; // Défaut Mistral
- }
- if (strpos($model, 'llama') !== false || strpos($model, 'ollama') !== false) {
- return [0.0, 0.0]; // Local, gratuit
- }
- return null;
- }
- // Détection par provider
- if (strpos($model, 'gpt') !== false) {
- return self::PRICING['gpt-3.5-turbo']; // Défaut OpenAI
- }
- if (strpos($model, 'claude') !== false) {
- return self::PRICING['claude-3-haiku']; // Défaut Anthropic (le moins cher)
- }
- if (strpos($model, 'mistral') !== false) {
- return self::PRICING['mistral-small']; // Défaut Mistral
- }
- return self::PRICING['gpt-3.5-turbo']; // Défaut OpenAI
- }
- if (strpos($model, 'claude') !== false) {
- return self::PRICING['claude-3-haiku']; // Défaut Anthropic (le moins cher)
- }
- if (strpos($model, 'mistral') !== false) {
- return self::PRICING['mistral-small']; // Défaut Mistral
- }
- if (strpos($model, 'llama') !== false || strpos($model, 'ollama') !== false) {
- return [0.0, 0.0]; // Local, gratuit
- }
- {
- $sql = trim(strtoupper($sql));
- if (strpos($sql, 'SELECT') === 0) return 'SELECT';
- if (strpos($sql, 'INSERT') === 0) return 'INSERT';
- if (strpos($sql, 'UPDATE') === 0) return 'UPDATE';
- if (strpos($sql, 'DELETE') === 0) return 'DELETE';
- return 'UNKNOWN';
- }
- $sql = trim(strtoupper($sql));
- if (strpos($sql, 'SELECT') === 0) return 'SELECT';
- if (strpos($sql, 'INSERT') === 0) return 'INSERT';
- if (strpos($sql, 'UPDATE') === 0) return 'UPDATE';
- if (strpos($sql, 'DELETE') === 0) return 'DELETE';
- return 'UNKNOWN';
- }
- /**
- */
- private function detectQueryType(string $sql): string
- {
- $sql = trim(strtoupper($sql));
- if (strpos($sql, 'SELECT') === 0) return 'SELECT';
- if (strpos($sql, 'INSERT') === 0) return 'INSERT';
- if (strpos($sql, 'UPDATE') === 0) return 'UPDATE';
- if (strpos($sql, 'DELETE') === 0) return 'DELETE';
- return 'UNKNOWN';
- private function detectQueryType(string $sql): string
- {
- $sql = trim(strtoupper($sql));
- if (strpos($sql, 'SELECT') === 0) return 'SELECT';
- if (strpos($sql, 'INSERT') === 0) return 'INSERT';
- if (strpos($sql, 'UPDATE') === 0) return 'UPDATE';
- if (strpos($sql, 'DELETE') === 0) return 'DELETE';
- return 'UNKNOWN';
- }
- $errorType = 'unknown';
- // Detect classification errors
- if (strpos($comment, 'wrong type') !== false ||
- strpos($comment, 'misclassified') !== false ||
- strpos($comment, 'should be analytics') !== false ||
- strpos($comment, 'should be semantic') !== false) {
- $isClassificationError = true;
- $errorType = 'misclassification';
- } elseif ($reason === 'irrelevant' || strpos($comment, 'irrelevant') !== false) {
- $isClassificationError = true;
- 'wrong category',
- 'incorrect classification'
- ];
- foreach ($classificationKeywords as $keyword) {
- if (strpos($comment, $keyword) !== false) {
- return true;
- }
- }
- if ($reason === 'irrelevant') {
- // Detect classification errors
- if (strpos($comment, 'wrong type') !== false ||
- strpos($comment, 'misclassified') !== false ||
- strpos($comment, 'should be analytics') !== false ||
- strpos($comment, 'should be semantic') !== false) {
- $isClassificationError = true;
- $errorType = 'misclassification';
- } elseif ($reason === 'irrelevant' || strpos($comment, 'irrelevant') !== false) {
- $isClassificationError = true;
- $errorType = 'irrelevant_result';
- $isClassificationError = false;
- $errorType = 'unknown';
- // Detect classification errors
- if (strpos($comment, 'wrong type') !== false ||
- strpos($comment, 'misclassified') !== false ||
- strpos($comment, 'should be analytics') !== false ||
- strpos($comment, 'should be semantic') !== false) {
- $isClassificationError = true;
- $errorType = 'misclassification';
- } elseif ($reason === 'irrelevant' || strpos($comment, 'irrelevant') !== false) {
- $isClassificationError = false;
- $errorType = 'unknown';
- // Detect classification errors
- if (strpos($comment, 'wrong type') !== false ||
- strpos($comment, 'misclassified') !== false ||
- strpos($comment, 'should be analytics') !== false ||
- strpos($comment, 'should be semantic') !== false) {
- $isClassificationError = true;
- $errorType = 'misclassification';
- strpos($comment, 'misclassified') !== false ||
- strpos($comment, 'should be analytics') !== false ||
- strpos($comment, 'should be semantic') !== false) {
- $isClassificationError = true;
- $errorType = 'misclassification';
- } elseif ($reason === 'irrelevant' || strpos($comment, 'irrelevant') !== false) {
- $isClassificationError = true;
- $errorType = 'irrelevant_result';
- } elseif ($reason === 'incomplete' && strpos($comment, 'wrong') !== false) {
- $isClassificationError = true;
- $errorType = 'partial_misclassification';
- $isClassificationError = true;
- $errorType = 'misclassification';
- } elseif ($reason === 'irrelevant' || strpos($comment, 'irrelevant') !== false) {
- $isClassificationError = true;
- $errorType = 'irrelevant_result';
- } elseif ($reason === 'incomplete' && strpos($comment, 'wrong') !== false) {
- $isClassificationError = true;
- $errorType = 'partial_misclassification';
- }
- if ($isClassificationError) {
- public static function hasTimePeriod(string $query): bool
- {
- $query = strtolower($query);
- foreach (self::$timePeriods as $period) {
- if (strpos($query, $period) !== false) {
- return true;
- }
- }
- return false;
- {
- $query = strtolower($query);
- $detected = [];
- foreach (self::$timePeriods as $period) {
- if (strpos($query, $period) !== false) {
- $detected[] = $period;
- }
- }
- return $detected;
- {
- $query = strtolower($query);
- $detected = [];
- foreach (self::$financialMetrics as $metric) {
- if (strpos($query, $metric) !== false) {
- $detected[] = $metric;
- }
- }
- return $detected;
- $errorType = 'unknown';
- // Detect classification errors
- if (strpos($comment, 'wrong type') !== false ||
- strpos($comment, 'misclassified') !== false ||
- strpos($comment, 'should be analytics') !== false ||
- strpos($comment, 'should be semantic') !== false) {
- $isClassificationError = true;
- $errorType = 'misclassification';
- } elseif ($reason === 'irrelevant' || strpos($comment, 'irrelevant') !== false) {
- $isClassificationError = true;
- $isClassificationError = false;
- $errorType = 'unknown';
- // Detect classification errors
- if (strpos($comment, 'wrong type') !== false ||
- strpos($comment, 'misclassified') !== false ||
- strpos($comment, 'should be analytics') !== false ||
- strpos($comment, 'should be semantic') !== false) {
- $isClassificationError = true;
- $errorType = 'misclassification';
- $isClassificationError = false;
- $errorType = 'unknown';
- // Detect classification errors
- if (strpos($comment, 'wrong type') !== false ||
- strpos($comment, 'misclassified') !== false ||
- strpos($comment, 'should be analytics') !== false ||
- strpos($comment, 'should be semantic') !== false) {
- $isClassificationError = true;
- $errorType = 'misclassification';
- } elseif ($reason === 'irrelevant' || strpos($comment, 'irrelevant') !== false) {
- // Detect classification errors
- if (strpos($comment, 'wrong type') !== false ||
- strpos($comment, 'misclassified') !== false ||
- strpos($comment, 'should be analytics') !== false ||
- strpos($comment, 'should be semantic') !== false) {
- $isClassificationError = true;
- $errorType = 'misclassification';
- } elseif ($reason === 'irrelevant' || strpos($comment, 'irrelevant') !== false) {
- $isClassificationError = true;
- $errorType = 'irrelevant_result';
- strpos($comment, 'misclassified') !== false ||
- strpos($comment, 'should be analytics') !== false ||
- strpos($comment, 'should be semantic') !== false) {
- $isClassificationError = true;
- $errorType = 'misclassification';
- } elseif ($reason === 'irrelevant' || strpos($comment, 'irrelevant') !== false) {
- $isClassificationError = true;
- $errorType = 'irrelevant_result';
- } elseif ($reason === 'incomplete' && strpos($comment, 'wrong') !== false) {
- $isClassificationError = true;
- $errorType = 'partial_misclassification';
- $isClassificationError = true;
- $errorType = 'misclassification';
- } elseif ($reason === 'irrelevant' || strpos($comment, 'irrelevant') !== false) {
- $isClassificationError = true;
- $errorType = 'irrelevant_result';
- } elseif ($reason === 'incomplete' && strpos($comment, 'wrong') !== false) {
- $isClassificationError = true;
- $errorType = 'partial_misclassification';
- }
- if ($isClassificationError) {
- return false;
- }
- }
- // Model not found in list - check by prefix as fallback
- return strpos($model, 'gpt-5') === 0;
- }
- /**
- * Get model context length limit
- *
- // Model-specific parameter mapping
- // GPT-4o-mini, GPT-4.1 series, GPT-5 series use max_completion_tokens
- if (strpos($model, 'gpt-4.1-mini') === 0 ||
- strpos($model, 'gpt-4.1') === 0 ||
- strpos($model, 'gpt-5') === 0) {
- $params['max_completion_tokens'] = $maxtoken;
- } else {
- // Default for GPT-4o, Anthropic, Mistral, LM Studio, and other models
- $params['max_tokens'] = $maxtoken;
- }
- {
- $params = [];
- // Model-specific parameter mapping
- // GPT-4o-mini, GPT-4.1 series, GPT-5 series use max_completion_tokens
- if (strpos($model, 'gpt-4.1-mini') === 0 ||
- strpos($model, 'gpt-4.1') === 0 ||
- strpos($model, 'gpt-5') === 0) {
- $params['max_completion_tokens'] = $maxtoken;
- } else {
- // Default for GPT-4o, Anthropic, Mistral, LM Studio, and other models
- $params = [];
- // Model-specific parameter mapping
- // GPT-4o-mini, GPT-4.1 series, GPT-5 series use max_completion_tokens
- if (strpos($model, 'gpt-4.1-mini') === 0 ||
- strpos($model, 'gpt-4.1') === 0 ||
- strpos($model, 'gpt-5') === 0) {
- $params['max_completion_tokens'] = $maxtoken;
- } else {
- // Default for GPT-4o, Anthropic, Mistral, LM Studio, and other models
- $params['max_tokens'] = $maxtoken;
- $models = self::getGptModel();
- foreach ($models as $modelInfo) {
- if ($modelInfo['id'] === $model) {
- // Check if this is a GPT-5 series model (uses reasoning API)
- if (strpos($modelInfo['id'], 'gpt-5') === 0) {
- return true;
- }
- return false;
- }
- public static function hasEntityKeyword(string $query): bool
- {
- $query = strtolower($query);
- foreach (SuperlativePatterns::$entityKeywords as $entity) {
- if (strpos($query, $entity) !== false) {
- return true;
- }
- }
- return false;
- {
- $query = strtolower($query);
- $detected = [];
- foreach (SuperlativePatterns::$entityKeywords as $entity) {
- if (strpos($query, $entity) !== false) {
- $detected[] = $entity;
- }
- }
- return $detected;
- // Register shutdown function to handle timeout gracefully
- register_shutdown_function(function() use ($seconds) {
- $error = error_get_last();
- if ($error && ($error['type'] === E_ERROR || $error['type'] === E_USER_ERROR)) {
- if (strpos($error['message'], 'Maximum execution time') !== false) {
- error_log('[INFO : TIME] TIMEOUT ERROR: Query exceeded ' . $seconds . ' seconds');
- if (ob_get_length()) ob_clean();
- header('Content-Type: application/json; charset=UTF-8');
- $part = trim($part);
- $partIntent = null;
- // Check analytics
- foreach ($analyticsKeywords as $keyword) {
- if (strpos($part, $keyword) !== false) {
- $partIntent = 'analytics';
- break;
- }
- }
- }
- // Check web_search
- if ($partIntent === null) {
- foreach ($webSearchKeywords as $keyword) {
- if (strpos($part, $keyword) !== false) {
- $partIntent = 'web_search';
- break;
- }
- }
- }
- $hasConjunction = false;
- $conjunctionUsed = '';
- foreach ($conjunctions as $conj) {
- if (strpos($query, $conj) !== false) {
- $hasConjunction = true;
- $conjunctionUsed = trim($conj);
- break;
- }
- }
- }
- // Check semantic
- if ($partIntent === null) {
- foreach ($semanticKeywords as $keyword) {
- if (strpos($part, $keyword) !== false) {
- $partIntent = 'semantic';
- break;
- }
- }
- }
- error_log("text_order_calculation length: " . strlen($orderCalculation) . " chars");
- error_log("text_query_examples length: " . strlen($queryExamples) . " chars");
- error_log("sqlFormatInstructions length: " . strlen($sqlFormatInstructions) . " chars");
- error_log("text_response_format length: " . strlen($responseFormat) . " chars");
- error_log("text_multi_token_rules length: " . strlen($multiTokenRules) . " chars");
- error_log("Contains 'MULTI-TOKEN' in multiTokenRules: " . (strpos($multiTokenRules, 'MULTI-TOKEN') !== false ? 'YES' : 'NO'));
- error_log("Contains 'ABSOLUTE RULE' in aggregationRules: " . (strpos($aggregationRules, 'ABSOLUTE RULE') !== false ? 'YES' : 'NO'));
- error_log("First 200 chars of multiTokenRules: " . substr($multiTokenRules, 0, 200));
- error_log("================================================================================");
- }
- error_log("text_query_examples length: " . strlen($queryExamples) . " chars");
- error_log("sqlFormatInstructions length: " . strlen($sqlFormatInstructions) . " chars");
- error_log("text_response_format length: " . strlen($responseFormat) . " chars");
- error_log("text_multi_token_rules length: " . strlen($multiTokenRules) . " chars");
- error_log("Contains 'MULTI-TOKEN' in multiTokenRules: " . (strpos($multiTokenRules, 'MULTI-TOKEN') !== false ? 'YES' : 'NO'));
- error_log("Contains 'ABSOLUTE RULE' in aggregationRules: " . (strpos($aggregationRules, 'ABSOLUTE RULE') !== false ? 'YES' : 'NO'));
- error_log("First 200 chars of multiTokenRules: " . substr($multiTokenRules, 0, 200));
- error_log("================================================================================");
- }
- // Construct complete message in the correct order
- error_log("DEBUG PromptBuilder::buildSystemMessage() - FINAL");
- error_log("================================================================================");
- error_log("Language ID: " . $this->languageId);
- error_log("Final message length: " . strlen($finalMessage));
- error_log("Contains 'CRITICAL RULES': " . (strpos($finalMessage, 'CRITICAL RULES') !== false ? 'YES' : 'NO'));
- error_log("Contains 'products_quantity': " . (strpos($finalMessage, 'products_quantity') !== false ? 'YES' : 'NO'));
- error_log("First 500 chars: " . substr($finalMessage, 0, 500));
- error_log("================================================================================");
- }
- return $finalMessage;
- while ($tableRow = $tablesResult->fetch()) {
- $tableName = array_values($tableRow)[0];
- // Skip non-clic tables
- if (strpos($tableName, 'clic_') !== 0) {
- continue;
- }
- $schema .= "Table: {$tableName}\n";
- error_log("================================================================================");
- error_log("DEBUG PromptBuilder::buildSystemMessage() - FINAL");
- error_log("================================================================================");
- error_log("Language ID: " . $this->languageId);
- error_log("Final message length: " . strlen($finalMessage));
- error_log("Contains 'CRITICAL RULES': " . (strpos($finalMessage, 'CRITICAL RULES') !== false ? 'YES' : 'NO'));
- error_log("Contains 'products_quantity': " . (strpos($finalMessage, 'products_quantity') !== false ? 'YES' : 'NO'));
- error_log("First 500 chars: " . substr($finalMessage, 0, 500));
- error_log("================================================================================");
- }
- if ($hasPriceKeyword) {
- foreach (WebSearchPatterns::$comparisonKeywords as $keyword) {
- if (strpos($query, $keyword) !== false) {
- // Additional check: ensure it's not just internal comparison
- // If query contains "database" or "internal", it's likely hybrid, not pure web_search
- if (strpos($query, 'database') === false && strpos($query, 'internal') === false) {
- $analysis['intent_type'] = 'web_search';
- $analysis['confidence'] = 0.90;
- $analysis['override_reason'] = "Price comparison detected: $keyword";
- $analysis['detection_method'] = 'pattern_post_filter';
- return $analysis;
- }
- // Only apply trends/news override if NO entity keywords AND NO financial keywords present
- if (!$hasEntityKeyword && !$hasFinancialKeyword) {
- foreach (WebSearchPatterns::$trendsNewsKeywords as $keyword) {
- if (strpos($query, $keyword) !== false) {
- $analysis['intent_type'] = 'web_search';
- $analysis['confidence'] = 0.95;
- $analysis['override_reason'] = "Trends/news keyword detected: $keyword (no entity/financial keywords)";
- $analysis['detection_method'] = 'pattern_post_filter';
- return $analysis;
- // ========================================================================
- // STEP 2: Check for competitor keywords (ENGLISH ONLY)
- // ========================================================================
- foreach (WebSearchPatterns::$competitorKeywords as $keyword) {
- if (strpos($query, $keyword) !== false) {
- $analysis['intent_type'] = 'web_search';
- $analysis['confidence'] = 0.95;
- $analysis['override_reason'] = "Competitor keyword detected: $keyword";
- $analysis['detection_method'] = 'pattern_post_filter';
- return $analysis;
- // Check if query has entity keywords (database query, not web search)
- // 🔧 MIGRATION: Use centralized EntityKeywordsPattern instead of WebSearchPatterns
- $hasEntityKeyword = false;
- foreach (WebSearchPatterns::getEntityKeywords() as $entity) {
- if (strpos($query, $entity) !== false) {
- $hasEntityKeyword = true;
- break;
- }
- }
- }
- // Check if query has financial metric keywords (analytics query, not web search)
- $hasFinancialKeyword = false;
- foreach ($financialMetricKeywords as $keyword) {
- if (strpos($query, $keyword) !== false) {
- $hasFinancialKeyword = true;
- break;
- }
- }
- }
- }
- if ($hasPriceKeyword) {
- foreach (WebSearchPatterns::$comparisonKeywords as $keyword) {
- if (strpos($query, $keyword) !== false) {
- // Additional check: ensure it's not just internal comparison
- // If query contains "database" or "internal", it's likely hybrid, not pure web_search
- if (strpos($query, 'database') === false && strpos($query, 'internal') === false) {
- $analysis['intent_type'] = 'web_search';
- $analysis['confidence'] = 0.90;
- $externalSites[] = strtolower($row['site_domain']);
- }
- // Check if query mentions any configured external site
- foreach ($externalSites as $site) {
- if (strpos($query, $site) !== false) {
- $analysis['intent_type'] = 'web_search';
- $analysis['confidence'] = 0.95;
- $analysis['override_reason'] = "External site detected: $site (from database)";
- $analysis['detection_method'] = 'pattern_post_filter';
- return $analysis;
- // Only check comparison if query is price-related
- $hasPriceKeyword = false;
- foreach (WebSearchPatterns::$priceKeywords as $priceWord) {
- if (strpos($query, $priceWord) !== false) {
- $hasPriceKeyword = true;
- break;
- }
- }
- // Suggestions based on the type of error
- if (strpos($errorMessage, 'Unknown column') !== false) {
- return CLICSHOPPING::getDef('text_column_reference_does_not_exist');
- }
- if (strpos($errorMessage, 'syntax error') !== false) {
- return CLICSHOPPING::getDef('text_sql_query_generated_error');
- }
- if (strpos($errorMessage, 'Table') !== false && strpos($errorMessage, 'doesn\'t exist') !== false) {
- return CLICSHOPPING::getDef('text_table_referenced_does_not_exist');
- // Load language definitions
- $CLICSHOPPING_Language = Registry::get('Language');
- DomainConfig::loadLanguageFile('rag_error_handler');
- // Suggestions based on the type of error
- if (strpos($errorMessage, 'Unknown column') !== false) {
- return CLICSHOPPING::getDef('text_column_reference_does_not_exist');
- }
- if (strpos($errorMessage, 'syntax error') !== false) {
- return CLICSHOPPING::getDef('text_sql_query_generated_error');
- if (strpos($errorMessage, 'syntax error') !== false) {
- return CLICSHOPPING::getDef('text_sql_query_generated_error');
- }
- if (strpos($errorMessage, 'Table') !== false && strpos($errorMessage, 'doesn\'t exist') !== false) {
- return CLICSHOPPING::getDef('text_table_referenced_does_not_exist');
- }
- // Generic suggestion
- return CLICSHOPPING::getDef('text_error_executing_query');
- foreach ($entityColumnMap as $columnName => $entityType) {
- $tableName = str_replace('_id', '', $columnName);
- // Check if table name appears in query
- if (strpos($sqlLower, "from {$tableName}") !== false ||
- strpos($sqlLower, "from clic_{$tableName}") !== false ||
- strpos($sqlLower, "join {$tableName}") !== false ||
- strpos($sqlLower, "join clic_{$tableName}") !== false) {
- return [
- 'entity_id' => null, // Can't extract ID from query alone
- // Check for table names in FROM clause
- foreach ($entityColumnMap as $columnName => $entityType) {
- $tableName = str_replace('_id', '', $columnName);
- // Check if table name appears in query
- if (strpos($sqlLower, "from {$tableName}") !== false ||
- strpos($sqlLower, "from clic_{$tableName}") !== false ||
- strpos($sqlLower, "join {$tableName}") !== false ||
- strpos($sqlLower, "join clic_{$tableName}") !== false) {
- return [
- // Check if table name appears in query
- if (strpos($sqlLower, "from {$tableName}") !== false ||
- strpos($sqlLower, "from clic_{$tableName}") !== false ||
- strpos($sqlLower, "join {$tableName}") !== false ||
- strpos($sqlLower, "join clic_{$tableName}") !== false) {
- return [
- 'entity_id' => null, // Can't extract ID from query alone
- 'entity_type' => $entityType,
- ];
- $tableName = str_replace('_id', '', $columnName);
- // Check if table name appears in query
- if (strpos($sqlLower, "from {$tableName}") !== false ||
- strpos($sqlLower, "from clic_{$tableName}") !== false ||
- strpos($sqlLower, "join {$tableName}") !== false ||
- strpos($sqlLower, "join clic_{$tableName}") !== false) {
- return [
- 'entity_id' => null, // Can't extract ID from query alone
- 'entity_type' => $entityType,
- $client = ProviderManager::getOpenAiGpt($parameters);
- } elseif (strpos($model, 'anth') === 0) {
- $client = ProviderManager::getAnthropicChat($model, $maxtoken);
- } elseif (strpos($model, 'mistral') === 0) {
- $client = ProviderManager::getMistralChat($model, $maxtoken);
- } elseif (strpos($model, 'ollama') === 0 || str_contains($model, ':latest')) {
- $client = ProviderManager::getOllamaChat($model);
- } elseif (strpos($model, 'openai/') === 0) {
- $client = ProviderManager::getLmStudioChat($model);
- } else {
- $client = ProviderManager::getLmStudioChat($model);
- $client = ProviderManager::getAnthropicChat($model, $maxtoken);
- } elseif (strpos($model, 'mistral') === 0) {
- $client = ProviderManager::getMistralChat($model, $maxtoken);
- } elseif (strpos($model, 'ollama') === 0 || str_contains($model, ':latest')) {
- $client = ProviderManager::getOllamaChat($model);
- } elseif (strpos($model, 'openai/') === 0) {
- $client = ProviderManager::getLmStudioChat($model);
- } else {
- $client = ProviderManager::getLmStudioChat($model);
- }
- define('CLICSHOPPING_APP_CHATGPT_CH_MODEL', 'gpt-5-mini');
- }
- $model = $engine ?? CLICSHOPPING_APP_CHATGPT_CH_MODEL;
- if (strpos($model, 'gpt') === 0) {
- $maxtoken = self::getMaxTokens($maxtoken);
- $temperature = $temperature ?? (float)CLICSHOPPING_APP_CHATGPT_CH_TEMPERATURE;
- $tokenParams = ModelManager::getModelApiParameters($model, $maxtoken);
- if ($engine !== null) {
- $parameters['model'] = $engine;
- }
- $client = ProviderManager::getOpenAiGpt($parameters);
- } elseif (strpos($model, 'anth') === 0) {
- $client = ProviderManager::getAnthropicChat($model, $maxtoken);
- } elseif (strpos($model, 'mistral') === 0) {
- $client = ProviderManager::getMistralChat($model, $maxtoken);
- } elseif (strpos($model, 'ollama') === 0 || str_contains($model, ':latest')) {
- $client = ProviderManager::getOllamaChat($model);
- }
- $client = ProviderManager::getOpenAiGpt($parameters);
- } elseif (strpos($model, 'anth') === 0) {
- $client = ProviderManager::getAnthropicChat($model, $maxtoken);
- } elseif (strpos($model, 'mistral') === 0) {
- $client = ProviderManager::getMistralChat($model, $maxtoken);
- } elseif (strpos($model, 'ollama') === 0 || str_contains($model, ':latest')) {
- $client = ProviderManager::getOllamaChat($model);
- } elseif (strpos($model, 'openai/') === 0) {
- $client = ProviderManager::getLmStudioChat($model);
- {
- $keywords = self::getModificationKeywords();
- $queryLower = mb_strtolower($query);
- foreach ($keywords as $keyword) {
- if (strpos($queryLower, $keyword) !== false) {
- return $keyword;
- }
- }
- return null;
- {
- $keywords = self::getModificationKeywords();
- $queryLower = mb_strtolower($query);
- foreach ($keywords as $keyword) {
- if (strpos($queryLower, $keyword) !== false) {
- return true;
- }
- }
- return false;
- // Process special vector fields
- $vector_fields = [];
- foreach ($data as $field => $value) {
- // Check if the field is meant to be a vector and starts with 'vec_'
- if (substr($field, 0, 4) === 'vec_') {
- $actual_field = substr($field, 4); // Get the actual field name without 'vec_' prefix
- $vector_fields[$actual_field] = $value; // Store the vector value
- unset($data[$field]); // Remove the special prefixed field
- }
- }
- if (!preg_match('/^[a-zA-Z0-9_]*$/', $prefix)) {
- throw new \InvalidArgumentException('Invalid table prefix');
- }
- // Ajout d'un underscore terminal si le préfixe est non vide et ne se termine pas déjà par un underscore
- if ($prefix !== '' && substr($prefix, -1) !== '_') {
- $prefix .= '_';
- }
- // Substitution sûre des tokens
- return preg_replace_callback('/:table_([a-zA-Z0-9_]+)/', function ($matches) use ($prefix) {
- if (is_null($temperature)) {
- $temperature = 0.5;
- }
- if (strpos(CLICSHOPPING_APP_CHATGPT_CH_MODEL, 'gpt') === 0) {
- $engine = CLICSHOPPING_APP_CHATGPT_CH_MODEL;
- $response = Gpt::getGptResponse($question, $maxtoken, $temperature, $engine);
- } else {
- //ollama
- $response = Gpt::getGptResponse($question, $maxtoken, $temperature);
- */
- private static function containsMaliciousPatterns(string $text): bool
- {
- $lowerText = strtolower($text);
- foreach (ObfuscationPatterns::$maliciousKeywords as $keyword) {
- if (strpos($lowerText, $keyword) !== false) {
- return true;
- }
- }
- return false;
- // Extract type from column info (now returns array with 'type' and 'comment')
- $type = is_array($columnInfo) ? $columnInfo['type'] : $columnInfo;
- // Detect ID columns that could be foreign keys
- if (preg_match('/_id$/', $column) && strpos($type, 'int') !== false) {
- $relatedTable = str_replace('_id', '', $column);
- // Validate related table name
- $safeRelatedTable = InputValidator::sanitizeIdentifier($relatedTable);
- // Extract type from column info (now returns array with 'type' and 'comment')
- $type = is_array($columnInfo) ? $columnInfo['type'] : $columnInfo;
- // Detect ID columns that could be foreign keys
- if (preg_match('/_id$/', $column) && strpos($type, 'int') !== false) {
- $relatedTable = str_replace('_id', '', $column);
- // Validate related table name
- $safeRelatedTable = InputValidator::sanitizeIdentifier($relatedTable);
- $metrics['total_interactions'] = $memStats['total_interactions'] ?? 0;
- $metrics['memory_size'] = $memStats['total_size'] ?? 0;
- }
- break;
- case strpos($name, 'Correction') !== false:
- $metrics['type'] = 'correction';
- if (method_exists($component, 'getLearningStats')) {
- $learnStats = $component->getLearningStats();
- $metrics['correction_accuracy'] = $learnStats['correction_accuracy'] ?? 0;
- $metrics['learned_patterns'] = $learnStats['learned_patterns'] ?? 0;
- $metrics['correction_accuracy'] = $learnStats['correction_accuracy'] ?? 0;
- $metrics['learned_patterns'] = $learnStats['learned_patterns'] ?? 0;
- }
- break;
- case strpos($name, 'WebSearch') !== false:
- $metrics['type'] = 'web_search';
- $metrics['cache_hit_rate'] = $this->extractCacheHitRate($componentStats);
- break;
- }
- $metrics['type'] = 'planner';
- $metrics['total_plans'] = $componentStats['total_plans_created'] ?? 0;
- $metrics['avg_steps'] = $componentStats['avg_steps_per_plan'] ?? 0;
- break;
- case strpos($name, 'Memory') !== false:
- $metrics['type'] = 'memory';
- if (method_exists($component, 'getStats')) {
- $memStats = $component->getStats();
- $metrics['total_interactions'] = $memStats['total_interactions'] ?? 0;
- $metrics['memory_size'] = $memStats['total_size'] ?? 0;
- $metrics = array_merge($metrics, $componentStats);
- }
- // Méthodes spécifiques par type de composant
- switch (true) {
- case strpos($name, 'Planner') !== false:
- $metrics['type'] = 'planner';
- $metrics['total_plans'] = $componentStats['total_plans_created'] ?? 0;
- $metrics['avg_steps'] = $componentStats['avg_steps_per_plan'] ?? 0;
- break;
- $schema .= 'NULL, ';
- } elseif (!\is_null($Qrows->value($i))) {
- $row = $Qrows->value($i);
- // Check if this is a VECTOR column
- if (isset($columnTypes[$i]) && strpos(strtolower($columnTypes[$i]), 'vector') !== false) {
- // VECTOR data is already in the correct format from MariaDB
- // Just escape it properly
- $row = addslashes($row);
- } else {
- $row = addslashes($row);
- $is_vector_type = preg_match('/^vector/i', $columnType);
- // Check if default value is already quoted (starts and ends with single quote)
- $is_already_quoted = (strlen($default_value) >= 2 &&
- substr($default_value, 0, 1) === "'" &&
- substr($default_value, -1) === "'");
- // Skip empty string defaults for numeric types (invalid SQL)
- if ($is_numeric_type && $default_value === '') {
- // Don't add default clause - let MySQL use its default behavior
- } elseif ($is_datetime_type && $default_value === '') {
- // Check if column type is VECTOR (cannot have empty string default)
- $is_vector_type = preg_match('/^vector/i', $columnType);
- // Check if default value is already quoted (starts and ends with single quote)
- $is_already_quoted = (strlen($default_value) >= 2 &&
- substr($default_value, 0, 1) === "'" &&
- substr($default_value, -1) === "'");
- // Skip empty string defaults for numeric types (invalid SQL)
- if ($is_numeric_type && $default_value === '') {
- // Don't add default clause - let MySQL use its default behavior
- {
- $markers = self::getResetMarkers();
- $queryLower = mb_strtolower($query);
- foreach ($markers as $marker) {
- if (strpos($queryLower, $marker) !== false) {
- return true;
- }
- }
- return false;
- $markers = self::getResetMarkers();
- $queryLower = mb_strtolower($query);
- $foundMarkers = [];
- foreach ($markers as $marker) {
- if (strpos($queryLower, $marker) !== false) {
- $foundMarkers[] = $marker;
- }
- }
- $hasReset = !empty($foundMarkers);
- }
- }
- foreach ($tablesList as $tableName) {
- // Skip technical tables
- if (strpos($tableName, '_embedding') !== false || strpos($tableName, $prefix . 'rag_') === 0) {
- continue;
- }
- $this->totalTables++;
- }
- }
- foreach ($tablesList as $tableName) {
- // Skip technical tables
- if (strpos($tableName, '_embedding') !== false || strpos($tableName, $prefix . 'rag_') === 0) {
- continue;
- }
- $this->totalTables++;
- $headers = ['status_code' => $statusCode];
- while (($line = fgets($socket)) !== false) {
- $line = trim($line);
- if ($line === '') break;
- if (strpos($line, ':') !== false) {
- [$name, $value] = explode(':', $line, 2);
- $headers[strtolower(trim($name))] = trim($value);
- }
- }
- 'Connection' => 'close'
- ];
- if (!empty($parameters['header'])) {
- foreach ($parameters['header'] as $header) {
- if (strpos($header, ':') !== false) {
- [$name, $value] = explode(':', $header, 2);
- $headers[trim($name)] = trim($value);
- }
- }
- }
- while ($Qembeddings->fetch()) {
- $tableName = $Qembeddings->value('table_name');
- // Skip technical tables (should not be in embeddings, but filter just in case)
- if (strpos($tableName, '_embedding') !== false || strpos($tableName, 'clic_rag_') === 0) {
- continue;
- }
- $embeddingText = $Qembeddings->value('embedding_text');
- while ($Qembeddings->fetch()) {
- $tableName = $Qembeddings->value('table_name');
- // Skip technical tables (should not be in embeddings, but filter just in case)
- if (strpos($tableName, '_embedding') !== false || strpos($tableName, 'clic_rag_') === 0) {
- continue;
- }
- $embeddingText = $Qembeddings->value('embedding_text');
- if (self::$prefixDb === null) {
- self::$prefixDb = CLICSHOPPING::getConfig('db_table_prefix');
- }
- // Add prefix if not already present
- if (strpos($tableName, self::$prefixDb) !== 0) {
- $tableName = self::$prefixDb . $tableName;
- }
- $connection = self::getEntityManager()->getConnection();
- $affectedRows = $connection->insert($tableName, $data);
- if (self::$prefixDb === null) {
- self::$prefixDb = CLICSHOPPING::getConfig('db_table_prefix');
- }
- // Add prefix if not already present
- if (strpos($tableName, self::$prefixDb) !== 0) {
- $tableName = self::$prefixDb . $tableName;
- }
- $connection = self::getEntityManager()->getConnection();
- return $connection->delete($tableName, $criteria);
- return true;
- }
- // Fuzzy match
- foreach ($allFields as $field) {
- if (strpos($field, $word) !== false || strpos($word, $field) !== false) {
- return true;
- }
- }
- // Check against non-database words
- if (self::$prefixDb === null) {
- self::$prefixDb = CLICSHOPPING::getConfig('db_table_prefix');
- }
- // Add prefix if not already present
- if (strpos($tableName, self::$prefixDb) !== 0) {
- $tableName = self::$prefixDb . $tableName;
- }
- $connection = self::getEntityManager()->getConnection();
- return $connection->update($tableName, $data, $criteria);
- if ($serverVersion) {
- $serverVersionLower = strtolower($serverVersion);
- // Extract and properly format the version
- if (strpos($serverVersionLower, 'mariadb') !== false) {
- // Typical format: "10.11.8-MariaDB" or "11.7.0-mariadb"
- preg_match('/(\d+\.\d+\.\d+)/', $serverVersion, $matches);
- if (!empty($matches[1])) {
- $versionNumber = $matches[1];
- // 🔧 FIX: If interpretations array is empty, use defaults based on ambiguity type
- if (empty($availableInterpretations)) {
- $ambiguityType = $ambiguityAnalysis['ambiguity_type'] ?? '';
- // For quantification queries, default to count and sum
- if (strpos($ambiguityType, 'quantification') !== false) {
- $availableInterpretations = ['count', 'sum'];
- if ($this->debug) {
- $this->logger->logSecurityEvent(
- "AmbiguityOptimizer: Empty interpretations array, using defaults for quantification: count, sum",
- // Clear from Memcached (note: Memcached doesn't support key pattern matching easily)
- // We'll rely on TTL expiration for Memcached
- // Clear from file cache
- foreach ($this->promptCache as $key => $data) {
- if (strpos($key, $prefix) === 0) {
- unset($this->promptCache[$key]);
- $cleared++;
- }
- }
- // iterate through all keys. We'll rely on TTL expiration for Memcached.
- // For production use, consider using Redis for better invalidation support.
- // Invalidate from file cache
- foreach ($this->promptCache as $key => $data) {
- if (strpos($key, self::CACHE_TYPE_SQL) === 0) {
- if (isset($data['tables_used']) && in_array($cleanTableName, $data['tables_used'], true)) {
- unset($this->promptCache[$key]);
- $invalidated++;
- }
- }
- }
- }
- // Invalidate from file cache
- foreach ($this->promptCache as $key => $data) {
- if (strpos($key, self::CACHE_TYPE_SQL) === 0) {
- if (isset($data['tables_used'])) {
- $intersection = array_intersect($cleanTableNames, $data['tables_used']);
- if (!empty($intersection)) {
- unset($this->promptCache[$key]);
- $invalidated++;
- $cleaned = $matches[1];
- }
- // STEP 4: Remove leading/trailing quotes if they wrap the entire string
- if ((substr($cleaned, 0, 1) === '"' && substr($cleaned, -1) === '"') ||
- (substr($cleaned, 0, 1) === "'" && substr($cleaned, -1) === "'")) {
- $cleaned = substr($cleaned, 1, -1);
- }
- return trim($cleaned);
- }
- if (preg_match('/is:\s*["\'](.+?)["\']$/i', $cleaned, $matches)) {
- $cleaned = $matches[1];
- }
- // STEP 4: Remove leading/trailing quotes if they wrap the entire string
- if ((substr($cleaned, 0, 1) === '"' && substr($cleaned, -1) === '"') ||
- (substr($cleaned, 0, 1) === "'" && substr($cleaned, -1) === "'")) {
- $cleaned = substr($cleaned, 1, -1);
- }
- return trim($cleaned);
- $cleaned = $matches[1];
- }
- // STEP 4: Remove leading/trailing quotes if they wrap the entire string
- if ((substr($cleaned, 0, 1) === '"' && substr($cleaned, -1) === '"') ||
- (substr($cleaned, 0, 1) === "'" && substr($cleaned, -1) === "'")) {
- $cleaned = substr($cleaned, 1, -1);
- }
- return trim($cleaned);
- }
- if (preg_match('/is:\s*["\'](.+?)["\']$/i', $cleaned, $matches)) {
- $cleaned = $matches[1];
- }
- // STEP 4: Remove leading/trailing quotes if they wrap the entire string
- if ((substr($cleaned, 0, 1) === '"' && substr($cleaned, -1) === '"') ||
- (substr($cleaned, 0, 1) === "'" && substr($cleaned, -1) === "'")) {
- $cleaned = substr($cleaned, 1, -1);
- }
- return trim($cleaned);
- if ($value === null) {
- return null;
- }
- // Si la valeur est déjà une chaîne formatée correctement, la retourner telle quelle
- if (is_string($value) && strpos($value, '[') === 0) {
- return $value;
- }
- // Convertir le tableau en chaîne formatée pour MariaDB
- if (is_array($value)) {
- * @return bool True if it contains a comparison operator, false otherwise.
- */
- private function isComparisonClause(string $clause): bool
- {
- foreach (self::ALLOWED_OPERATORS as $operator) {
- if (strpos($clause, $operator) !== false) {
- return true;
- }
- }
- return false;
- }
- {
- // Remove backticks and quotes
- $tableName = str_replace(['`', '"', "'"], '', $tableName);
- // Remove database prefix (e.g., database.table -> table)
- if (strpos($tableName, '.') !== false) {
- $parts = explode('.', $tableName);
- $tableName = end($parts);
- }
- // Remove alias (take only the first word)
- $isHtmlContent = false;
- if (isset($results['text_response']) && !empty($results['text_response'])) {
- $interpretationText = $results['text_response'];
- // Check if text_response contains HTML
- $isHtmlContent = (strpos($interpretationText, '<div') !== false || strpos($interpretationText, '<p>') !== false);
- } elseif (isset($results['interpretation']) && $results['interpretation'] !== 'Array') {
- $interpretationText = $results['interpretation'];
- }
- if (!empty($interpretationText)) {
- if ($path === '' || $path === '.' . $separator) {
- return $systemroot;
- }
- if (substr($path, 0, 3) === '..' . $separator) {
- $path = $systemroot . $path;
- }
- // Normalize path
- $path = rtrim($path, $separator) . $separator;
- if ($path[0] === $separator || strpos($path, $systemroot) === 0) {
- return $path;
- }
- // Relative path from 'Here'
- if (substr($path, 0, 2) === '.' . $separator || $path[0] !== '.') {
- $arrn = preg_split('/\\' . $separator . '/', $path, -1, PREG_SPLIT_NO_EMPTY);
- if ($arrn[0] !== '.') {
- array_unshift($arrn, '.');
- }
- $arrn[0] = rtrim($base, $separator);
- // Normalize path
- $path = rtrim($path, $separator) . $separator;
- // Absolute path
- if ($path[0] === $separator || strpos($path, $systemroot) === 0) {
- return $path;
- }
- // Relative path from 'Here'
- if (substr($path, 0, 2) === '.' . $separator || $path[0] !== '.') {
- // Check if the path is within allowed directories
- $isAllowed = false;
- foreach ($allowedDirs as $allowedDir) {
- $normalizedAllowedDir = str_replace('\\', '/', realpath($allowedDir));
- if ($normalizedAllowedDir && strpos($realPath, $normalizedAllowedDir) === 0) {
- $isAllowed = true;
- break;
- }
- }
- foreach ($files as $sm) {
- $result['file'][] = ['files_name' => $sm];
- }
- foreach ($result['file'] as &$module) {
- if (strpos($module['files_name'], '.') !== false) {
- $class = substr($module['files_name'], 0, strrpos($module['files_name'], '.'));
- $this->startService($class);
- }
- }
- }
- * @param string $ip The IP address to check
- * @param string $range The CIDR range (e.g., '
- */
- public static function ipInRange(string $ip, string $range): bool
- {
- if (strpos($range, '/') === false) {
- return $ip === $range;
- }
- list($subnet, $bits) = explode('/', $range);
- $ip = ip2long($ip);
- if ($this->debug) {
- error_log("AmbiguousQueryDetector: LLM returned empty interpretations for ambiguous query, generating defaults");
- }
- // Generate default interpretations based on ambiguity type
- if (strpos($ambiguityType, 'quantification') !== false) {
- // For quantification queries: count vs sum
- $interpretations = [
- [
- 'type' => 'count',
- 'label' => 'Count of items',
- 'label' => 'Sum of quantities',
- 'description' => 'Sum the total quantity',
- 'sql_hint' => 'Use SUM(quantity_field)'
- ]
- ];
- } else if (strpos($ambiguityType, 'scope') !== false) {
- // For scope queries: all vs recent
- $interpretations = [
- [
- 'type' => 'all',
- 'label' => 'All items',
- * @return bool True if the text contains valid JSON, false otherwise.
- */
- private static function validateJsonStructure(string $text): bool
- {
- // If the text contains JSON, check its validity
- if (strpos($text, '{') !== false || strpos($text, '[') !== false) {
- return json_last_error() === JSON_ERROR_NONE;
- }
- return true; // No JSON detected
- }
- $isHtmlContent = false;
- if (isset($results['text_response']) && !empty($results['text_response'])) {
- $interpretationText = $results['text_response'];
- // Check if text_response contains HTML
- $isHtmlContent = (strpos($interpretationText, '<div') !== false || strpos($interpretationText, '<p>') !== false);
- } elseif (isset($results['response']) && !empty($results['response'])) {
- $interpretationText = $results['response'];
- } elseif (isset($results['interpretation']) && $results['interpretation'] !== 'Array') {
- $interpretationText = $results['interpretation'];
- }
gyakutsuki
gyakutsuki
gyakutsuki
gyakutsuki