日志记录与文档策略
记录提示词版本、输入和输出,以便进行可复现的调试。
日志记录与文档策略 是 CoddyKit 上的免费 AI Prompt Engineering 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 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 对象。这种格式具有以下特点:
- 无需加锁即可 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 反馈 — 无需本地设置。