Стратегии журналирования и документирования
Сохраняйте версии запросов, входные и выходные данные для воспроизводимой отладки.
«Стратегии журналирования и документирования» — бесплатный урок AI Prompt Engineering на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Prompt Engineering, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Prompt Engineering содержит 4 уроков всего.
Почему важно журналировать запросы
Без журналирования сбои запросов остаются незаметными, пока о них не сообщит пользователь. С журналированием можно:
- Обнаруживать регрессии сразу после их возникновения
- Точно воспроизводить любой прошлый сбой
- Измерять улучшения со временем по мере развития запросов
- Проверять поведение модели на соответствие требованиям и безопасность
Журналирование не является необязательным для рабочих систем запросов — это основа надёжных приложений на базе LLM.
Минимально необходимая запись журнала
При каждом взаимодействии с запросом необходимо как минимум записывать следующие поля:
timestamp: ISO 8601 UTCprompt_id: какой шаблон запроса использовалсяmodel: точное название и версия моделиtemperature: параметр выборкиinput: сообщение пользователя (или хеш, если есть PII)output: ответ моделиlatency_ms: время откликаtokens_used: токены входных и выходных данных
import time, json
from datetime import datetime, timezone
def logged_call(prompt_id, system_prompt, user_message, model='gpt-4o', temperature=0.7):
start = time.time()
resp = client.chat.completions.create(
model=model,
messages=[
{'role': 'system', 'content': system_prompt},
{'role': 'user', 'content': user_message}
],
temperature=temperature
)
latency = int((time.time() - start) * 1000)
output = resp.choices[0].message.content
log_entry = {
'timestamp': datetime.now(timezone.utc).isoformat(),
'prompt_id': prompt_id,
'model': model,
'temperature': temperature,
'input': user_message,
'output': output,
'latency_ms': latency,
'input_tokens': resp.usage.prompt_tokens,
'output_tokens': resp.usage.completion_tokens
}
append_log(log_entry)
return outputФормат структурированного журналирования
Используйте JSON с разделителями-новыми строками (JSONL) для файлов журнала. Каждая строка представляет собой полный корректный объект JSON. Этот формат:
- Позволяет легко дополнять журнал без блокировок
- Удобен для чтения с помощью jq, pandas и всех средств агрегации журналов
- Подходит для потоковой обработки — каждую строку можно обрабатывать по мере поступления
import json
LOG_FILE = 'prompt_logs.jsonl'
def append_log(entry):
with open(LOG_FILE, 'a') as f:
f.write(json.dumps(entry) + '\n')
def read_logs():
with open(LOG_FILE) as f:
return [json.loads(line) for line in f if line.strip()]
# Query: all entries for prompt_id 'summarize_v3'
logs = read_logs()
summarize_logs = [e for e in logs if e['prompt_id'] == 'summarize_v3']
print(f'Total calls to summarize_v3: {len(summarize_logs)}')Версионирование запросов
Запросы со временем меняются. Без версионирования нельзя воспроизвести прошлое поведение или сравнить выходные данные модели для разных версий запроса. Используйте идентификатор версии в каждой записи журнала.
Простое версионирование: строка семантической версии (например, v1.2.3) или хеш изменения Git. Храните версии запросов в отдельном файле, чтобы любую версию можно было получить для повторного воспроизведения.
PROMPTS = {
'summarize': {
'v1': 'Summarize the following text.',
'v2': 'Summarize the following text in 3 sentences.',
'v3': 'Summarize the following text in exactly 3 sentences. '
'Start each sentence on a new line. No bullet points.'
}
}
CURRENT_VERSIONS = {'summarize': 'v3'}
def get_prompt(prompt_id):
version = CURRENT_VERSIONS[prompt_id]
return version, PROMPTS[prompt_id][version]
version, prompt = get_prompt('summarize')
log_entry['prompt_version'] = versionОбработка PII в журналах
Входные данные пользователя могут содержать персональные данные, позволяющие установить личность (PII). Журналирование необработанных входных данных может нарушать GDPR или CCPA. Возможные варианты:
- Хеширование: сохранять SHA-256 входных данных — можно воспроизводить для удаления дубликатов, но не для повторного воспроизведения
- Маскирование: использовать регулярное выражение или модель NER, чтобы заменять PII перед журналированием
- Раздельное хранение: журналировать PII в зашифрованном хранилище с контролем доступа; в основном журнале сохранять только ссылочный ID
import hashlib, re
def redact_pii(text):
# Redact email addresses
text = re.sub(r'[\w.-]+@[\w.-]+\.\w+', '[EMAIL]', text)
# Redact phone numbers (US format)
text = re.sub(r'\b\d{3}[-.]\d{3}[-.]\d{4}\b', '[PHONE]', text)
return text
def hash_input(text):
return hashlib.sha256(text.encode()).hexdigest()[:16]
log_entry['input'] = redact_pii(user_message)
log_entry['input_hash'] = hash_input(user_message)Отслеживание задержки и стоимости
Журналы позволяют создавать панели мониторинга стоимости и задержки. Отслеживайте показатели для каждой версии запроса, чтобы обнаруживать регрессии производительности или стоимости после изменения запроса:
def compute_cost(entry, price_per_1m_input=5.0, price_per_1m_output=15.0):
input_cost = entry['input_tokens'] / 1_000_000 * price_per_1m_input
output_cost = entry['output_tokens'] / 1_000_000 * price_per_1m_output
return input_cost + output_cost
def prompt_stats(prompt_id, version):
logs = [e for e in read_logs()
if e['prompt_id'] == prompt_id and e.get('prompt_version') == version]
if not logs:
return
avg_latency = sum(e['latency_ms'] for e in logs) / len(logs)
total_cost = sum(compute_cost(e) for e in logs)
print(f'{prompt_id} {version}: {len(logs)} calls, avg {avg_latency:.0f}ms, total ${total_cost:.4f}')Журналирование оценки выходных данных
Помимо необработанных журналов, сохраняйте оценки качества рядом с каждой записью журнала. Это позволяет анализировать тенденции: повышается ли качество выходных данных между версиями запроса?
def evaluated_call(prompt_id, system_prompt, user_message, evaluator_fn):
output = logged_call(prompt_id, system_prompt, user_message)
score = evaluator_fn(user_message, output)
# Update the last log entry with the evaluation score
logs = read_logs()
last = logs[-1]
last['eval_score'] = score
last['eval_pass'] = score >= 0.8
# Rewrite the last line
with open(LOG_FILE, 'a') as f:
# In practice, use a DB or separate eval log
pass
return output, scoreДокументирование запросов
Для каждого шаблона запроса должна существовать сопутствующая документация, содержащая:
- Назначение: какую задачу выполняет этот запрос
- Переменные: какие заполнители существуют и какие значения они ожидают
- Известные ограничения: входные данные, на которых запрос заведомо даёт сбой
- История версий: что изменилось в каждой версии и почему
- Проверочные примеры: ссылка на набор проверок для этого запроса
PROMPT_DOCS = {
'summarize': {
'purpose': 'Summarize a single text passage into 3 sentences.',
'variables': {'text': 'The passage to summarize (max 2000 tokens)'},
'known_limitations': [
'Fails to preserve numbers accurately for texts with many statistics',
'May not summarize correctly for non-English text'
],
'versions': {
'v1': 'Initial version — vague length instruction',
'v2': 'Added 3-sentence limit',
'v3': 'Added line-break and no-bullet formatting fix'
},
'test_suite': 'tests/test_summarize.py'
}
}Использование централизованных служб журналирования
Для рабочих систем записывайте журналы в централизованный сервис, а не в локальные файлы:
- LangSmith: встроенная в LangChain платформа трассировки и оценки
- Weights and Biases Prompts: отслеживание экспериментов с запросами
- Datadog / Grafana: стандартные операционные панели мониторинга с настраиваемыми показателями
- Supabase / PostgreSQL: запрос журналов с помощью языка структурированных запросов для разового анализа
Структура остаётся той же; меняется только место назначения.
# Example: writing to Supabase
from supabase import create_client
supabase = create_client('https://xxx.supabase.co', 'your-anon-key')
def log_to_supabase(entry):
supabase.table('prompt_logs').insert(entry).execute()
# Now query with SQL:
# SELECT prompt_id, prompt_version, AVG(latency_ms), COUNT(*)
# FROM prompt_logs
# WHERE timestamp > NOW() - INTERVAL '7 days'
# GROUP BY prompt_id, prompt_version
# ORDER BY COUNT(*) DESC;Оповещение о всплесках сбоев
Настройте оповещения, когда доля сбоев превышает заданный порог. Например, если более 10% вызовов промпта возвращают некорректный JSON за 5-минутный период, отправляйте оповещение.
from collections import deque
from datetime import datetime, timezone, timedelta
recent_results = deque(maxlen=100) # sliding window
def track_and_alert(prompt_id, success, alert_fn, threshold=0.10):
recent_results.append({'success': success, 'time': datetime.now(timezone.utc)})
window = [
r for r in recent_results
if r['time'] > datetime.now(timezone.utc) - timedelta(minutes=5)
]
if not window:
return
fail_rate = sum(1 for r in window if not r['success']) / len(window)
if fail_rate > threshold:
alert_fn(f'ALERT: {prompt_id} failure rate {fail_rate:.0%} in last 5 min')Хранение и архивирование
Определите политику хранения журналов:
- Необработанные журналы вызовов: 30 дней (скользящий период) — большой объём, необходимы для отладки недавних проблем
- Агрегированные показатели: 1 год — необходимы для анализа тенденций и прогнозирования затрат
- Журналы сбоев: бессрочно — необходимы для анализа закономерностей, связанных с первопричинами
Сжимайте и архивируйте необработанные журналы через 30 дней. Никогда не удаляйте журналы сбоев — это ваша накопленная база знаний по проектированию промптов.
Проверка знаний
Каково основное преимущество использования формата JSONL (JSON с разделителями строк) для журналов промптов по сравнению с одним большим массивом JSON?
Итоги: ведение журналов и документация
Основные методы ведения журналов и документирования промптов:
- Записывайте каждый вызов: временная метка, идентификатор промпта, версия, модель, температура, входные данные, выходные данные, задержка, токены
- Используйте формат JSONL: удобен для добавления записей и доступен для запросов с помощью стандартных инструментов
- Версионируйте промпты: каждое изменение получает новую версию; журналы ссылаются на эту версию
- Обрабатывайте PII: удаляйте или хешируйте конфиденциальные входные данные перед записью в журнал
- Отслеживайте затраты и задержку: выявляйте ухудшения после обновления промпта
- Настраивайте оповещения о всплесках сбоев: отслеживайте долю сбоев в скользящем окне
На этом завершается курс 17 по отладке сбоев промптов. Далее: инъекция промпта и защита от неё.
Часто задаваемые вопросы
Урок «Стратегии журналирования и документирования» бесплатный?
Да — полный текст урока «Стратегии журналирования и документирования» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Prompt Engineering, подпишись на CoddyKit PRO. Курс AI Prompt Engineering содержит 4 уроков всего.
Чему я научусь в уроке «Стратегии журналирования и документирования»?
Сохраняйте версии запросов, входные и выходные данные для воспроизводимой отладки. Ты практикуешь AI Prompt Engineering с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Prompt Engineering?
Предыдущий опыт не требуется. AI Prompt Engineering на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Стратегии журналирования и документирования»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Prompt Engineering?
Да. Каждый урок AI Prompt Engineering включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Диагностика неожиданных результатов
- Анализ первопричин запросов
- Системный подход к отладке
- Стратегии журналирования и документирования