Skip to content

Repository files navigation

Log Analyzer Agent

Агент для анализа логов на базе фреймворка Koog (JetBrains). Принимает задачу из Slack-трекера (SlackTask), автономно строит запрос к GCP Cloud Logging, забирает релевантные логи, делит их на чанки, анализирует параллельно через LLM, верифицирует через Critic Agent и формирует структурированный Markdown-отчёт.

Возможности

  • Slack-задача как вход — универсальный тип SlackTask (тип, описание, сервис, окружение, приоритет, metadata) вместо пути к файлу. Готов к маршрутизации из Router Graph Strategy.
  • Agentic GCP log discovery — LLM-нода log-discovery с GCP-инструментами (listGcpServices, previewGcpLogs, queryGcpLogs) сама исследует проект, валидирует фильтр и возвращает GcpFetchPlan
  • Разделение plan/execute — LLM планирует запрос (GcpFetchPlan), детерминированная нода fetch-and-chunk исполняет его через GcpLogClient
  • Подменяемый GCP-клиентMockGcpLogClient (читает sample.log) → RealGcpLogClient без изменения стратегии
  • Параллельный анализ — fan-out/fan-in через parallel() Koog: 3 слота анализируют репрезентативный срез файла одновременно
  • Generator-Critic loop — reporter генерирует отчёт, critic верифицирует, цикл повторяется до 3 раз при необходимости
  • Граф-стратегия Koog — детерминированный пайплайн с условными рёбрами (onCondition), циклами и параллельными нодами
  • ReportMetadata — тайминги, счётчик итераций критика, количество чанков, источник (задача + GCP-фильтр) — в структурированном виде
  • Поддержка нескольких провайдеров — OpenAI, Anthropic, LMStudio (локальные модели)
  • Наблюдаемость — тайминги каждого LLM-вызова через EventHandler
  • OpenTelemetry + LangFuse — опциональный трейсинг нод графа (включается при заданных env-ключах LangFuse)
  • Сохранение отчёта — автоматически в reports/report_<task-id>_<timestamp>.md

Требования

  • JDK 21+
  • Gradle 8+
  • API-ключ одного из провайдеров или локальный LMStudio-сервер

Быстрый старт

1. Клонировать репозиторий

git clone <repo-url>
cd log-analyzer

2. Настроить API-ключ (для облачных моделей)

# Windows
set OPENAI_API_KEY=sk-...
set ANTHROPIC_API_KEY=sk-ant-...

# Linux / macOS
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-...

3. Запустить

./gradlew run --console=plain

Агент запросит описание задачи (Enter — demo-алерт), затем предложит меню выбора модели:

Описание задачи (Enter = demo alert): [ALERT] payment-service: error rate 8.3% in production

+--------------------------------------------------+
|          Select model for log analysis           |
+--------------------------------------------------+

  Cloud models:
    1. OpenAI GPT-4o             -- requires OPENAI_API_KEY
    2. OpenAI GPT-4o-mini        -- requires OPENAI_API_KEY
    3. Anthropic Claude Sonnet 4.5 -- requires ANTHROPIC_API_KEY
    4. Anthropic Claude Haiku 4.5  -- requires ANTHROPIC_API_KEY

  Local models:
    5. LMStudio (local)         -- OpenAI-compatible API

  Choice [1]:

Описание задачи также можно передать первым аргументом:

./gradlew run --args="[ALERT] payment-service error spike in production" --console=plain

GCP-параметры (gcp_project, time_range_minutes, min_severity) задаются в SlackTask.metadata — в Main.kt они выставлены под demo. MockGcpLogClient их логирует, но читает sample.log; реальный RealGcpLogClient будет использовать эти значения для запроса.

4. Результат

Отчёт и метаданные выводятся в терминал, отчёт сохраняется в reports/:

============================================================
FINAL REPORT
============================================================
## Summary
...
============================================================

--- Pipeline Metadata ---
  Source      : [TASK-1042] payment-service | GCP: resource.labels.container_name="payment-service" AND severity>=ERROR
  Chunks      : 3 (parallel slots: 3)
  Total errors: 5
  Critic iters: 0
  Duration    : 98420ms
  Generated   : 2026-04-12T17:00:00Z

