Агент для анализа логов на базе фреймворка 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-сервер
git clone <repo-url>
cd log-analyzer# 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-..../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=plainGCP-параметры (
gcp_project,time_range_minutes,min_severity) задаются вSlackTask.metadata— вMain.ktони выставлены под demo.MockGcpLogClientих логирует, но читаетsample.log; реальныйRealGcpLogClientбудет использовать эти значения для запроса.
Отчёт и метаданные выводятся в терминал, отчёт сохраняется в 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
- Загрузить модель с поддержкой function calling (см. таблицу ниже)
- Запустить Local Server в LMStudio
- Выбрать пункт
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-InstructQ4_K_M (~9 GB) — быстро и точно.Qwen2.5-32B-InstructQ4_K_M (~20 GB) — лучшее качество.Для
log-discovery(агентная фаза с несколькими вызовами инструментов) function calling обязателен — локальная модель должна уверенно вызывать GCP-тулы.
Агент создаёт OpenTelemetry-спаны для каждой ноды графа, LLM-вызова и инструмента — только если настроен LangFuse (при параллельном выполнении без экспортёра span-дерево Koog некорректно закрывает дочерние span-ы parallel-ноды).
-
Запустить LangFuse через Docker Compose:
git clone https://github.com/langfuse/langfuse cd langfuse docker compose up -d # UI: http://localhost:3000
-
Создать проект в UI → получить
LANGFUSE_PUBLIC_KEYиLANGFUSE_SECRET_KEY. -
Задать переменные окружения перед запуском:
# 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-...
-
Запустить агент — трейсы появятся в LangFuse:
--- Observability --- LangFuse : enabled → http://localhost:3000
В каждом трейсе отображаются: дерево нод графа, latency каждого LLM-вызова, входные/выходные токены.
Примечание:
LANGFUSE_HOSTопциональна (по умолчаниюhttp://localhost:3000). Если ключи не заданы — OpenTelemetry не устанавливается, агент работает в обычном режиме.
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
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 test74 теста, покрывают доменные модели, чанкинг, файловые и 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 | Целевая платформа |
// 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))
}val parallelAnalyzer by parallel(slot0, slot1, slot2, dispatcher = Dispatchers.Default) {
fold(AnalysisList(emptyList())) { acc, partial ->
AnalysisList((acc.analyses + partial.analyses).sortedBy { it.chunkId })
}
}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
})// Вместо 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 = "...")
)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 timeoutsMIT