AI-поиск в логах: генерация regex через естественный язык | Vercel AI SDK | AiManual
AiManual Logo Ai / Manual.
16 Янв 2026 Гайд

Как добавить AI-поиск в логах на естественном языке: промпт-инжиниринг для генерации regex

Пошаговый гайд по добавлению AI-поиска в логи. Заменяем сложные regex на естественный язык с помощью промпт-инжиниринга и Vercel AI SDK. Безопасность Extension

Забудьте о regex. Научите ИИ искать в логах на человеческом языке

Вы когда-нибудь тратили полчаса на составление регулярного выражения, чтобы найти в гигабайтах логов одну строчку? Тот самый момент, когда вы понимаете, что \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z.*ERROR.*user_id=\d+ должно было работать, но не работает. Потому что в логах оказались миллисекунды с двумя цифрами, а не с тремя.

Регулярные выражения — это боль. Они точны, но требуют экспертизы, которую не хочется держать в голове в 3 часа ночи, когда падает продакшен.

Что если вместо этого вы могли бы написать: "Найди все ошибки авторизации для пользователя 12345 за последний час"? И получить готовый regex, который сразу работает?

Это не фантастика. Это AI-поиск в логах. И он работает уже сегодня. Я покажу, как сделать его production-ready, безопасным и быстрым.

Почему простой промпт "сгенерируй regex" не работает

Кажется, что задача простая. Берем GPT-4, даем ему описание поиска, получаем regex. На практике вы столкнетесь с тремя проблемами:

  • ИИ не знает структуру ваших логов. Он может сгенерировать regex для идеализированного формата, который не совпадет с реальностью.
  • Безопасность Extension Host. Если вы встраиваете это в VS Code расширение, нельзя просто слать логи в OpenAI. Они могут содержать чувствительные данные.
  • Контекст из случайных строк. ИИ плохо работает с абсолютно случайными данными. Ему нужны паттерны, а не шум.

Вот как выглядит типичная неудача:

// ПЛОХОЙ ПРОМПТ
const badPrompt = `Сгенерируй regex для поиска ошибок 500 в логах`;

// Результат: /ERROR.*500/g
// Проблема: пропустит "HTTP 500", "status=500", "code:500"

ИИ не видит примеров ваших логов. Он генерирует абстракцию, которая почти никогда не подходит.

Архитектура: как дать ИИ контекст, не отправляя логи в облако

Ключевая идея: мы не отправляем сами логи в LLM. Мы отправляем образцы структуры и метаданные формата.

💡
Это похоже на принцип RAG (Retrieval-Augmented Generation), но для структурированных данных. Вместо поиска по документам мы извлекаем паттерны из логов. Если хотите глубже понять RAG, посмотрите мой гайд про проблемы деградации поиска.

Вот как выглядит безопасная архитектура:

  1. Пользователь пишет запрос на естественном языке
  2. Система анализирует первые 100 строк логов локально
  3. Извлекает паттерны: форматы дат, уровни логирования, структура сообщений
  4. Собирает промпт с этими паттернами + запрос пользователя
  5. Отправляет только промпт (без самих логов) в LLM через Vercel AI SDK
  6. Получает regex, валидирует его на локальных образцах
  7. Применяет regex ко всем логам

Никакие данные не уходят за пределы вашего контроля. Это критично для VS Code расширений, которые работают с корпоративными логами.

1Подготовка: извлекаем паттерны из логов

Сначала напишем функцию, которая анализирует логи и вытаскивает структуру. Мы ищем:

  • Форматы дат и времени
  • Уровни логирования (ERROR, WARN, INFO, DEBUG)
  • Шаблоны идентификаторов (user_id, request_id, session_id)
  • Разделители между полями
