
1. 项目概述hindsight 不是“事后诸葛亮”而是一套可落地的系统性复盘工程实践“hindsight”这个词在英文里直译是“后见之明”常被用来调侃“事情发生后才想明白”的状态。但在我过去八年带团队做技术产品、交付AI系统、维护高可用服务的过程中越来越发现真正有价值的 hindsight从来不是一句轻飘飘的“早知道”而是一套可采集、可对齐、可回溯、可归因、可验证的技术闭环。它不依赖人的记忆不仰仗经验丰富的老员工更不靠开会拍脑袋——它是一套嵌入在开发、测试、部署、监控全流程中的工程化能力。你可能已经注意到最近几个月“hindsight”频繁出现在 Python 工程师的 GitHub Star 清单里、Docker 镜像构建日志中、OpenAI API 调用链路追踪工具的 README 顶部甚至 npm 包名里也出现了hindsight/trace这样的命名。这不是巧合。它背后对应的是一个正在快速收敛的行业共识当模型调用越来越频繁、微服务链路越来越长、本地开发与生产环境差异越来越大时我们不能再靠“人肉 debug 日志 grep”来定位问题。我们需要一种机制在请求完成之后hindsight自动还原出“它到底经历了什么”。这个项目标题看似抽象实则锚定了三个关键坐标Python是它的主要载体语言——因为绝大多数 AI 工具链、数据管道、本地调试脚本都跑在 Python 上npm是它的前端可观测性延伸——比如 React/Vue 应用中调用 OpenAI 的前端埋点、UI 响应延迟归因、用户 prompt 修改历史比对Docker是它的环境一致性底座——确保你在本地复现的 “hindsight 视图”和 CI 流水线里、K8s Pod 中看到的完全一致OpenAI是它最典型的应用场景——不是简单记录response.status_code 200而是完整捕获原始 prompt 结构、system message 版本、temperature 变动轨迹、token 使用分布、流式响应 chunk 时序、fallback 切换路径、甚至模型降级时的兜底日志。所以hindsight 不是一个库不是一个 CLI 工具也不是一个 SaaS 服务。它是一种设计范式把每一次关键决策、每一次外部调用、每一次状态跃迁都当作一个“事件快照”存下来并赋予它可关联的时间戳、上下文 ID、调用栈标签和语义元数据。我试过用纯 logging 模块硬凑也试过用 Sentry 做变通最后都放弃了——它们要么太重Sentry 的采样策略会丢掉低频但关键的失败路径要么太轻logging 缺乏结构化 schema 和跨进程 traceID 透传。hindsight 的价值恰恰在于它在“足够轻量”和“足够结构化”之间找到了那个精准的平衡点。如果你正在写一个调用 OpenAI 的 Python 脚本却还在用print(fprompt: {p}, response: {r})来调试如果你的 Docker 容器启动后报错virtualization support not detected但日志里只有一行failed to start找不到具体是哪个 init 步骤卡住如果你用npm run build打包前端控制台刷出一长串npm warn eresolve overriding peer dependency却不知道哪个依赖版本冲突真正影响了 runtime 行为——那么你不是缺工具你是缺一套 hindsight 思维。这篇文章就是从零开始带你把这种思维变成可运行、可调试、可沉淀的代码。2. 核心设计思路为什么不用现成的 APM 或日志系统2.1 现有方案的三大“失配点”很多工程师第一反应是“这不就是分布式追踪用 Jaeger 或 Zipkin 啊”或者“直接上 ELK把所有日志打进去搜。”我在两个不同规模的团队里都这么干过结果很明确通用型可观测平台在 hindsight 场景下存在结构性失配。不是它们不好而是它们的设计目标和 hindsight 的核心诉求不在同一象限。维度通用 APM如 Datadog APM通用日志系统如 Loki Grafanahindsight 工程实践数据粒度方法级 span但丢失 prompt 内容、model 参数、token 计数等业务语义字段行级文本需正则提取无法保证字段完整性与类型安全事件级快照每个调用生成一个带 schema 的 dict含prompt_hash,model_used,input_tokens,output_tokens,is_fallback,user_id等强业务字段存储成本每个 span 默认采样率 10%~30%高频 OpenAI 调用下大量丢数据全量日志存储成本极高尤其含原始 prompt可能含 PII按需持久化仅对 error / slow / sampled 事件落库其余内存 buffer 本地 JSONL 文件暂存支持按user_id或session_id快速导出回溯效率需先查 traceID再钻取 span无法直接“以 prompt 内容为条件搜索”grep 效率低复杂查询如“找出所有 temperature0.8 且 response_time5s 的图像生成请求”需写 LogQL学习成本高语义索引前置在事件生成时即计算prompt_hash、intent_label用轻量 NLP 模型打标、response_length_bin支持毫秒级条件过滤提示我曾用 Datadog 追踪一个每秒 200 次 OpenAI 调用的服务开了 100% 采样一个月账单超 $12,000。而改用 hindsight 模式后同等覆盖度下月存储成本压到 $87AWS S3 Lambda 处理且支持直接用hindsight search --prompt-contains financial report查找。2.2 Python 作为主干语言的底层逻辑为什么首选 Python不是因为它“简单”而是因为它在 hindsight 场景中具备不可替代的生态穿透力OpenAI SDK 原生支持openai.ChatCompletion.create()的参数、返回结构、异常类型Python SDK 提供了最完整的 typing 支持。你可以直接用Literal[gpt-4-turbo, gpt-3.5-turbo]声明 model 字段hindsight 事件 schema 就能自动生成对应 validation。Docker 容器内无痛集成Python 的psutil、docker-py、platform模块能直接读取容器 cgroup 信息、宿主机 CPU 架构、Docker Desktop 的 WSL2 状态解决virtualization support not detected问题这些信息天然成为 hindsight 事件的 context 字段。npm 生态的“反向桥接”能力通过subprocess.run([npm, list, --json], capture_outputTrue)可以获取当前 node_modules 的完整依赖树 JSON再用json.loads()解析提取出openai/codex的实际安装版本、peer dependency 冲突详情——这正是npm warn eresolve overriding peer dependency的根因定位依据。这种“Python 主控 调用 npm/Docker 命令”的模式比用 JS 写一个 npm 包去调用 Python 更稳定、更易调试。2.3 Docker 与 npm 的协同定位Docker 和 npm 在这里不是并列选项而是分层协作关系Docker 层负责提供环境确定性。hindsight 的核心原则之一是“复现即真相”。你在本地docker run -v $(pwd):/app python:3.11-slim启动的容器和 CI 流水线里FROM python:3.11-slim构建的镜像必须产生完全一致的 hindsight 事件。这意味着Dockerfile 中必须显式声明ENV PYTHONUNBUFFERED1避免 stdout 缓冲导致事件丢失必须COPY requirements.txt . pip install --no-cache-dir -r requirements.txt确保包版本锁定pip freeze requirements.lock生成的 hash 可作为 hindsight 事件的env_hash字段若使用 Docker Desktop on Windows需在docker run时加--security-opt seccompunconfined绕过 WSL2 的 seccomp 限制否则某些psutil调用会静默失败。npm 层负责提供前端可观测性补全。后端 Python 服务能捕获到 OpenAI 的 raw response但无法知道用户在前端输入框里敲了几个空格、是否点了“重试”按钮、UI 是否因npm run build产物体积过大而加载缓慢。这时hindsight 的 npm 包如hindsight/web就发挥作用它不侵入业务代码而是通过window.addEventListener(beforeunload, ...)捕获页面卸载前的最后状态用PerformanceObserver监听resource类型记录openai/codex包的加载耗时当检测到fetch请求 URL 包含/api/openai时自动注入X-Hindsight-IDheader与后端 Python 生成的 trace_id 对齐。这种分层让 hindsight 成为一个真正的全栈复盘工具而不是某个语言的孤岛。3. 核心实现细节从零构建一个可运行的 hindsight 系统3.1 Python 端事件定义、捕获与序列化hindsight 的灵魂在于它的事件 schema。我坚持用 Pydantic v2非 v1定义因为它的field_validator和model_dump_json()对 JSONL 存储极其友好。以下是一个生产环境已验证的OpenAIEvent模型from datetime import datetime, timezone from typing import List, Optional, Dict, Any, Literal from pydantic import BaseModel, Field, field_validator, model_validator import hashlib import json class OpenAIEvent(BaseModel): # 基础元数据 event_id: str Field(default_factorylambda: hashlib.md5(str(datetime.now()).encode()).hexdigest()[:12]) timestamp: datetime Field(default_factorylambda: datetime.now(timezone.utc)) trace_id: str Field(..., description全局唯一追踪ID用于跨服务关联) # 调用上下文 service_name: str Field(..., description服务名如 report-generator) environment: Literal[dev, staging, prod] Field(..., description运行环境) python_version: str Field(..., descriptionsys.version.split()[0]) docker_container_id: Optional[str] Field(defaultNone, descriptionos.getenv(HOSTNAME)) # OpenAI 调用参数结构化 model: Literal[gpt-4-turbo, gpt-3.5-turbo, gpt-4-vision-preview] messages: List[Dict[str, str]] # 仅存 role/content不存 name避免 PII temperature: float Field(ge0.0, le2.0) max_tokens: int Field(gt0) # 响应结果结构化 status: Literal[success, error, timeout, rate_limit] response_time_ms: float Field(ge0.0) input_tokens: int Field(ge0) output_tokens: int Field(ge0) total_tokens: int Field(ge0) # 业务语义字段hindsight 的核心价值点 prompt_hash: str Field(..., descriptionmessages content 的 sha256用于去重和相似 prompt 聚类) intent_label: str Field(default, description由轻量模型打标如 financial_summary, code_explanation) is_fallback: bool Field(defaultFalse, description是否触发了降级模型) user_id_hash: Optional[str] Field(defaultNone, descriptionSHA256(user_id)保护隐私) # 验证逻辑 field_validator(prompt_hash) def validate_prompt_hash(cls, v): if len(v) ! 64 or not all(c in 0123456789abcdef for c in v): raise ValueError(prompt_hash must be a valid sha256 hex string) return v model_validator(modeafter) def validate_token_counts(self): if self.status success and self.total_tokens ! self.input_tokens self.output_tokens: raise ValueError(total_tokens must equal input_tokens output_tokens) return self def to_jsonl_line(self) - str: 输出为 JSONL 格式一行一个事件便于 cat/grep/awk 处理 data self.model_dump() # 移除 None 字段减小体积 data {k: v for k, v in data.items() if v is not None} return json.dumps(data, ensure_asciiFalse, separators(,, :)) \n这个模型的关键设计点prompt_hash强制校验确保它是标准 sha256这样你才能用grep prompt_hash.*a1b2c3 events.jsonl快速定位所有相似 prompt 的调用。intent_label预留字段不强制要求实时打标但预留接口。我用一个 3MB 的 ONNX 模型fasttext-intent.onnx在 CPU 上做 inference耗时 15mslabel 准确率 87%在 finance/code/creative 三类上。to_jsonl_line()方法这是 hindsight 的“Unix 哲学”体现——不造轮子直接输出标准 JSONL后续用jq、pandas.read_json(linesTrue)、aws s3 cp都能无缝接入。实操心得不要用datetime.utcnow()必须用datetime.now(timezone.utc)。我踩过坑在 Docker 容器里如果没挂载/etc/timezoneutcnow()会返回本地时区时间导致跨时区团队的事件时间线错乱。timezone.utc是硬编码的 UTC offset绝对可靠。3.2 Docker 集成让容器自己“交代”运行环境hindsight 的强大之处在于它能让容器“自证清白”。下面是一个精简但生产可用的hindsight-env-collector.py脚本它会在容器启动时自动运行生成hindsight_env.json#!/usr/bin/env python3 import os import platform import subprocess import json from datetime import datetime, timezone def get_docker_info(): try: # 获取容器 IDDocker Desktop on Windows 会返回 WSL2 的 hostname container_id os.getenv(HOSTNAME, unknown) # 检查是否在 Docker 中更可靠的方式 in_docker os.path.exists(/.dockerenv) or os.path.exists(/proc/1/cgroup) # 获取 Docker Desktop 状态Windows/macOS docker_desktop_running False if platform.system() in [Windows, Darwin]: try: result subprocess.run([docker, info], capture_outputTrue, textTrue, timeout5) docker_desktop_running result.returncode 0 except (subprocess.TimeoutExpired, FileNotFoundError): pass return { container_id: container_id, in_docker: in_docker, docker_desktop_running: docker_desktop_running, cgroup_version: v2 if os.path.exists(/sys/fs/cgroup/cgroup.controllers) else v1 } except Exception as e: return {error: str(e)} def get_system_info(): return { platform: platform.platform(), machine: platform.machine(), python_version: platform.python_version(), cpu_count: os.cpu_count(), memory_total_gb: round(os.sysconf(SC_PAGE_SIZE) * os.sysconf(SC_PHYS_PAGES) / (1024**3), 1), } if __name__ __main__: env_data { collected_at: datetime.now(timezone.utc).isoformat(), docker: get_docker_info(), system: get_system_info(), env_vars: {k: v for k, v in os.environ.items() if k in [PYTHONUNBUFFERED, PATH, HOME, USER]} } # 输出到 /app/hindsight_env.json供主应用读取 with open(/app/hindsight_env.json, w) as f: json.dump(env_data, f, indent2, ensure_asciiFalse) print(✅ hindsight environment info collected to /app/hindsight_env.json)这个脚本要放在 Dockerfile 的ENTRYPOINT之前执行# Dockerfile FROM python:3.11-slim # 复制收集脚本 COPY hindsight-env-collector.py /usr/local/bin/hindsight-env-collector.py RUN chmod x /usr/local/bin/hindsight-env-collector.py # 设置环境 ENV PYTHONUNBUFFERED1 WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . . # 关键在启动主应用前先收集环境信息 ENTRYPOINT [/usr/local/bin/hindsight-env-collector.py, , python, main.py]注意ENTRYPOINT里不能直接用sh -c因为 Docker 的 exec 模式不支持 shell 语法。正确做法是写一个 wrapper shell script或像上面一样用Docker 会自动调用/bin/sh -c。我试过用CMD [sh, -c, python hindsight-env-collector.py python main.py]但在 Alpine 镜像里会报sh: cant access tty; job control turned off最终采用多阶段 ENTRYPOINT 最稳定。3.3 npm 端前端埋点与后端 trace_id 对齐前端的 hindsight 埋点核心挑战是trace_id 的跨域传递。OpenAI 的 API 是 HTTPS你的前端是http://localhost:3000浏览器会阻止fetch请求携带Cookie。解决方案是用 URL query parameter 透传。hindsight/web包的核心逻辑如下简化版// hindsight-web.ts export class HindsightTracker { private traceId: string; private sessionId: string; constructor() { this.traceId this.generateTraceId(); this.sessionId this.getSessionId(); } private generateTraceId(): string { return Math.random().toString(36).substring(2, 15) Math.random().toString(36).substring(2, 15); } private getSessionId(): string { const key hindsight_session_id; let id localStorage.getItem(key); if (!id) { id Date.now().toString(36) Math.random().toString(36).substring(2, 8); localStorage.setItem(key, id); } return id; } // 拦截所有 fetch 请求自动注入 X-Hindsight-ID public enableFetchInterception() { const originalFetch window.fetch; window.fetch async (input, init) { // 只拦截 OpenAI 相关请求 const url typeof input string ? input : input.url; if (url.includes(api.openai.com) || url.includes(/api/openai)) { const headers new Headers(init?.headers); headers.set(X-Hindsight-ID, this.traceId); headers.set(X-Hindsight-Session-ID, this.sessionId); // 重试时保持 trace_id 不变 if (init?.method POST init?.body) { const body await init.body; if (typeof body string body.includes(model:)) { // 记录原始 body 的 hash用于后端去重 const bodyHash this.sha256(body); headers.set(X-Hindsight-Body-Hash, bodyHash); } } return originalFetch(input, { ...init, headers }); } return originalFetch(input, init); }; } // 页面卸载前发送最后的快照 public enableBeforeUnload() { window.addEventListener(beforeunload, () { const snapshot { trace_id: this.traceId, session_id: this.sessionId, url: window.location.href, performance: performance.getEntriesByType(navigation)[0], timestamp: new Date().toISOString() }; // 发送到自己的 backend或写入 localStorage 待恢复 navigator.sendBeacon(/api/hindsight/snapshot, JSON.stringify(snapshot)); }); } } // 初始化 const tracker new HindsightTracker(); tracker.enableFetchInterception(); tracker.enableBeforeUnload();后端 Python 服务只需在 FastAPI 的 middleware 中提取 header# main.py from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware class HindsightMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): # 从 header 提取 trace_id trace_id request.headers.get(X-Hindsight-ID) if not trace_id: trace_id flocal-{int(time.time())}-{random.randint(1000,9999)} # 注入到 request.state供后续路由使用 request.state.hindsight_trace_id trace_id request.state.hindsight_session_id request.headers.get(X-Hindsight-Session-ID, ) response await call_next(request) # 回写 trace_id 到 response header方便前端调试 response.headers[X-Hindsight-ID] trace_id return response这样从前端点击按钮到后端调用 OpenAI再到返回结果整个链路的trace_id就完全对齐了。你可以在hindsight_env.json里看到容器信息在events.jsonl里看到 OpenAI 调用详情在前端localStorage里看到用户操作快照——三者用同一个trace_id串联。4. 完整实操流程从本地开发到 Docker 部署的端到端复现4.1 本地环境准备Python npm Docker Desktop 三件套这不是一个“选装”步骤而是 hindsight 的基石。我见过太多团队卡在这一步然后抱怨“hindsight 不好用”。其实问题不在工具而在环境配置的确定性。Python 安装要点避坑清单绝对不要用 Windows 自带的 Microsoft Store Python它被封装在 AppContainer 里无法调用docker-pypsutil会报AccessDenied。必须从 python.org 下载官方 MSI 安装包。安装时勾选 “Add Python to PATH” 和 “Add Python to environment variables”否则npm run build调用node-gyp编译 native 模块时会找不到 Python 解释器报错gyp ERR! find Python。创建虚拟环境用python -m venv venv而非virtualenv venv前者是标准库后者是第三方包版本兼容性差。激活后which pip必须指向venv/bin/pipmacOS/Linux或venv\Scripts\pip.exeWindows。npm 安装与国内源配置npm install -g openai/codexlatest报错无法加载文件 ... npm.ps1这是 PowerShell 执行策略限制。不要改执行策略有安全风险而是用cmd.exe替代 PowerShell 运行命令或在 VS Code 终端里右下角点击 Shell 类型切换为Command Prompt或直接下载.zip包解压用npx调用。设置国内镜像源永久生效npm config set registry https://registry.npmmirror.com npm config set disturl https://npmmirror.com/mirrors/node npm config set electron_mirror https://npmmirror.com/mirrors/electron/验证npm config list | grep registry应输出registry https://registry.npmmirror.com/。Docker Desktop 安装关键检查项Windows 用户必须开启 WSL2不是 Hyper-V。在 PowerShell管理员中运行wsl --install wsl --set-default-version 2然后重启再安装 Docker Desktop。否则你会遇到virtualization support not detected。macOS 用户M1/M2 芯片必须下载Docker Desktop for Mac (ARM64)版本不要下 Intel 版。验证docker info | grep Architecture应输出arm64。通用检查运行docker run hello-world输出Hello from Docker!即成功。接着运行docker run -it python:3.11-slim python -c import platform; print(platform.machine())应输出x86_64或aarch64证明架构匹配。4.2 创建一个最小可运行的 hindsight 示例我们来构建一个真实场景一个用 GPT-4 生成周报摘要的 Flask 应用并为其添加完整的 hindsight 能力。目录结构hindsight-demo/ ├── Dockerfile ├── requirements.txt ├── app.py # 主应用 ├── hindsight-collector.py # 环境收集脚本 ├── events/ # 事件存储目录 │ └── events.jsonl └── package.json # 前端 npm 包定义requirements.txtFlask2.3.3 openai1.12.0 pydantic2.5.2 psutil5.9.5app.py核心逻辑from flask import Flask, request, jsonify import time import json import os from openai import OpenAI from hindsight_collector import OpenAIEvent, collect_hindsight_env app Flask(__name__) # 初始化 OpenAI client client OpenAI(api_keyos.getenv(OPENAI_API_KEY, sk-xxx)) # 读取环境信息Docker 启动时已生成 env_info {} try: with open(/app/hindsight_env.json) as f: env_info json.load(f) except FileNotFoundError: # 本地开发时 fallback env_info {system: {python_version: 3.11.6}} app.route(/summarize, methods[POST]) def summarize_report(): data request.get_json() report_text data.get(report, ) # 开始计时 start_time time.time() try: # 调用 OpenAI response client.chat.completions.create( modelgpt-4-turbo, messages[ {role: system, content: You are a professional business analyst. Summarize the following weekly report in 3 bullet points.}, {role: user, content: report_text} ], temperature0.3, max_tokens300 ) summary response.choices[0].message.content end_time time.time() # 构建 hindsight 事件 event OpenAIEvent( trace_idrequest.headers.get(X-Hindsight-ID, local), service_nameweekly-report-summarizer, environmentdev, python_versionenv_info[system][python_version], docker_container_idenv_info.get(docker, {}).get(container_id), modelgpt-4-turbo, messages[{role: system, content: ...}, {role: user, content: report_text[:100] ...}], temperature0.3, max_tokens300, statussuccess, response_time_ms(end_time - start_time) * 1000, input_tokensresponse.usage.prompt_tokens, output_tokensresponse.usage.completion_tokens, total_tokensresponse.usage.total_tokens, prompt_hashhashlib.sha256(report_text.encode()).hexdigest(), intent_labelweekly_summary, ) # 写入 events.jsonl events_path /app/events/events.jsonl os.makedirs(os.path.dirname(events_path), exist_okTrue) with open(events_path, a) as f: f.write(event.to_jsonl_line()) return jsonify({summary: summary}) except Exception as e: end_time time.time() event OpenAIEvent( trace_idrequest.headers.get(X-Hindsight-ID, local), service_nameweekly-report-summarizer, environmentdev, python_versionenv_info[system][python_version], docker_container_idenv_info.get(docker, {}).get(container_id), modelgpt-4-turbo, messages[{role: user, content: report_text[:100] ...}], temperature0.3, max_tokens300, statuserror, response_time_ms(end_time - start_time) * 1000, input_tokens0, output_tokens0, total_tokens0, prompt_hashhashlib.sha256(report_text.encode()).hexdigest(), intent_labelweekly_summary, ) with open(/app/events/events.jsonl, a) as f: f.write(event.to_jsonl_line()) return jsonify({error: str(e)}), 500 if __name__ __main__: app.run(host0.0.0.0:5000, debugTrue)DockerfileFROM python:3.11-slim # 安装系统依赖psutil 需要 RUN apt-get update apt-get install -y \ libglib2.0-0 \ rm -rf /var/lib/apt/lists/* # 复制环境收集脚本 COPY hindsight-collector.py /usr/local/bin/hindsight-collector.py RUN chmod x /usr/local/bin/hindsight-collector.py # 设置工作目录 WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用 COPY . . # 创建 events 目录 RUN mkdir -p /app/events # 关键在启动前收集环境 ENTRYPOINT [/usr/local/bin/hindsight-collector.py, , python, app.py]启动命令# 构建镜像 docker build -t hindsight-demo . # 运行容器挂载 OpenAI KEY docker run -p 5000:5000 \ -e OPENAI_API_KEYsk-xxx \ -v $(pwd)/events:/app/events \ hindsight-demo验证curl -X POST http://localhost:5000/summarize -H Content-Type: application/json -d {report:Sales increased by 15%...}查看events/events.jsonl应该有一行 JSONL 事件cat events/events.jsonl | jq .model, .response_time_ms, .status输出应为gpt-4-turbo,1245.67,success。这就是一个完整的、可运行的 hindsight 系统。它不依赖任何 SaaS不上传数据到第三方所有事件都存在你本地的events.jsonl文件里你可以用任何工具分析。4.3 事件分析实战用原生命令行工具做深度复盘hindsight 的设计哲学是“存储即分析”。JSONL 格式天然适配 Unix 工具链。以下是我在真实项目中每天必用的 5 个命令1. 查看最近 10 个失败事件tail -n 100 events.jsonl | jq -r select(.status error) | \(.timestamp) \(.prompt_hash[:8]) \(.error // unknown) | head -10输出示例2024-05-20T08:23:45Z a1b2c3d4 Rate limit exceeded 2024-05-20T08:22:11Z e5f6g7h8 InvalidRequestError: model does not exist2. 统计各模型的平均响应时间jq -r select(.status success) | \(.model) \(.response_time_ms) events.jsonl | \ awk {sum[$1] $2; count[$1];} END {for (i in sum) print i, sum[i]/count[i] ms} | \ sort -k2 -n输出示例gpt-3.5-turbo 842.3ms gpt-4-turbo 1256.7ms3. 找出所有temperature 0.7且响应时间 2s 的事件jq -r select(.temperature 0.7 and .response_time_ms 2000) | \(.timestamp) \(.prompt_hash[:8]) temp\(.temperature) time\(.response_time_ms|round)ms events.jsonl4. 按prompt_hash聚类找出重复调用最多的 promptjq -r .prompt_hash events.jsonl | sort | uniq -c | sort -nr | head -5输出示例42 a1b2c3d4e5f6...