Files
crm.clientright.ru/iacode12.php

1066 lines
48 KiB
PHP
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ini_set('log_errors', 1);
ini_set('error_log', 'aiassist/logs/php_errors.log');
// Подключаем модули (если они есть)
require_once 'aiassist/elastic.php'; // Функции для работы с ElasticSearch
require_once 'aiassist/crm_handler.php'; // Функции для связи с CRM
require_once 'aiassist/search.php'; // Функции для поиска (searchIndex, searchCases, parseSearchResults)
require_once 'aiassist/ner.php'; // Функции для NER (извлечение сущностей)
require_once 'aiassist/vectorizer.php'; // Функции для вычисления эмбеддингов (getTextEmbedding)
// Настройки OpenAI API и другие константы
const OPENAI_API_KEY = 'sk-GS24OxHQYfq8ErW5CRLoN5F1CfJPxNsY';
const OPENAI_ASSISTANT_API = 'https://api.proxyapi.ru/openai/v1/assistants';
const OPENAI_FILES_API = 'https://api.proxyapi.ru/openai/v1/files';
const OPENAI_THREADS_API = 'https://api.proxyapi.ru/openai/v1/threads';
const OPENAI_VECTOR_STORES_API = 'https://api.proxyapi.ru/openai/v1/vector_stores';
const OPENAI_VISION_API = 'https://api.proxyapi.ru/openai/v1/chat/completions'; // для описания изображений
const NSFW_MODERATION_API = 'https://api.proxyapi.ru/v1/moderation/nsfw';
const LOG_FILE = 'aiassist/logs/testlog.log';
const ASSISTANT_ID = 'asst_suGt51aoepXUkJiC0t3vobeG';
const ASSISTANT_NAME = 'Clientright';
// Для корректной обработки кириллицы
setlocale(LC_ALL, 'ru_RU.UTF-8');
// Подключение к БД (Vtiger CRM)
$dsn = 'mysql:host=localhost;port=3306;dbname=ci20465_72new;charset=utf8mb4';
$user = 'ci20465_72new';
$password = 'EcY979Rn';
$assistantId = ASSISTANT_ID; // Объявляем переменную
try {
$pdo = new PDO($dsn, $user, $password, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
} catch (PDOException $e) {
logMessage("Ошибка подключения к БД: " . $e->getMessage());
die("Ошибка подключения к БД");
}
function logMessage($message) {
if (!is_dir('aiassist/logs')) {
mkdir('aiassist/logs', 0777, true);
}
file_put_contents(LOG_FILE, date('Y-m-d H:i:s') . " - " . $message . "\n", FILE_APPEND | LOCK_EX);
}
function normalizeFilename($filename) {
$filename = iconv('UTF-8', 'UTF-8//IGNORE', $filename);
return preg_replace('/[^\w\.]+/u', '_', $filename);
}
function cleanQueryText($text) {
// Удаляем непечатные символы (например, управляющие символы)
$text = preg_replace('/[\x00-\x1F\x7F]/u', ' ', $text);
// Заменяем множественные пробелы на один
$text = preg_replace('/\s+/', ' ', $text);
// Обрезаем пробелы в начале и конце строки
return trim($text);
}
/* ===================== Основной скрипт обработки ===================== */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 1. Получаем ID кейса из $_POST или JSON-тела запроса
$id = $_POST['id'] ?? null;
if (!$id) {
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? null;
}
if (!$id || !is_numeric($id)) {
logMessage("Ошибка: Некорректный ID.");
die(json_encode(["status" => "error", "message" => "Некорректный ID."], JSON_UNESCAPED_UNICODE));
}
$GLOBALS['caseId'] = $id;
// 2. Извлекаем документы из CRM
$documents = fetchDocumentData($pdo, $id);
if (empty($documents)) {
logMessage("Документы не найдены для ID: $id");
die("Документы не найдены для ID: $id");
}
logMessage("Документы получены из БД: " . json_encode($documents, JSON_UNESCAPED_UNICODE));
// Создаем массив путей к файлам
$filePathList = array_map(function($doc) {
return $doc['filepath'];
}, $documents);
// 3. Проверка наличия ранее сохранённого анализа (если реализовано кеширование)
$previousAnalysis = checkPreviousAnalysis($id, $filePathList);
logMessage("DEBUG: Значение предыдущего анализа: " . print_r($previousAnalysis, true));
if ($previousAnalysis) {
logMessage("Найден сохранённый анализ, отправляем его в CRM.");
# sendAnalysisToCRM($id, $previousAnalysis);
exit;
} else {
logMessage("Сохранённый анализ не найден, продолжаем обработку.");
}
// 4. Подготовка документов: загрузка файлов в vector store, извлечение текста (pdftotext/OCR/NSFW)
$uploadResult = createVectorStoreAndUploadFiles($filePathList);
if (!$uploadResult) {
logMessage("Ошибка создания Vector Store или загрузки файлов");
die("Ошибка создания Vector Store или загрузки файлов");
}
$vectorStoreId = $uploadResult['vectorStoreId'];
$uploadedFileIds = $uploadResult['fileIds'];
if (!updateAssistantWithVectorStore($vectorStoreId)) {
logMessage("Ошибка обновления ассистента с Vector Store");
die("Ошибка обновления ассистента");
}
$combinedContent = analyzeDocuments($documents, $uploadedFileIds);
logMessage("Собранный контент для анализа:\n" . $combinedContent);
if (empty($combinedContent)) {
logMessage("Ошибка: анализ документов не вернул результатов");
die("Ошибка: анализ документов не вернул результатов");
}
// 5. Формируем объединенный список идентификаторов файлов
$fileIdCombined = implode(',', array_values($uploadedFileIds));
logMessage("Объединённый список идентификаторов файлов: " . $fileIdCombined);
// 6. Формирование итогового контекста (prompt) для GPT
// Подключаем файл aiassist/search_context.php, который формирует prompt с учетом результатов поиска по разным индексам.
$finalPrompt = include 'aiassist/search_context.php';
logMessage("Итоговый prompt для GPT:\n" . $finalPrompt);
// 7. Создаем новый тред для общения с GPT
$threadId = createNewThread();
if (!$threadId) {
logMessage("Ошибка создания треда");
die("Ошибка создания треда");
}
// 8. Отправляем итоговый prompt в GPT через ассистента (stream) - запрос второго анализа в GPT
//******************************************************************************************************** */
/* $analysis = analyzeDocumentWithAssistantStream($threadId, ASSISTANT_ID, $fileIdCombined, $finalPrompt);
if (!$analysis) {
logMessage("❌ Ошибка анализа совокупного запроса");
die("Ошибка анализа совокупного запроса");
}
*/
//**********************************************************************************************************/
// 9. Сохраняем результат анализа в ElasticSearch и отправляем его в CRM
//********************************************************************************************************/
/*
$saveResult = saveAnalysisToElasticSearch($id, $analysis, $filePathList);
if ($saveResult) {
logMessage("✅ Анализ успешно сохранён в ElasticSearch.");
} else {
logMessage("❌ Ошибка при сохранении анализа в ElasticSearch.");
}
*/
//******************************************************************************************************/
// 10. эмбилдин
// Получаем объединённый и очищенный текст для эмбеддинга:
// 1. Анализируем текст обращения через GPT-4
//$parsedResponse = extractCaseDetailsWithGPT($queryText);
// Проверяем, пришли ли все нужные данные
$category = $parsedResponse['category'] ?? "Не определено";
$articles = $parsedResponse['articles'] ?? [];
$claim_amount = $parsedResponse['claim_amount'] ?? "Не указана";
$facts = $parsedResponse['facts'] ?? "Нет данных";
$articlesList = is_array($articles) ? implode(" ", $articles) : "Не указаны";
$parsedResponse = extractCaseDetailsWithGPT($threadId, ASSISTANT_ID, $fileIdCombined, $combinedContent);
if (!$parsedResponse || !is_array($parsedResponse)) {
logMessage("❌ Ошибка: GPT-4 не вернул корректные данные.");
die("Ошибка анализа обращения через GPT-4");
}
logMessage("✅ GPT-4 вернул: Категория: $category, Статьи: " . json_encode($articles, JSON_UNESCAPED_UNICODE) .
", Сумма: $claim_amount, Факты: $facts");
logMessage("✅ GPT-4 вернул: Категория: $category, Статьи: " . json_encode($articles) . ", Сумма: $claim_amount, Факты: $facts");
if (!$parsedResponse) {
logMessage("❌ Ошибка анализа обращения через GPT-4.");
die("Ошибка анализа обращения через GPT-4");
}
logMessage("✅ GPT-4 выделил данные: " . json_encode($parsedResponse, JSON_UNESCAPED_UNICODE));
$fullTextForEmbedding = buildFullTextForEmbedding($documents);
//$fullTextForEmbedding = buildFullTextForEmbedding($queryText);
logMessage("Полный текст для эмбеддинга (очищенный): " . $fullTextForEmbedding);
// Затем передаем очищенный текст в функцию для получения эмбеддинга:
$embedding = getTextEmbedding($fullTextForEmbedding);
logMessage("Полученный эмбеддинг (первые 10 значений): " . json_encode(array_slice($embedding, 0, 10)));
// 2. Генерируем эмбеддинг по ключевым фактам обращения
$embedding = getTextEmbedding($parsedResponse['facts']);
logMessage("✅ Создан эмбеддинг для поиска.");
// 3. Делаем комбинированный поиск (BM25 + векторный)
if (!isset($es) || !$es) {
logMessage("❌ Ошибка: Подключение к Elasticsearch отсутствует!");
die(json_encode(["status" => "error", "message" => "Ошибка соединения с Elasticsearch."], JSON_UNESCAPED_UNICODE));
}
$searchResults = searchSimilarCases([
'category' => $parsedResponse['category'],
'article' => implode(" ", $parsedResponse['articles']),
'amount' => $parsedResponse['claim_amount'],
'facts' => $parsedResponse['facts'],
'embedding' => $embedding
]);
logMessage("✅ Найдены похожие судебные решения: " . json_encode($searchResults, JSON_UNESCAPED_UNICODE));
logMessage("Обработка всех документов завершена.");
exit;
} else {
logMessage("Ошибка: запрос должен быть POST");
die("Ошибка: запрос должен быть POST");
}
/* ===================== Функции для работы с CRM и Vector Store ===================== */
//пилим предзапрос//
function extractCaseDetailsWithGPT($threadId, $assistantId, $fileId, $content) {
logMessage("📌 Предварительный анализ обращения через GPT-4 (stream): thread_id=$threadId, fileId=$fileId");
$userMessage = "🔹 Разбери обращение и выдели ключевые параметры:
1⃣ **Категория дела**:
Определи, к какому типу спора относится это обращение (например, защита прав потребителей, нарушение договора, трудовой спор).
2⃣ **Применимые статьи закона**:
Выяви, какие нормы права могут быть применимы в данном случае (например, ГК РФ ст. 395, КоАП РФ).
3⃣ **Основные обстоятельства дела**:
Выдели важные факты из обращения и приложенных документов.
4⃣ **Сумма требований (если указана)**:
Если в обращении указана денежная сумма извлеки её.
5⃣ **Тип запрашиваемого документа**:
Нужно ли составить иск, претензию, жалобу, ходатайство?
6⃣ **Оценка вероятности выигрыша**:
Исходя из предоставленных данных, оцени, какие шансы на положительное решение (в %).
🔹 **Формат ответа (JSON)**:
{
\"category\": \"\",
\"articles\": [\"\"],
\"facts\": \"\",
\"claim_amount\": \"\",
\"document_type\": \"\",
\"win_probability\": \"\"
}
📌 Файлы: " . $fileId . "\n
📌 Содержимое для анализа:\n" . $content;
logMessage("📌 Формируем запрос к GPT-4: " . $userMessage);
$payload = [
"assistant_id" => $assistantId,
"thread" => [
"messages" => [
["role" => "user", "content" => $userMessage]
]
],
"stream" => true
];
$payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE);
logMessage("🔹 Отправляем запрос в GPT-4 (Payload):\n" . $payloadJson);
$finalMessage = "";
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_THREADS_API . "/runs",
CURLOPT_RETURNTRANSFER => false,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payloadJson,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . OPENAI_API_KEY,
'OpenAI-Beta: assistants=v2'
],
CURLOPT_WRITEFUNCTION => function($ch, $data) use (&$finalMessage) {
$finalMessage .= $data;
return strlen($data);
}
]);
curl_exec($curl);
$curlError = curl_error($curl);
if ($curlError) {
logMessage("❌ Ошибка cURL в extractCaseDetailsWithGPT: " . $curlError);
curl_close($curl);
return null;
}
curl_close($curl);
logMessage("📌 Сырой потоковый ответ (raw stream):\n" . $finalMessage);
// Разбираем потоковый ответ
$parsedMessage = "";
$lines = explode("\n", $finalMessage);
foreach ($lines as $line) {
$line = trim($line);
if (strpos($line, "data: ") === 0) {
$dataPart = substr($line, 6);
if ($dataPart === "[DONE]") {
break;
}
$json = json_decode($dataPart, true);
logMessage("🔹 DEBUG: Распарсенный фрагмент: " . print_r($json, true));
if (is_array($json) && isset($json['delta']['content'])) {
$contentPiece = "";
foreach ($json['delta']['content'] as $segment) {
if (isset($segment['text']['value'])) {
$contentPiece .= $segment['text']['value'];
}
}
$parsedMessage .= $contentPiece;
}
}
}
if (empty(trim($parsedMessage))) {
logMessage("❌ Парсинг не дал результата, используем сырой ответ.");
$parsedMessage = $finalMessage;
}
logMessage("✅ Итоговый ответ от GPT-4 (stream):\n" . $parsedMessage);
$structuredData = json_decode($parsedMessage, true);
if (!$structuredData) {
logMessage("❌ Ошибка парсинга JSON, возвращаем сырой ответ.");
return ["error" => "Ошибка обработки данных"];
}
return $structuredData;
}
// Функция для проверки, является ли документ судебным решением
function isCourtDecision($filename, $content) {
$keywords = ['решение суда', 'решение', 'судебное решение', 'постановление', 'определение', 'приказ'];
foreach ($keywords as $keyword) {
if (stripos($filename, $keyword) !== false || stripos($content, $keyword) !== false) {
return true;
}
}
return false;
}
function fetchDocumentData($pdo, $id) {
logMessage("Получение данных документа из CRM по ID: $id");
$sql = "
SELECT
n.title,
CASE
WHEN a.storedname IS NOT NULL THEN CONCAT(a.path, a.attachmentsid, '_', a.storedname)
ELSE CONCAT(a.path, a.attachmentsid, '_', a.name)
END AS filepath,
f.foldername AS folder_name,
f.folderid AS folder_id
FROM vtiger_senotesrel r
LEFT JOIN vtiger_notes n ON n.notesid = r.notesid
LEFT JOIN vtiger_crmentity e ON e.crmid = r.notesid
LEFT JOIN vtiger_notescf ncf ON ncf.notesid = r.notesid
LEFT JOIN vtiger_seattachmentsrel r2 ON r2.crmid = r.notesid
LEFT JOIN vtiger_attachments a ON a.attachmentsid = r2.attachmentsid
LEFT JOIN vtiger_attachmentsfolder f ON f.folderid = n.folderid
WHERE r.crmid = ? AND e.deleted = 0
AND (a.type = 'application/pdf' OR a.type = 'application/octet-stream')
";
try {
$stmt = $pdo->prepare($sql);
$stmt->execute([$id]);
$documents = $stmt->fetchAll(PDO::FETCH_ASSOC);
logMessage("Документы получены из CRM: " . json_encode($documents, JSON_UNESCAPED_UNICODE));
return $documents;
} catch (PDOException $e) {
logMessage("Ошибка при выполнении запроса к CRM: " . $e->getMessage());
return [];
}
}
function createVectorStoreAndUploadFiles($filePaths) {
logMessage("Создание Vector Store и загрузка файлов...");
$vectorStoreId = createVectorStore();
if (!$vectorStoreId) return null;
$uploadedFiles = [];
foreach ($filePaths as $filePath) {
logMessage("Загрузка файла: $filePath");
if (!file_exists($filePath)) {
logMessage("Ошибка: Файл не существует: $filePath");
continue;
}
$fileId = uploadFileToOpenAI($filePath);
if (!$fileId) {
logMessage("Ошибка загрузки файла: $filePath");
continue;
}
if (!addFileToVectorStore($vectorStoreId, $fileId)) {
logMessage("Ошибка добавления файла в Vector Store: $filePath");
} else {
logMessage("Файл успешно добавлен в Vector Store: $filePath");
$uploadedFiles[$filePath] = $fileId;
}
}
return ['vectorStoreId' => $vectorStoreId, 'fileIds' => $uploadedFiles];
}
function createVectorStore() {
$curl = curl_init();
$payload = json_encode(['name' => 'Vector Store']);
logMessage("Создание Vector Store. Отправляем payload: " . $payload);
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_VECTOR_STORES_API,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . OPENAI_API_KEY,
'OpenAI-Beta: assistants=v2'
]
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$curlError = curl_error($curl);
curl_close($curl);
logMessage("Ответ OpenAI (создание Vector Store): HTTP $httpCode - " . $response);
if ($curlError) {
logMessage("Ошибка cURL при создании Vector Store: " . $curlError);
return null;
}
$decoded = json_decode($response, true);
if ($httpCode !== 200 || !isset($decoded['id'])) {
logMessage("Ошибка при создании Vector Store: " . json_encode($decoded, JSON_UNESCAPED_UNICODE));
return null;
}
return $decoded['id'];
}
function uploadFileToOpenAI($filePath) {
logMessage("Загрузка файла в OpenAI: $filePath");
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_FILES_API,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'file' => new CURLFile($filePath),
'purpose' => 'assistants'
],
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . OPENAI_API_KEY,
'OpenAI-Beta: assistants=v2'
]
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$curlError = curl_error($curl);
curl_close($curl);
logMessage("Ответ OpenAI (загрузка файла): HTTP $httpCode - " . $response);
if ($curlError) {
logMessage("Ошибка cURL при загрузке файла: " . $curlError);
return null;
}
$decoded = json_decode($response, true);
if ($httpCode !== 200 || !isset($decoded['id'])) {
logMessage("Ошибка при загрузке файла: " . json_encode($decoded, JSON_UNESCAPED_UNICODE));
return null;
}
return $decoded['id'];
}
function addFileToVectorStore($vectorStoreId, $fileId) {
$curl = curl_init();
$payload = json_encode(['file_id' => $fileId]);
logMessage("Добавление файла в Vector Store. Payload: " . $payload);
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_VECTOR_STORES_API . "/$vectorStoreId/files",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . OPENAI_API_KEY,
'OpenAI-Beta: assistants=v2'
]
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$curlError = curl_error($curl);
curl_close($curl);
logMessage("Ответ OpenAI (добавление файла): HTTP $httpCode - " . $response);
if ($curlError) {
logMessage("Ошибка cURL при добавлении файла в Vector Store: " . $curlError);
return false;
}
$decoded = json_decode($response, true);
if ($httpCode !== 200 || !isset($decoded['id'])) {
logMessage("Ошибка добавления файла: " . json_encode($decoded, JSON_UNESCAPED_UNICODE));
return false;
}
return true;
}
function updateAssistantWithVectorStore($vectorStoreId) {
$data = [
'tool_resources' => [
'file_search' => [
'vector_store_ids' => [$vectorStoreId]
]
]
];
$curl = curl_init();
$payload = json_encode($data);
logMessage("Обновление ассистента. Payload: " . $payload);
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_ASSISTANT_API . "/" . ASSISTANT_ID,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . OPENAI_API_KEY,
'OpenAI-Beta: assistants=v2'
]
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$curlError = curl_error($curl);
curl_close($curl);
logMessage("Ответ OpenAI (обновление ассистента): HTTP $httpCode - " . $response);
if ($curlError) {
logMessage("Ошибка обновления ассистента: " . $curlError);
return false;
}
$decoded = json_decode($response, true);
if ($httpCode !== 200 || !isset($decoded['id'])) {
logMessage("Ошибка обновления ассистента: " . json_encode($decoded, JSON_UNESCAPED_UNICODE));
return false;
}
return true;
}
function extractDocumentContent($filePath) {
// 1. Попытка извлечь текст через pdftotext
$text = extractText($filePath);
if (mb_strlen($text, 'UTF-8') >= 200) {
logMessage("pdftotext дал достаточное количество символов.");
return ['mode' => 'pdf', 'content' => $text];
} else {
logMessage("pdftotext дал недостаточное количество символов. Переходим к обработке через изображения.");
// 2. Конвертация PDF в изображения (JPG)
$outputDir = sys_get_temp_dir() . '/pdf_images_' . md5($filePath);
$images = convertPdfToImages($filePath, $outputDir);
if (empty($images)) {
logMessage("Ошибка: не удалось конвертировать PDF в изображения для $filePath");
return ['mode' => 'error', 'content' => ''];
}
// 3. Проверка каждого изображения на NSFW
$allSafe = true;
foreach ($images as $image) {
$classification = classifyImage($image);
$absImagePath = realpath($image);
$unsafeProbability = isset($classification[$absImagePath]) ? ($classification[$absImagePath]['unsafe'] ?? 0) : 0;
logMessage("DEBUG: Для изображения '$absImagePath' получено unsafeProbability = " . $unsafeProbability);
if ($unsafeProbability > 0.8) {
logMessage("DEBUG: Изображение '$absImagePath' не прошло NSFW проверку.");
$allSafe = false;
break;
}
}
if (!$allSafe) {
return ['mode' => 'nsfw', 'content' => 'Файл содержит трешконтент.'];
} else {
// 4. Если все изображения безопасны, запускаем OCR для извлечения текста
$combinedOcrText = "";
foreach ($images as $image) {
$ocrText = doOCR($image);
if (!empty($ocrText)) {
logMessage("DEBUG: OCR успешно извлечёк текст для изображения: " . $image);
$combinedOcrText .= $ocrText . "\n";
} else {
logMessage("DEBUG: OCR не смог извлечь текст для изображения: " . $image);
}
}
if (empty(trim($combinedOcrText))) {
logMessage("Ошибка: Не удалось извлечь текст посредством OCR для $filePath");
return ['mode' => 'error', 'content' => ''];
}
return ['mode' => 'ocr', 'content' => trim($combinedOcrText)];
}
}
}
function analyzeDocuments($documents, $uploadedFileIds) {
$combinedMessages = [];
foreach ($documents as $doc) {
if (empty($doc['filepath']) || strpos($doc['filepath'], '_') === 0) {
logMessage("Неверный путь: " . json_encode($doc, JSON_UNESCAPED_UNICODE));
continue;
}
$normalizedPath = normalizeFilename($doc['filepath']);
$messageBlock = "Документ: " . $doc['title'] . " (папка: " . $doc['folder_name'] . ")\n";
$extraction = extractDocumentContent($doc['filepath']);
if (isset($doc['folder_name']) && mb_stripos($doc['folder_name'], 'судебные решения') !== false) {
logMessage("📌 Найдено судебное решение в папке '{$doc['folder_name']}': " . $doc['title']);
saveCourtDecisionToElastic($GLOBALS['caseId'], $doc['title'], $extraction['content']);
$messageBlock .= "Тип: Судебное решение (сохранено отдельно).\n";
} else {
if ($extraction['mode'] === 'pdf') {
logMessage("DEBUG: Извлечение текста успешно для " . $doc['filepath'] . ". Передаём PDF файл.");
$fileId = $uploadedFileIds[$doc['filepath']] ?? '';
$messageBlock .= "Тип: PDF (извлечение текста успешно, PDF передан для анализа).\nFileID: " . $fileId . "\n";
} elseif ($extraction['mode'] === 'ocr') {
logMessage("DEBUG: Извлечение текста посредством OCR для " . $doc['filepath']);
$messageBlock .= "OCR извлёк текст:\n" . $extraction['content'] . "\n";
} elseif ($extraction['mode'] === 'nsfw') {
logMessage("DEBUG: Файл " . $doc['filepath'] . " не прошёл NSFW проверку.");
$messageBlock .= "Ошибка: Файл не прошёл цензуру. Файл: " . $normalizedPath . "\n";
} else {
$messageBlock .= "Ошибка: Не удалось извлечь текст. Файл: " . $normalizedPath . "\n";
}
}
$combinedMessages[] = $messageBlock;
}
return implode("\n-----------------\n", $combinedMessages);
}
/**
* Собирает полный текст из всех документов, очищает его и возвращает.
*
* @param array $documents Массив документов (каждый должен содержать 'filepath').
* @return string Очищенный полный текст для эмбеддинга.
*/
function buildFullTextForEmbedding($documents) {
$fullText = "";
foreach ($documents as $doc) {
if (empty($doc['filepath'])) {
logMessage("Пропущен документ без пути: " . json_encode($doc, JSON_UNESCAPED_UNICODE));
continue;
}
// Извлекаем текст из файла (может быть как через pdftotext, так и через OCR)
$extraction = extractDocumentContent($doc['filepath']);
if (!empty($extraction['content'])) {
$fullText .= $extraction['content'] . "\n";
} else {
logMessage("Нет извлеченного текста для файла: " . $doc['filepath']);
}
}
// Очищаем полученный текст: удаляем управляющие символы, заменяем множественные пробелы на один и обрезаем крайние пробелы
$cleanText = cleanTextForEmbedding($fullText);
return $cleanText;
}
/**
* Функция для очистки текста, предназначенного для векторизации.
*
* @param string $text Исходный текст.
* @return string Очищенный текст.
*/
function cleanTextForEmbedding($text) {
// Удаляем непечатаемые символы (например, управляющие символы)
$text = preg_replace('/[\x00-\x1F\x7F]/u', ' ', $text);
// Заменяем последовательности пробелов и переводов строк одним пробелом
$text = preg_replace('/\s+/', ' ', $text);
// Обрезаем крайние пробелы
$text = trim($text);
return $text;
}
/* Функции, связанные с отправкой в GPT, остаются, но не используются, так как вызовы закомментированы.
Их можно оставить для будущего восстановления функциональности, либо удалить полностью.
*/
function createNewThread() {
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_THREADS_API,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . OPENAI_API_KEY,
'OpenAI-Beta: assistants=v2'
]
]);
$response = curl_exec($curl);
$decoded = json_decode($response, true);
curl_close($curl);
if (isset($decoded['id'])) {
logMessage("🔄 Создан новый thread_id: " . $decoded['id']);
return $decoded['id'];
} else {
logMessage("❌ Ошибка создания нового треда: " . json_encode($decoded));
return null;
}
}
function extractText($filePath) {
$extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
if ($extension !== 'pdf') {
return '';
}
$outputFile = tempnam(sys_get_temp_dir(), 'txt_') . '.txt';
$command = "pdftotext " . escapeshellarg($filePath) . " " . escapeshellarg($outputFile);
logMessage("Выполняем команду для извлечения текста: " . $command);
exec($command, $output, $returnVar);
if ($returnVar !== 0) {
logMessage("Ошибка pdftotext: " . implode("\n", $output));
return '';
}
if (!file_exists($outputFile)) {
logMessage("Файл pdftotext не создан: $filePath");
return '';
}
$text = file_get_contents($outputFile);
unlink($outputFile);
$cleanText = trim(preg_replace('/\pC+/u', '', $text));
if (mb_strlen($cleanText, 'UTF-8') < 200) {
logMessage("Извлечённый текст для $filePath слишком короткий или не содержит полезной информации. Запуск OCR.");
return '';
}
$snippet = mb_substr($cleanText, 0, 500, 'UTF-8');
logMessage("Извлечённый текст для $filePath (первые 500 символов): " . $snippet);
return $cleanText;
}
function doOCR($filePath) {
logMessage("Запуск OCR для файла: $filePath");
$outputFile = tempnam(sys_get_temp_dir(), 'ocr_') . '.txt';
$command = "tesseract " . escapeshellarg($filePath) . " " . escapeshellarg($outputFile) . " -l rus --psm 6 --oem 1";
logMessage("Выполняем команду OCR: " . $command);
exec($command, $output, $returnVar);
if ($returnVar !== 0) {
logMessage("Ошибка Tesseract: " . implode("\n", $output));
return '';
}
if (!file_exists($outputFile . ".txt")) {
logMessage("Файл OCR не создан: $filePath");
return '';
}
$text = file_get_contents($outputFile . ".txt");
if (empty($text)) {
logMessage("DEBUG: Tesseract вернул пустой результат для $filePath");
}
unlink($outputFile . ".txt");
return $text;
}
function describeImageWithVision($filePath) {
logMessage("Запуск описания изображения через Vision для файла: $filePath");
$imageData = base64_encode(file_get_contents($filePath));
$data = [
"model" => "gpt-4-vision-preview",
"messages" => [
[
"role" => "user",
"content" => [
[
"type" => "text",
"text" => "Опиши это изображение подробно. Если это документ, прочитай и опиши его содержимое."
],
[
"type" => "image_url",
"image_url" => [
"url" => "data:image/jpeg;base64,$imageData"
]
]
]
]
],
"max_tokens" => 500
];
$curl = curl_init();
logMessage("Отправляем запрос на описание изображения. Payload: " . json_encode($data));
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_VISION_API,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . OPENAI_API_KEY
]
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$curlError = curl_error($curl);
curl_close($curl);
logMessage("Ответ Vision (описание): HTTP $httpCode - " . $response);
if ($curlError) {
logMessage("Ошибка cURL в описании изображения: " . $curlError);
return '';
}
$decoded = json_decode($response, true);
if (isset($decoded['choices'][0]['message']['content'])) {
$desc = $decoded['choices'][0]['message']['content'];
if (empty($desc)) {
logMessage("DEBUG: Описание изображения пустое для $filePath");
}
return $desc;
} else {
logMessage("Ошибка при получении описания изображения: " . json_encode($decoded));
return '';
}
}
function checkNSFWWithVision($filePath) {
logMessage("NSFW-проверка через URL " . NSFW_MODERATION_API . " для файла: $filePath");
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => NSFW_MODERATION_API,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'file' => new CURLFile($filePath)
],
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . OPENAI_API_KEY
]
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$curlError = curl_error($curl);
curl_close($curl);
logMessage("Ответ NSFW: HTTP $httpCode - " . $response);
if ($curlError) {
logMessage("Ошибка cURL при проверке NSFW: " . $curlError);
return null;
}
$decoded = json_decode($response, true);
if ($httpCode !== 200 || isset($decoded['detail'])) {
logMessage("Ошибка анализа NSFW: " . json_encode($decoded, JSON_UNESCAPED_UNICODE));
return null;
}
return $decoded['nsfw'] ?? null;
}
function classifyImage($imagePath) {
$absolutePath = realpath($imagePath);
if (!$absolutePath) {
logMessage("ERROR: Не удалось получить абсолютный путь для " . $imagePath);
return [];
}
logMessage("DEBUG: Абсолютный путь для классификации: " . $absolutePath);
$escapedPath = escapeshellarg($absolutePath);
logMessage("DEBUG: Экранированный путь для классификации: " . $escapedPath);
$command = "python3 -c \"import json; from nudenet import NudeClassifier; classifier = NudeClassifier(); print(json.dumps(classifier.classify($escapedPath)))\"";
logMessage("DEBUG: Выполнение команды: " . $command);
$output = shell_exec($command);
logMessage("DEBUG: Вывод команды: " . $output);
if ($output === null) {
logMessage("ERROR: shell_exec вернул null при выполнении NudeClassifier");
return [];
}
return json_decode(trim($output), true);
}
function convertPdfToImages($pdfPath, $outputDir) {
if (!file_exists($pdfPath)) {
logMessage("Файл не существует: $pdfPath");
return [];
}
if (!file_exists($outputDir)) {
mkdir($outputDir, 0777, true);
logMessage("Создана директория для изображений: $outputDir");
}
$imagePattern = $outputDir . '/page-%03d.jpg';
$command = "LC_ALL=en_US.UTF-8 convert -density 300 " . escapeshellarg($pdfPath) . " -quality 90 " . escapeshellarg($imagePattern);
logMessage("Выполняем команду: " . $command);
exec($command . " 2>&1", $output, $returnVar);
logMessage("DEBUG: Вывод convert: " . implode("\n", $output));
if ($returnVar !== 0) {
logMessage("Ошибка при конвертации PDF в изображения.");
return [];
}
return glob($outputDir . '/*.jpg');
}
function getKnowledgeBaseContext($filePath) {
return "Статическая информация: нормы и законы РФ, судебные прецеденты...";
}
function analyzeDocumentWithAssistantStream($threadId, $assistantId, $fileId, $content) {
logMessage("Анализ документа через ассистента (stream): thread_id=$threadId, fileId=$fileId");
$userMessage = "🔹 Отвечай по шаблону:
Если в запросе присутствуют файлы (например, PDF или изображения), извлеки из них текст для анализа, даже если содержимое уже передано отдельно.
Задача:
Проанализируй загруженные документы, выполнив следующие действия:
1⃣ Список файлов и проверка соответствия названий
Перечисли все загруженные файлы, без указания технических идентификаторов и служебных слов.
Сравни название файлов с их содержимым. Если имеются расхождения, укажи это.
Если файл содержит изображение, сначала извлеки текст (если есть), затем проанализируй его содержание; если текста нет опиши изображение.
Особое внимание удели NSFW-контенту: если присутствует nsfw_alert со значением true, укажи, что файл требует ручной проверки.
2⃣ Краткий анализ спора
Определи истца (потребителя) и ответчика (компанию или ИП).
Проверь, совпадают ли лицо-отправитель и лицо, заключившее договор. Если нет добавь, что требуется проверка.
Опиши суть спора и основные аргументы сторон.
3⃣ Проверка на цензуру
Проверь документы на ненормативную лексику и нецензурные изображения.
4⃣ Выдача итогового вердикта
Если все соответствует требованиям, укажи строго:
«Вердикт: Прошло модерацию.»
Если обнаружены проблемы (например, отсутствие извлечённого текста или расхождение данных), отметь, что требуется ручная проверка.
5⃣ Характер спора
Дай краткую характеристику дела (например: «некачественная услуга», «невыполнение условий договора»).
6⃣ Вероятность положительного решения спора
Укажи вероятность положительного решения спора в формате:
«Вероятность положительного решения спора: _____ %».
7⃣ Чего не хватает/запросить у Ответчика
Перечисли, какие документы или сведения необходимо запросить у ответчика.
8⃣ Чего не хватает/запросить у Истца
Укажи, каких дополнительных документов не хватает для полного отчёта.
📌 Отчёт должен быть структурированным, чётким и лаконичным, с указанием норм права РФ для регулирования спора.
Файлы: " . $fileId . "\n" .
"Содержимое для анализа:\n" . $content;
logMessage("Формируем текст запроса для ассистента (stream):\n" . $userMessage);
$userMessage = mb_convert_encoding($userMessage, 'UTF-8', 'auto');
$userMessage = iconv("UTF-8", "UTF-8//IGNORE//TRANSLIT", $userMessage);
$payload = [
"assistant_id" => $assistantId,
"thread" => [
"messages" => [
["role" => "user", "content" => $userMessage]
]
],
"stream" => true
];
$payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE);
logMessage("Финальный запрос для GPT (Payload):\n" . $payloadJson);
$finalMessage = "";
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_THREADS_API . "/runs",
CURLOPT_RETURNTRANSFER => false,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payloadJson,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . OPENAI_API_KEY,
'OpenAI-Beta: assistants=v2'
],
CURLOPT_WRITEFUNCTION => function($ch, $data) use (&$finalMessage) {
$finalMessage .= $data;
return strlen($data);
}
]);
curl_exec($curl);
$curlError = curl_error($curl);
if ($curlError) {
logMessage("Ошибка cURL в analyzeDocumentWithAssistantStream: " . $curlError);
curl_close($curl);
return null;
}
curl_close($curl);
logMessage("Сырой потоковый ответ (raw stream):\n" . $finalMessage);
$parsedMessage = "";
$lines = explode("\n", $finalMessage);
foreach ($lines as $line) {
$line = trim($line);
if (strpos($line, "data: ") === 0) {
$dataPart = substr($line, 6);
if ($dataPart === "[DONE]") {
break;
}
$json = json_decode($dataPart, true);
logMessage("DEBUG: Распарсенный фрагмент: " . print_r($json, true));
if (is_array($json) && isset($json['delta']['content'])) {
$contentPiece = "";
foreach ($json['delta']['content'] as $segment) {
if (isset($segment['text']['value'])) {
$contentPiece .= $segment['text']['value'];
}
}
$parsedMessage .= $contentPiece;
}
}
}
if (empty(trim($parsedMessage))) {
logMessage("Парсинг не дал результата, используем сырой ответ.");
$parsedMessage = $finalMessage;
}
logMessage("DEBUG: Итоговый ответ от ассистента (stream):\n" . $parsedMessage);
$verdict = 'Вердикт не определён';
if (preg_match('/Вердикт:\s*(.+)$/mi', $parsedMessage, $matches)) {
$extractedVerdict = trim($matches[1]);
if (!empty($extractedVerdict)) {
$verdict = $extractedVerdict;
}
}
return [
"status" => "complete",
"content" => $parsedMessage,
"moderationVerdict" => $verdict
];
}
function generateReport($allResults) {
$report = "### Итоговый отчет\n\n";
foreach ($allResults as $result) {
$report .= "**Результат анализа:**\n" . ($result['analysis']['content'] ?? 'Нет данных') . "\n";
$report .= "**Вердикт:** " . ($result['analysis']['moderationVerdict'] ?? 'Не определен') . "\n\n";
}
return $report;
}
?>