function extractLogPatterns(logLines) {
  const patterns = {
    timestampFormats: [],
    logLevels: new Set(),
    commonFields: {},
    separators: new Set()
  };
  
  // Анализируем первые 50 строк для скорости
  const sampleLines = logLines.slice(0, 50);
  
  sampleLines.forEach(line => {
    // Ищем timestamp - самый распространенный паттерн
    const timestampMatch = line.match(
      /(\d{4}[-/]\d{2}[-/]\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d{1,6})?(Z|[+-]\d{2}:?\d{2})?)/
    );
    
    if (timestampMatch) {
      patterns.timestampFormats.push(timestampMatch[1]);
    }
    
    // Ищем уровни логирования
    const levelMatch = line.match(/\b(ERROR|WARN|INFO|DEBUG|FATAL|TRACE)\b/);
    if (levelMatch) {
      patterns.logLevels.add(levelMatch[1]);
    }
    
    // Ищем common fields типа user_id=123 или requestId="abc"
    const fieldMatches = line.matchAll(/(\w+)[=:"']([^\s"']+)/g);
    for (const match of fieldMatches) {
      const [_, key, value] = match;
      if (!patterns.commonFields[key]) {
        patterns.commonFields[key] = new Set();
      }
      patterns.commonFields[key].add(value);
    }
  });
  
  // Убираем дубликаты и преобразуем Set в массивы
  patterns.timestampFormats = [...new Set(patterns.timestampFormats)];
  patterns.logLevels = [...patterns.logLevels];
  
  // Для common fields оставляем только ключи с несколькими значениями
  Object.keys(patterns.commonFields).forEach(key => {
    if (patterns.commonFields[key].size < 2) {
      delete patterns.commonFields[key];
    } else {
      patterns.commonFields[key] = [...patterns.commonFields[key]].slice(0, 5);
    }
  });
  
  return patterns;
}

Эта функция работает локально, не отправляя данные в сеть. Она создает "отпечаток" формата ваших логов.

2Промпт-инжиниринг: учим ИИ думать как парсер логов

Теперь главное — промпт. Он должен дать ИИ достаточно контекста, но не перегружать его. Вот структура, которая работает:

function buildRegexPrompt(userQuery, logPatterns) {
  return `Ты — эксперт по парсингу логов. Твоя задача — сгенерировать точное регулярное выражение для поиска в логах.

СТРУКТУРА ЛОГОВ:
- Форматы timestamp: ${logPatterns.timestampFormats.join(', ') || 'не обнаружено'}
- Уровни логирования: ${logPatterns.logLevels.join(', ') || 'не обнаружено'}
- Частые поля: ${Object.entries(logPatterns.commonFields)
    .map(([key, values]) => `${key} (примеры: ${values.slice(0, 3).join(', ')}...)`)
    .join('; ')}

ПРАВИЛА ГЕНЕРАЦИИ REGEX:
1. Используй named groups для важных полей: (?...), (?...)
2. Учитывай, что между полями могут быть пробелы разной длины
3. Логи чувствительны к регистру, если не указано иное
4. Предпочитай нежадные квантификаторы (*?, +?)
5. Экранируй специальные символы в тексте сообщений
6. Включай флаги: 'g' для глобального поиска, 'm' для multiline если нужно

ЗАПРОС ПОЛЬЗОВАТЕЛЯ: "${userQuery}"

Сгенерируй ТОЛЬКО регулярное выражение в формате /regex/flags.
Не добавляй объяснений, комментариев или дополнительного текста.`;
}

Обратите внимание на ключевые моменты:

  • Роль: "эксперт по парсингу логов" — задает контекст
  • Структура логов: конкретные примеры из реальных данных
  • Правила генерации: технические требования к regex
  • Строгий формат ответа: только regex, без лишнего текста

Важно: если ваш AI-агент начинает "халтурить" и генерировать неточные regex, проблема может быть в недостатке контекста. В моей статье про проблемы контекста в бизнес-среде я разбираю эту тему подробно.

3Интеграция с Vercel AI SDK: безопасный вызов LLM

Vercel AI SDK дает удобный интерфейс для работы с разными провайдерами. Вот как выглядит вызов:

import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

export async function generateLogRegex(userQuery, logPatterns) {
  const prompt = buildRegexPrompt(userQuery, logPatterns);
  
  try {
    const { text: regex } = await generateText({
      model: openai('gpt-4-turbo-preview'),
      prompt,
      temperature: 0.1, // Низкая температура для детерминированности
      maxTokens: 500,
    });
    
    // Валидируем, что ответ — валидное регулярное выражение
    if (!regex.match(/^\/(?:[^\/\\]|\\.)*\/[gimuy]*$/)) {
      throw new Error('Некорректный формат regex от ИИ');
    }
    
    // Тестируем на sample данных
    const testResult = testRegexOnSamples(regex, sampleLogs);
    if (testResult.matchCount === 0) {
      // Если не нашел ничего, возможно regex слишком строгий
      return await generateFallbackRegex(userQuery, logPatterns);
    }
    
    return {
      regex,
      testResults: testResult
    };
    
  } catch (error) {
    console.error('Ошибка генерации regex:', error);
    // Fallback: простой текстовый поиск
    return {
      regex: `/${escapeRegex(userQuery)}/gi`,
      isFallback: true
    };
  }
}

function testRegexOnSamples(regexStr, samples) {
  const regex = new RegExp(
    regexStr.slice(1, regexStr.lastIndexOf('/')),
    regexStr.slice(regexStr.lastIndexOf('/') + 1)
  );
  
  const matches = [];
  samples.forEach((line, index) => {
    if (regex.test(line)) {
      matches.push({ line: line.substring(0, 100), index });
    }
    // Сбрасываем lastIndex для глобального regex
    regex.lastIndex = 0;
  });
  
  return {
    matchCount: matches.length,
    matches: matches.slice(0, 5) // Первые 5 совпадений для демонстрации
  };
}

Температура 0.1 — это важно. Мы хотим детерминированные результаты, а не креативность. Если ИИ "выдумывает" форматы, которых нет в логах, поиск не сработает.

Ошибки, которые сломают вашу систему

Я видел десятки попыток внедрить AI-поиск в логах. Вот что идет не так:

ОшибкаПоследствиеКак исправить
Отправка полных логов в OpenAIУтечка данных, блокировка APIОтправлять только паттерны, как показано выше
Температура > 0.5Разные regex для одинаковых запросовИспользовать temperature 0.1-0.2
Нет валидации regexСинтаксические ошибки, падение приложенияТестировать на sample перед применением
Игнорирование multiline логовПропуск stack traces и исключенийДобавлять флаг 'm' и обрабатывать \n

Самая коварная ошибка — доверять ИИ без проверки. Всегда тестируйте сгенерированный regex на подмножестве данных. Если он не находит ничего там, где должен находить, нужен fallback.

Производительность: когда AI-поиск тормозит

Генерация regex через LLM занимает 2-5 секунд. Это приемлемо для интерактивного поиска, но не для скриптов, которые обрабатывают терабайты логов.

Решение — кэширование. Хэшируйте пару (запрос пользователя, отпечаток формата логов) и сохраняйте результат. 80% запросов повторяются: "ошибки за сегодня", "медленные запросы", "пользователь X".

const regexCache = new Map();

async function getCachedRegex(query, patterns) {
  const cacheKey = `${query}-${JSON.stringify(patterns)}`;
  
  if (regexCache.has(cacheKey)) {
    const cached = regexCache.get(cacheKey);
    // Проверяем, не устарел ли кэш (формат логов мог измениться)
    if (Date.now() - cached.timestamp < 5 * 60 * 1000) { // 5 минут
      return cached.regex;
    }
  }
  
  const regex = await generateLogRegex(query, patterns);
  regexCache.set(cacheKey, {
    regex,
    timestamp: Date.now()
  });
  
  // Ограничиваем размер кэша
  if (regexCache.size > 100) {
    const oldestKey = [...regexCache.keys()][0];
    regexCache.delete(oldestKey);
  }
  
  return regex;
}

С кэшированием повторные запросы выполняются за миллисекунды. Это меняет UX: пользователь не ждет генерации каждый раз.

Расширение для VS Code: полная реализация

Вот как выглядит основная логика расширения для поиска в логах:

// extension.js
const vscode = require('vscode');
const { getCachedRegex } = require('./regex-generator');
const { extractLogPatterns } = require('./log-analyzer');

export function activate(context) {
  const provider = vscode.languages.registerCompletionItemProvider('log', {
    async provideCompletionItems(document, position) {
      // Триггерим AI-поиск при вводе "? "
      const linePrefix = document.lineAt(position).text.substr(0, position.character);
      if (!linePrefix.endsWith('? ')) return [];
      
      const userQuery = linePrefix.slice(0, -2); // Убираем "? "
      
      // Получаем весь текст логов
      const fullText = document.getText();
      const logLines = fullText.split('\n');
      
      // Извлекаем паттерны (локально, безопасно)
      const patterns = extractLogPatterns(logLines);
      
      // Показываем индикатор загрузки
      vscode.window.setStatusBarMessage('Генерация AI-поиска...');
      
      try {
        // Получаем regex через кэшированный AI-вызов
        const { regex, testResults } = await getCachedRegex(userQuery, patterns);
        
        // Создаем completion item с готовым regex
        const item = new vscode.CompletionItem(`AI Search: ${userQuery}`);
        item.insertText = regex;
        item.detail = `Найдено совпадений в sample: ${testResults.matchCount}`;
        item.documentation = new vscode.MarkdownString(`
**Сгенерированный regex:**
\`\`\`
${regex}
\`\`\`

**Тестовые совпадения:**
${testResults.matches.map(m => `- Строка ${m.index}: ${m.line}...`).join('\n')}
        `);
        
        return [item];
        
      } catch (error) {
        vscode.window.showErrorMessage(`Ошибка AI-поиска: ${error.message}`);
        return [];
      } finally {
        vscode.window.setStatusBarMessage('');
      }
    }
  });
  
  context.subscriptions.push(provider);
}

Пользователь вводит в логах "ошибки базы данных за последний час? ", получает автодополнение с готовым regex, нажимает Tab — и поиск выполняется.

💡
Если вы делаете более сложных AI-агентов с инструментами и планированием, посмотрите мой гайд про production-ready AI-агентов. Там те же принципы, но в большем масштабе.

Что делать, когда ИИ генерирует неправильный regex

Даже с идеальным промптом ИИ ошибается в 10-15% случаев. Нужна стратегия graceful degradation:

  1. Fallback на текстовый поиск: если regex не находит ничего, ищем просто подстроку
  2. Упрощение запроса: "ошибки базы данных" → "ERROR.*database"
  3. Логирование провалов: собирайте запросы, где ИИ ошибся, чтобы улучшить промпт
  4. Ручная коррекция: дайте пользователю возможность исправить сгенерированный regex
async function generateFallbackRegex(userQuery, patterns) {
  // Упрощаем запрос: удаляем стоп-слова, оставляем ключевые
  const keywords = userQuery
    .toLowerCase()
    .split(/\s+/)
    .filter(word => !['найди', 'все', 'за', 'для', 'с'].includes(word))
    .map(escapeRegex)
    .join('.*');
  
  // Добавляем контекст из паттернов
  let regex = '';
  if (patterns.logLevels.includes('ERROR')) {
    regex = `ERROR.*${keywords}`;
  } else {
    regex = keywords;
  }
  
  return `/${regex}/gi`;
}

Простой fallback лучше, чем полный отказ. Пользователь получит хоть какие-то результаты, даже если не идеальные.

Будущее: от regex к семантическому поиску

Следующий шаг — вообще отказаться от regex. Вместо преобразования естественного языка в регулярные выражения, преобразовывать логи в эмбеддинги и искать семантически.

Запрос "ошибки при оплате" найдет не только строки с "ERROR" и "payment", но и "transaction failed", "charge declined", "payment gateway timeout". Это требует векторной базы и эмбеддинг-модели, но становится все доступнее.

Пока что гибридный подход работает лучше: AI генерирует regex для точного поиска, а семантический поиск добавляется для сложных запросов. Как в дорожной карте RAG 2026 — комбинируем разные техники.

Главное — начать с чего-то работающего. С regex-генерацией через ИИ. Это дает немедленную пользу, не требуя инфраструктурной революции. А когда будете готовы к семантическому поиску, у вас уже будет понимание, как работать с AI в контексте логов.

Попробуйте. Сэкономленные 30 минут на составлении одного сложного regex окупят время на внедрение. Особенно в 3 часа ночи.