Интегрировать OpenTelemetry в RAG
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Интегрировать OpenTelemetry в RAG
1. Цель задачи
Освоить инструментирование RAG-пайплайна с помощью OpenTelemetry (OTel) для трассировки ключевых шагов: retrieval, reranking, генерация ответа. Реализовать сбор трейсов, экспорт их в Jaeger и визуализацию полной цепочки вызовов, чтобы на практике убедиться в прозрачности и диагностируемости системы.
Ключевой результат В Jaeger отображается целостный trace с тремя вложенными span’ами (retrieve, rerank, generate), по каждому из которых видны длительность, атрибуты (число найденных документов, модель reranker, модель LLM и т.д.) и корректная вложенность.
2. Исходные данные
Перед началом необходимо иметь:
| Что нужно | Откуда взять |
|---|---|
| RAG-система (минимальная) | Пет-проект из предыдущих занятий или любой open‑source пример (LangChain RAG, LlamaIndex RAG) |
| OpenTelemetry SDK для Python | pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp |
| Jaeger (в Docker) | docker run -d -p 16686:16686 -p 4317:4317 jaegertracing/all-in-one:latest |
| Пример запросов и документов | Любая коллекция текстов (например, статьи Wikipedia или датасет SQuAD) |
Если нет реальной RAG-системы — симулируем:
- Напишите простую RAG-функцию, которая:
- использует sentence-transformers для эмбеддингов,
- faiss (или in-memory list) для поиска,
- заглушку для reranker (например,
sorted()[:k]по score), - любой LLM (OpenAI API, или dummy-функцию, возвращающую "Generated answer").
- Заверните три шага в отдельные функции retrieve(query), rerank(docs), generate(context, query).
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| Трассировка | OpenTelemetry SDK (Python) | Создание и управление спанами |
| Экспорт | OTLP gRPC exporter | Отправка трейсов в Jaeger |
| Бэкенд трассировки | Jaeger (all-in-one) | Приём, хранение, визуализация трейсов |
| RAG пайплайн | LangChain / LlamaIndex / собственный код | Исполнение retrieve → rerank → generate |
| Векторный поиск | FAISS / Qdrant (in-memory) | Поиск релевантных документов |
| LLM | OpenAI / Anthropic / mock-функция | Генерация ответа |
| Контейнеризация (опционально) | Docker | Запуск Jaeger |
4. Этапы выполнения
Этап 1: Подготовка окружения (15 минут)
Действия
- Установите необходимые пакеты:
pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp pip install opentelemetry-instrumentation-openai # если используете OpenAI - Запустите Jaeger в Docker:
docker run -d --name jaeger \ -p 16686:16686 \ -p 4317:4317 \ jaegertracing/all-in-one:latest - Убедитесь, что веб-интерфейс Jaeger доступен: http://localhost:16686.
Ожидаемый результат этапа Jaeger запущен, стартовый экран виден в браузере.
Этап 2: Инициализация OpenTelemetry Tracer Provider (20 минут)
Действия
- Создайте файл tracing.py с конфигурацией OTel:
from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter provider = TracerProvider() processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="localhost:4317", insecure=True)) provider.add_span_processor(processor) trace.set_tracer_provider(provider) tracer = trace.get_tracer(__name__) - В главном файле rag.py импортируйте
tracer:from tracing import tracer
Ожидаемый результат этапа Код инициализации трассировки готов, программа не зависает при запуске.
Этап 3: Инструментирование retrieval (20 минут)
Действия
- Создайте функцию retrieve(query, top_k=5).
- Внутри функции создайте span:
with tracer.start_as_current_span("retrieve") as span: # логика поиска документов docs = vector_store.search(query, top_k) span.set_attribute("db.system", "faiss") span.set_attribute("top_k", top_k) span.set_attribute("num_docs_retrieved", len(docs)) - Добавьте обработку ошибок: при исключении запишите span.set_status(Status(StatusCode.ERROR)).
Ожидаемый результат этапа При вызове retrieve() в Jaeger появляется span с именем retrieve и заданными атрибутами.
Этап 4: Инструментирование rerank и generate (25 минут)
Действия
- Аналогично создайте span для rerank:
with tracer.start_as_current_span("rerank") as span: reranked = reranker.rerank(docs, query) span.set_attribute("model", "cross-encoder/ms-marco-MiniLM-L2-v2") span.set_attribute("num_input_docs", len(docs)) span.set_attribute("num_output_docs", len(reranked)) - Для generate:
with tracer.start_as_current_span("generate") as span: answer = llm.generate(context, query) span.set_attribute("llm.model", "gpt-4") span.set_attribute("input_tokens", count_tokens(context)) span.set_attribute("output_tokens", count_tokens(answer)) - Убедитесь, что все три span’а вложены в родительский span (например,
rag_query):with tracer.start_as_current_span("rag_query") as root_span: docs = retrieve(query) reranked = rerank(docs) answer = generate(reranked, query)
Ожидаемый результат этапа В Jaeger виден корневой span rag_query с тремя дочерними span’ами, каждый — со своими атрибутами.
Этап 5: Верификация и отладка (20 минут)
Действия
- Выполните несколько разных запросов к RAG-системе (минимум 3).
- Откройте Jaeger UI → Search → сервис
your-service-name(по умолчанию__main__). - Найдите трейсы, убедитесь, что:
- каждый запрос отображается как отдельный trace,
- в каждом trace три вложенных span’а,
- тайминги логичны (retrieve < rerank < generate).
- Искусственно сломайте один из шагов (например, выключите векторную БД) и проверьте, что span помечается как ERROR.
Ожидаемый результат этапа Все трейсы отображаются корректно, ошибки видны.
5. Критерии приемки (Definition of Done)
- В Jaeger UI есть хотя бы один trace, у которого корневой span называется
rag_query. - Внутри корневого span присутствуют ровно три дочерних span’а:
retrieve,rerank,generate. - Каждый span содержит как минимум два кастомных атрибута (например,
num_docs_retrieved,llm.model). - Длительность span’ов не является нулевой или отрицательной.
- При генерации исключения в одном из шагов span помечается статусом
ERRORи содержит описание ошибки. - Код трассировки вынесен в отдельный модуль
tracing.pyи переиспользуется. - В
requirements.txtдобавлены все зависимости OpenTelemetry. - Для каждого запроса к RAG создаётся отдельный новый trace.
6. Ожидаемый результат
Основной артефакт Папка с файлами:
rag.py– основная RAG-логика с интеграцией OTel,tracing.py– инициализация TracerProvider и экспортёра,requirements.txt,- скриншот Jaeger UI, показывающий tree‑view с тремя span’ами.
Дополнительно
docker-compose.ymlдля запуска Jaeger (если используется).- Краткая документация (в комментариях к коду), какие атрибуты установлены и зачем.
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| Jaeger не видит трейсы (404 в UI) | Проверьте, что Jaeger запущен на порту 4317 (gRPC) и экспортёр использует localhost. В Docker Desktop используйте host.docker.internal. Убедитесь, что insecure=True для gRPC. |
| Span’ы не вложенные | Гарантируйте, что дочерние span’ы создаются внутри контекста родительского (используйте with tracer.start_as_current_span внутри такого же with для родителя). |
opentelemetry-instrumentation-openai конфликтует с версией OpenAI | Установите конкретную версию: pip install openai==1.0.0 opentelemetry-instrumentation-openai==0.45b0. |
| Не видны атрибуты в Jaeger | Убедитесь, что атрибуты строковые или числовые (не списки/дикты). Для списков используйте span.set_attribute("key", str(value)). |
| Трейсы не отображаются после перезапуска Jaeger | Jaeger in-memory теряет данные при перезапуске — используйте постоянное хранилище (Elasticsearch) или перезапускайте редко. |
8. Бюджет времени (оценка)
| Этап | Время |
|---|---|
| 1. Подготовка окружения | 15 мин |
| 2. Инициализация OTel | 20 мин |
| 3. Инструментирование retrieval | 20 мин |
| 4. Инструментирование rerank и generate | 25 мин |
| 5. Верификация и отладка | 20 мин |
| Итого | 1 ч 40 мин |
Примечание Время рассчитано на один подход без учёта дополнительного изучения документации. При первом выполнении заложите ещё 30–60 минут на чтение ошибок и настройку экспортёра.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 42 | Как установить OpenTelemetry Python SDK |
| 43 | Настройка экспортёра OTLP gRPC |
| 44 | Разница между BatchSpanProcessor и SimpleSpanProcessor |
| 45 | Как запустить Jaeger в Docker |
| 46 | Создание вложенных спанов с помощью start_as_current_span |
| 47 | Установка атрибутов и статусов спанов |
| 48 | Обработка ошибок в OTel (RecordException) |
| 49 | Интеграция OTel с LangChain (instrumentation) |
| 50 | Визуализация трейсов в Jaeger UI |
| 51 | Отладка: трейсы не отображаются |
10. Чек-лист самопроверки
- Я запустил Jaeger перед выполнением кода и проверил, что UI отвечает.
- Я создал отдельный модуль
tracing.pyсTracerProviderиBatchSpanProcessor. - Я импортировал
tracerв основной скрипт и использовал его для каждого span’а. - Все три основные функции (
retrieve,rerank,generate) обёрнуты в span’ы. - Я добавил хотя бы два атрибута в каждый span (например, количество документов, модель LLM).
- Я выполнил минимум 3 запроса и убедился, что в Jaeger появилось 3 отдельных трейса.
- Я проверил, что при ошибке (например, выключение БД) span помечается красным в Jaeger.
- Я зафиксировал скриншот UI с видимой вложенностью span’ов.
- Я добавил все зависимости в
requirements.txt.