Files
crm.clientright.ru/code778.php

721 lines
31 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

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
// Настройки 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/v1/chat/completions'; // для описания изображений
// Для NSFW-модерации используем корректный URL:
const NSFW_MODERATION_API = 'https://api.proxyapi.ru/v1/moderation/nsfw';
const LOG_FILE = 'logs/scriptDS3.log';
// ID и имя ассистента
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';
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('logs')) {
mkdir('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);
}
/* ===================== Основной скрипт ===================== */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? null;
if (!$id) {
logMessage("Ошибка: отсутствует ID документа");
die("Ошибка: отсутствует ID документа");
}
logMessage("Начало обработки документа с ID: $id");
$documents = fetchDocumentData($pdo, $id);
if (empty($documents)) {
logMessage("Документы не найдены для ID: $id");
die("Документы не найдены для ID: $id");
}
logMessage("Документы получены из БД: " . json_encode($documents, JSON_UNESCAPED_UNICODE));
$filePaths = array_column($documents, 'filepath');
$uploadResult = createVectorStoreAndUploadFiles($filePaths);
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);
if (empty($combinedContent)) {
logMessage("Ошибка: анализ документов не вернул результатов");
die("Ошибка: анализ документов не вернул результатов");
}
// Создаем один тред (реальный вызов API)
$threadId = createThread();
if (!$threadId) {
logMessage("Ошибка создания треда");
die("Ошибка создания треда");
}
// Отправляем объединённое сообщение в ассистента (fileId оставляем пустым для совокупного анализа)
$analysis = analyzeDocumentWithAssistant($threadId, ASSISTANT_ID, '', $combinedContent);
if (!$analysis) {
logMessage("Ошибка анализа совокупного запроса");
die("Ошибка анализа совокупного запроса");
}
// Формируем отчёт с объединённым анализом
$report = generateReport([
[
'document' => 'Объединенный анализ',
'status' => 'complete',
'analysis' => $analysis
]
]);
logMessage("Итоговый отчет:\n" . $report);
echo $report;
logMessage("Обработка всех документов завершена.");
} else {
logMessage("Ошибка: запрос должен быть POST");
die("Ошибка: запрос должен быть POST");
}
/* ===================== Функции для работы с CRM и Vector Store ===================== */
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
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_seattachmentsrel r2 ON r2.crmid = r.notesid
LEFT JOIN
vtiger_attachments a ON a.attachmentsid = r2.attachmentsid
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();
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_VECTOR_STORES_API,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['name' => 'Vector Store']),
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);
if ($curlError) {
logMessage("Ошибка cURL при создании Vector Store: " . $curlError);
return null;
}
logMessage("Ответ OpenAI (создание Vector Store): HTTP $httpCode - " . $response);
$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);
if ($curlError) {
logMessage("Ошибка cURL при загрузке файла: " . $curlError);
return null;
}
logMessage("Ответ OpenAI (загрузка файла): HTTP $httpCode - " . $response);
$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();
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_VECTOR_STORES_API . "/$vectorStoreId/files",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['file_id' => $fileId]),
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);
if ($curlError) {
logMessage("Ошибка cURL при добавлении файла в Vector Store: " . $curlError);
return false;
}
logMessage("Ответ OpenAI (добавление файла): HTTP $httpCode - " . $response);
$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();
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_ASSISTANT_API . "/" . ASSISTANT_ID,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($data),
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);
if ($curlError) {
logMessage("Ошибка обновления ассистента: " . $curlError);
return false;
}
logMessage("Ответ OpenAI (обновление ассистента): HTTP $httpCode - " . $response);
$decoded = json_decode($response, true);
if ($httpCode !== 200 || !isset($decoded['id'])) {
logMessage("Ошибка обновления ассистента: " . json_encode($decoded, JSON_UNESCAPED_UNICODE));
return false;
}
return true;
}
/* ===================== Логика анализа документов ===================== */
/**
* Функция analyzeDocuments собирает информацию по всем документам и возвращает объединённый текст.
*/
function analyzeDocuments($documents, $uploadedFileIds) {
$results = [];
foreach ($documents as $doc) {
if (empty($doc['filepath']) || strpos($doc['filepath'], '_') === 0) {
logMessage("Неверный путь: " . json_encode($doc, JSON_UNESCAPED_UNICODE));
continue;
}
$finalContent = "";
// 1. Попытка извлечения текста напрямую
$extractedText = extractText($doc['filepath']);
if (!empty($extractedText)) {
logMessage("DEBUG: Извлечение текста успешно для " . $doc['filepath'] . ". Отправляем PDF файл в ассистента.");
// Если текст успешно извлечён, не передаём его в finalContent будем использовать file_id
} else {
logMessage("DEBUG: Извлечение текста не удалось для " . $doc['filepath'] . ". Пытаемся обработать через изображения.");
$extension = strtolower(pathinfo($doc['filepath'], PATHINFO_EXTENSION));
if ($extension === 'pdf') {
$outputDir = sys_get_temp_dir() . '/pdf_images_' . md5($doc['filepath']);
$images = convertPdfToImages($doc['filepath'], $outputDir);
if (empty($images)) {
logMessage("Ошибка: Не удалось конвертировать PDF в изображения для " . $doc['filepath']);
$finalContent = "Не удалось извлечь текст: конвертация в изображение не выполнена.";
} else {
$allSafe = true;
// Проверяем каждое изображение на цензуру
foreach ($images as $image) {
logMessage("DEBUG: Проверяем изображение: " . $image);
$classification = classifyImage($image);
$absImagePath = realpath($image);
$unsafeProbability = 0;
if (isset($classification[$absImagePath])) {
$unsafeProbability = $classification[$absImagePath]['unsafe'] ?? 0;
}
logMessage("DEBUG: Для изображения '$absImagePath' получено unsafeProbability = " . $unsafeProbability);
if ($unsafeProbability > 0.8) {
logMessage("DEBUG: Изображение '$absImagePath' не прошло цензуру (unsafeProbability = $unsafeProbability).");
$allSafe = false;
break;
}
}
if (!$allSafe) {
$finalContent = "Файл не прошёл цензуру.";
} else {
// Если все изображения безопасны, запускаем 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($combinedOcrText)) {
$finalContent = $combinedOcrText . "\n" . getKnowledgeBaseContext($doc['filepath']);
} else {
$finalContent = "Не удалось извлечь текст посредством OCR.";
}
}
}
} else {
$finalContent = "Не удалось извлечь текст: не поддерживаемый формат.";
}
}
// 2. Получаем file_id для привязки (если имеется)
$fileId = $uploadedFileIds[$doc['filepath']] ?? '';
logMessage("DEBUG: fileId для " . $doc['filepath'] . " = " . $fileId);
// 3. Создаем тред для анализа
$threadId = createThread();
if (!$threadId) {
logMessage("Ошибка создания треда для " . $doc['filepath']);
continue;
}
// 4. Отправляем итоговое содержимое ассистенту для анализа.
// Если finalContent пустое, значит, текст извлечён успешно и мы передаем PDF через file_id.
logMessage("DEBUG: Вызов analyzeDocumentWithAssistant с параметрами: threadId=$threadId, assistantId=" . ASSISTANT_ID . ", fileId=$fileId, finalContent=" . substr($finalContent, 0, 50));
$analysis = analyzeDocumentWithAssistant($threadId, ASSISTANT_ID, $fileId, $finalContent);
if ($analysis) {
logMessage("Анализ завершен: " . json_encode($analysis, JSON_UNESCAPED_UNICODE));
$results[] = [
'document' => $doc['title'],
'status' => 'Анализ завершен',
'analysis' => $analysis
];
} else {
logMessage("Ошибка анализа " . $doc['filepath']);
$results[] = [
'document' => $doc['title'],
'status' => 'Ошибка анализа',
'message' => 'Не удалось проанализировать документ.'
];
}
}
return $results;
}
/**
* Функция createThread выполняет реальный вызов к API для создания треда.
*/
function createThread() {
$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);
$threadId = $decoded['id'] ?? null;
logMessage("Создан тред с id: " . ($threadId ?: "не удалось создать"));
return $threadId;
}
/**
* Функция extractText пытается извлечь текст с помощью pdftotext.
*/
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);
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);
if (empty($text)) {
logMessage("DEBUG: pdftotext вернул пустой результат для $filePath");
}
unlink($outputFile);
return $text;
}
/**
* Функция doOCR использует Tesseract для извлечения текста.
*/
function doOCR($filePath) {
logMessage("Запуск OCR для файла: $filePath");
$outputFile = tempnam(sys_get_temp_dir(), 'ocr_') . '.txt';
$command = "tesseract " . escapeshellarg($filePath) . " " . escapeshellarg($outputFile) . " -l rus";
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;
}
/**
* Функция describeImageWithVision вызывает API для получения описания изображения.
*/
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();
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);
if ($curlError) {
logMessage("Ошибка cURL в описании изображения: " . $curlError);
return '';
}
logMessage("Ответ Vision (описание): HTTP $httpCode - " . $response);
$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 '';
}
}
/**
* Функция checkNSFWWithVision использует корректный URL для NSFW-модерации.
*/
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);
if ($curlError) {
logMessage("Ошибка cURL при проверке NSFW: " . $curlError);
return null;
}
logMessage("Ответ NSFW: HTTP $httpCode - " . $response);
$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;
}
/**
* Функция classifyImage использует NudeClassifier через Python.
*/
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);
}
/**
* Функция convertPdfToImages конвертирует PDF в изображения.
*/
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');
}
/**
* Stub-функция для получения контекста из базы знаний.
*/
function getKnowledgeBaseContext($filePath) {
return "Статическая информация: нормы и законы РФ, судебные прецеденты...";
}
/**
* Функция analyzeDocumentWithAssistant отправляет реальный запрос к GPT.
*
* Здесь используется очистка строки, mb_substr, а данные формируются в ключе "messages".
*/
function analyzeDocumentWithAssistant($threadId, $assistantId, $fileId, $content) {
logMessage("Анализ документа через ассистента: thread_id=$threadId, fileId=$fileId");
if (empty($content)) {
$userMessage = "Проанализируй документ, предоставленный как PDF (file_id: $fileId).";
} else {
$userMessage = "Проанализируй документ. Содержимое для анализа:\n" . $content;
}
// Очистка строки от битых символов
$userMessage = mb_convert_encoding($userMessage, 'UTF-8', 'auto');
$userMessage = iconv("UTF-8", "UTF-8//IGNORE//TRANSLIT", $userMessage);
// Формирование payload для вызова ассистента через threads/runs
$payload = [
"assistant_id" => $assistantId,
"thread" => [
// Можно указать идентификатор существующего треда, если API это поддерживает,
// либо оставить сообщения, чтобы создать новый контекст в рамках треда
"messages" => [
["role" => "user", "content" => $userMessage]
]
],
"temperature" => 0.7,
"top_p" => 1.0,
"stream" => false
];
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_THREADS_API . "/runs", // новый endpoint для ассистента
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
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);
if ($curlError) {
logMessage("Ошибка cURL: " . $curlError);
return null;
}
if ($httpCode !== 200) {
logMessage("Ошибка API: HTTP $httpCode - " . $response);
return null;
}
$decoded = json_decode($response, true);
// Предполагается, что в ответе содержится объект сообщения ассистента,
// например, в ключе 'message' с полем 'content'
$assistantMessage = $decoded['message']['content'] ?? '';
logMessage("DEBUG: Полученный ответ от ассистента: " . $assistantMessage);
return [
"status" => "complete",
"content" => $assistantMessage,
"moderationVerdict" => ""
];
}
/**
* Функция generateReport формирует итоговый отчет.
*/
function generateReport($allResults) {
$report = "### Итоговый отчет\n\n";
foreach ($allResults as $result) {
$report .= "**Результат анализа:**\n" . ($result['analysis']['content'] ?? 'Нет данных') . "\n";
$report .= "**Вердикт:** " . ($result['analysis']['moderationVerdict'] ?? 'Не определен') . "\n\n";
}
return $report;
}