Report saved: reports/report_TASK-1042_2026-04-12_20-00.md

Локальные модели (LMStudio)

  1. Скачать и установить LMStudio
  2. Загрузить модель с поддержкой function calling (см. таблицу ниже)
  3. Запустить Local Server в LMStudio
  4. Выбрать пункт 5 в меню
LMStudio server URL [http://localhost:1234]: http://192.168.0.10:1234
Loaded model name [lmstudio-local]:          ← Enter

Совместимость моделей

Серия Function calling Примечание
Qwen2.5 Instruct Рекомендуется. Q4_K_M: ~9 GB (14B) / ~20 GB (32B)
Qwen2.5 Coder Проверено на 32B
Llama 3.x Instruct Проверено на Llama 3.3 70B
Qwen3 (любые) Thinking-режим вызывает бесконечную петлю в subgraphWithTask

Рекомендация для RTX 3090 (24 GB VRAM): Qwen2.5-14B-Instruct Q4_K_M (~9 GB) — быстро и точно. Qwen2.5-32B-Instruct Q4_K_M (~20 GB) — лучшее качество.

Для log-discovery (агентная фаза с несколькими вызовами инструментов) function calling обязателен — локальная модель должна уверенно вызывать GCP-тулы.

Наблюдаемость (OpenTelemetry + LangFuse)

Агент создаёт OpenTelemetry-спаны для каждой ноды графа, LLM-вызова и инструмента — только если настроен LangFuse (при параллельном выполнении без экспортёра span-дерево Koog некорректно закрывает дочерние span-ы parallel-ноды).

С LangFuse (self-hosted)

  1. Запустить LangFuse через Docker Compose:

    git clone https://github.com/langfuse/langfuse
    cd langfuse
    docker compose up -d
    # UI: http://localhost:3000
  2. Создать проект в UI → получить LANGFUSE_PUBLIC_KEY и LANGFUSE_SECRET_KEY.

  3. Задать переменные окружения перед запуском:

    # Windows
    set LANGFUSE_HOST=http://localhost:3000
    set LANGFUSE_PUBLIC_KEY=pk-lf-...
    set LANGFUSE_SECRET_KEY=sk-lf-...
    
    # Linux / macOS
    export LANGFUSE_HOST=http://localhost:3000
    export LANGFUSE_PUBLIC_KEY=pk-lf-...
    export LANGFUSE_SECRET_KEY=sk-lf-...
  4. Запустить агент — трейсы появятся в LangFuse:

    --- Observability ---
    LangFuse : enabled → http://localhost:3000
    

В каждом трейсе отображаются: дерево нод графа, latency каждого LLM-вызова, входные/выходные токены.

Примечание: LANGFUSE_HOST опциональна (по умолчанию http://localhost:3000). Если ключи не заданы — OpenTelemetry не устанавливается, агент работает в обычном режиме.

Архитектура

Пайплайн (граф Koog)

SlackTask (тип, описание, сервис, окружение, priority, metadata)
     │
     ▼
┌──────────────┐
│ capture-task │  Сохраняет задачу в closure, фиксирует время старта
└──────────────┘
     │ SlackTask (passthrough)
     ▼
┌──────────────┐
│ log-discovery│  LLM + GCP-тулы: listGcpServices → previewGcpLogs → queryGcpLogs
└──────────────┘  Строит и валидирует фильтр GCP Logging Query Language
     │ GcpFetchPlan (projectId + filter + timeRange + reasoning)
     ▼
┌───────────────┐
│ fetch-and-chunk│  Детерминированно: GcpLogClient.queryLogs(plan) → режет на чанки
└───────────────┘
     │ ChunkList
     ▼
┌──────────────────────────────────────────────┐
│            parallel-analyzer                 │  fan-out
│  ┌────────┐   ┌────────┐   ┌────────┐        │
│  │ slot-0 │   │ slot-1 │   │ slot-2 │        │  3 параллельных LLM-вызова
│  └────────┘   └────────┘   └────────┘        │  (репрезентативный срез: начало/середина/конец)
│              fold() merge                    │  fan-in
└──────────────────────────────────────────────┘
     │ AnalysisList (merged, sorted by chunkId)
     ▼
┌──────────────────┐
│  capture-context │  Сохраняет в closure, сбрасывает счётчик критика
└──────────────────┘
     │ AnalysisList
     ▼
┌──────────┐   ◄────────────────────────────────┐
│ reporter │   LLM генерирует Markdown-отчёт     │ (retry с фидбеком)
└──────────┘                                     │
     │ Report                                    │
     ▼                                           │
┌────────┐   [rejected AND iter < 3]  ┌──────────────┐
│ critic │ ──────────────────────────►│ prepare-retry│
└────────┘                            └──────────────┘
     │ [approved OR iter >= 3]
     ▼
┌─────────────────┐
│ finalize-report │  Строит DomainReport + ReportMetadata в коде (не LLM)
└─────────────────┘
     │ DomainReport
     ▼
  Markdown-отчёт  →  reports/report_*.md

Domain Model

SlackTask                     DomainReport
  ├─ id, type, title            ├─ content (Markdown)
  ├─ description                ├─ isValid
  ├─ service, environment       └─ ReportMetadata
  ├─ priority, requestedBy            ├─ sourceFile (task id + GCP filter)
  ├─ createdAt                        ├─ analyzedChunks
  └─ metadata                         ├─ totalErrors
       ├─ gcp_project                 ├─ criticIterations
       ├─ time_range_minutes          ├─ parallelSlots
       └─ min_severity                ├─ durationMs
                                       └─ generatedAt

        промежуточный тип фазы discovery:
        GcpFetchPlan (projectId + filter + timeRangeMinutes + reasoning)

Структура проекта

src/main/kotlin/ru/cephei/
├── Main.kt                        # Точка входа: строит SlackTask, EventHandler, observability
├── config/
│   ├── AgentConfig.kt             # Фабрики executor-ов, системный промпт, константы
│   └── ModelSelector.kt           # Интерактивный выбор модели в терминале
├── strategy/
│   └── LogAnalysisStrategy.kt     # Граф: discovery → fetch → fan-out/fan-in → critic loop
├── model/
│   ├── SlackTask.kt               # Входной объект стратегии (задача из Slack-трекера)
│   ├── GcpFetchPlan.kt            # План запроса к GCP (выход log-discovery)
│   ├── DomainReport.kt            # Выходной объект стратегии (Aggregate)
│   ├── ReportMetadata.kt          # Метаданные выполнения (Value Object)
│   ├── LogChunk.kt                # Фрагмент лог-файла
│   ├── ChunkList.kt               # Контейнер чанков
│   ├── AnalysisResult.kt          # Результат анализа одного чанка
│   ├── ChunkAnalysis.kt           # Привязка результата к ID чанка
│   ├── AnalysisList.kt            # Контейнер всех результатов анализа
│   ├── Report.kt                  # LLM-отчёт (сериализуемый для subgraphWithTask)
│   ├── CriticVerdict.kt           # Вердикт Critic Agent
│   └── AnalysisOptions.kt         # Опции пайплайна (Value Object, зарезервировано)
├── tools/
│   ├── GcpLogToolSet.kt           # GcpLogClient + MockGcpLogClient + LLM-тулы GCP
│   ├── ChunkingTool.kt            # Разбивка лога на чанки по маркерам даты
│   └── LogToolSet.kt              # Файловые тулы (readLogFile, getLogStats, getLogChunk)
└── util/
    └── ReportSaver.kt             # Сохранение отчёта в файл

src/test/kotlin/ru/cephei/
├── model/ModelTest.kt             # 17 тестов доменных моделей
└── tools/
    ├── ChunkingToolTest.kt        # 12 тестов ChunkingTool
    ├── LogToolSetTest.kt          # 34 теста LogToolSet
    └── GcpLogToolSetTest.kt       # 11 тестов GCP-инструментов и Mock-клиента

reports/                           # Генерируется автоматически
sample.log                         # Пример лог-файла (источник для MockGcpLogClient)

Запуск тестов

./gradlew test

74 теста, покрывают доменные модели, чанкинг, файловые и GCP-инструменты.

Сборка

./gradlew build

Технологии

Технология Версия Назначение
Kotlin 2.3.0 Язык разработки
Koog agents 0.7.3 Агентский фреймворк (JetBrains)
kotlinx.serialization JSON-схемы для structured output
kotlinx.coroutines 1.10.2 Параллельное выполнение нод (parallel())
OpenTelemetry SDK 1.51.0 Distributed tracing (транзитивно через Koog)
LangFuse self-hosted Observability UI (опционально, через env vars)
Logback 1.5.16 Логирование
JUnit (kotlin-test) Тестирование
JVM toolchain 21 Целевая платформа

Ключевые паттерны Koog

Agentic discovery + детерминированное исполнение

// LLM планирует запрос с помощью GCP-инструментов
val logDiscovery by subgraphWithTask<SlackTask, GcpFetchPlan>(
    tools = GcpLogToolSet(gcpClient).asTools()
) { task -> /* промпт: listGcpServices → previewGcpLogs → вернуть GcpFetchPlan */ }

// Детерминированная нода исполняет план
val fetchAndChunk by node<GcpFetchPlan, ChunkList>("fetch-and-chunk") { plan ->
    val content = gcpClient.queryLogs(plan.projectId, plan.filter, plan.timeRangeMinutes)
    ChunkList(chunkingTool.split(content))
}

Fan-out / Fan-in

val parallelAnalyzer by parallel(slot0, slot1, slot2, dispatcher = Dispatchers.Default) {
    fold(AnalysisList(emptyList())) { acc, partial ->
        AnalysisList((acc.analyses + partial.analyses).sortedBy { it.chunkId })
    }
}

Conditional loop (Generator-Critic)

edge(critic forwardTo prepareRetry onCondition { verdict ->
    !verdict.approved && criticIteration < AgentConfig.MAX_CRITIC_ITERATIONS
})
edge(critic forwardTo finalizeReport onCondition { verdict ->
    verdict.approved || criticIteration >= AgentConfig.MAX_CRITIC_ITERATIONS
})

Domain-typed strategy

// Вместо AIAgentGraphStrategy<String, String>:
fun createLogAnalysisStrategy(
    gcpClient: GcpLogClient = MockGcpLogClient(),
    chunkingTool: ChunkingTool = ChunkingTool(),
): AIAgentGraphStrategy<SlackTask, DomainReport>

val domainReport: DomainReport = agent.run(
    SlackTask(id = "TASK-1042", type = "log_analysis", title = "...", description = "...")
)

Место в системе (roadmap)

createLogAnalysisStrategy() — один пайплайн в будущей мульти-агентной матрёшке:

Long-Polling Executor (Slack)
  → SlackTask
  → Router Graph Strategy (читает task.type)
      → createLogAnalysisStrategy()   ← реализовано
      → createCodeReviewStrategy()
      → createDebugStrategy()
      ...

Переход на реальный GCP не затрагивает стратегию — достаточно подменить клиент:

createLogAnalysisStrategy(
    gcpClient = RealGcpLogClient(credentials = GoogleCredentials.getApplicationDefault())
)

Пример отчёта

## Summary
The application experienced errors with the payment gateway and database
connection pool exhaustion, as well as issues with Redis cache.

## Critical Issues
* 2026-04-10 09:02:11 [PaymentService] Payment gateway timeout: stripe, order_id=55312
* 2026-04-10 10:00:00 [Database] Connection pool exhausted — all 10 connections in use

## Warnings
* High connection pool usage: 9/10 active (2026-04-10 08:31:45)
* Slow query detected: SELECT * FROM orders WHERE status='pending' (1240ms)

## Recommendations
* Increase database connection pool size or optimize queries
* Implement retry mechanisms for payment gateway timeouts

Лицензия

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages