0Pricing
AI Prompt Engineering · 课时

日志记录与文档策略

记录提示词版本、输入和输出,以便进行可复现的调试。

日志记录与文档策略 是 CoddyKit 上的免费 AI Prompt Engineering 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Prompt Engineering 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Prompt Engineering 课程共包含 4 节课。

提示词日志记录为何重要

没有日志记录,提示词失败只有在用户报告后才会被发现。有了日志记录,您可以:

  • 在回归发生的瞬间检测到它
  • 完全按照过去发生的方式重现任何失败
  • 随着提示词演进,衡量其长期改进情况
  • 审查模型行为是否符合要求或安全标准

对于生产环境中的提示词系统,日志记录不是可选项——它是可靠 LLM 应用的基础。

最小可用日志条目

每次提示词交互至少应记录以下字段:

  • timestamp:ISO 8601 UTC
  • prompt_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 对象。这种格式具有以下特点:

  • 无需加锁即可 append
  • 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;

失败率激增告警

请在失败率超过阈值并激增时配置告警。例如:如果在 5 分钟窗口内,针对某个提示词的调用中有超过 10% 返回无效 JSON,请发送告警。

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')

保留与归档

定义日志保留策略:

  • 原始 call 日志:30 天(滚动保留)—数量大,需要用于调试近期问题
  • 聚合指标:1 年 —需要用于趋势分析和成本预测
  • 失败日志:永久保留 —需要用于分析根因模式

30 天后压缩并归档原始日志。绝不要删除失败日志 —它们是您进行提示词工程时的组织记忆。

知识检查

与单个大型 JSON 数组相比,对提示词日志使用以换行符分隔的 JSON(JSONL)格式的主要优势是什么?

回顾:日志记录与文档

提示词日志记录与文档编写的关键实践:

  • 记录每次 call:时间戳、提示词标识符、版本、模型、温度、输入、输出、延迟、令牌数
  • 使用 JSONL 格式:便于 append,可使用标准工具查询
  • 为提示词添加版本:每次更改都使用新版本;日志引用相应版本
  • 处理 PII:记录前对敏感输入进行脱敏或哈希处理
  • 跟踪成本和延迟:提示词更新后检测回归
  • 失败率激增时发出告警:监控滑动窗口中的失败率

第 17 门课程“调试提示词失败”到此结束。下一课:提示词注入与防御。

常见问题解答

「日志记录与文档策略」课时是免费的吗?

是的 — 「日志记录与文档策略」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Prompt Engineering 课程的其余内容,请升级到 CoddyKit PRO。 AI Prompt Engineering 课程共包含 4 节课。

「日志记录与文档策略」这节课中我会学到什么?

记录提示词版本、输入和输出,以便进行可复现的调试。 你通过在浏览器中直接运行的动手代码来练习 AI Prompt Engineering,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 AI Prompt Engineering 需要有经验吗?

无需任何先前经验。CoddyKit 上的 AI Prompt Engineering 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「日志记录与文档策略」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 AI Prompt Engineering 课中编写并运行代码吗?

能。每节 AI Prompt Engineering 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 诊断意外输出
  2. 提示的根因分析
  3. 系统化调试方法
  4. 日志记录与文档策略
← 返回 AI Prompt Engineering