news 2026/11/5 19:13:00

GPT-4应用工程化:构建可监控、可回滚的LLM服务闭环

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GPT-4应用工程化:构建可监控、可回滚的LLM服务闭环

简介:本资源是面向AI应用开发者与技术实践者的前沿学习材料,聚焦GPT-4与ChatGPT在真实场景中的工程化落地,涵盖智能聊天机器人、自动化内容生成等核心应用开发方法。全书以O’Reilly Early Release形式发布,内容为作者Olivier Caelen与Marie-Alice Blete撰写的原始未删节稿,含第1章完整技术导论及开发框架概览,帮助读者抢先掌握大模型API集成、提示工程设计与应用架构搭建等关键能力。资源为单文件PDF格式,共1个文件,大小2.86MB,轻量易读,适合作为入门指引或开发参考手册随用随查。目前已有1133人学习下载,内容结构清晰,包含技术原理阐释、典型用例示意及后续GitHub代码库预告,可直接用于项目预研、技术选型与快速原型开发。

1. 用 GPT-4 和 ChatGPT 构建真实可用的应用:不是调 API,而是做工程闭环

你手头有一份叫《Developing Apps with GPT-4 and ChatGPT.pdf》的资料,点开发现全是“调用 OpenAI API”“设置 temperature=0.7”“用 stream=True 实现流式响应”——但真正动手时,模型输出乱码、上下文突然丢失、用户连续追问后答案自相矛盾、生产环境里 token 耗尽报错却查不到哪条对话拖垮了配额……这些不是玄学,是没把 GPT 当成一个有状态、有边界、有副作用的工程组件来对待。这份 PDF 的价值不在教你怎么发请求,而在于帮你建立一套可落地、可监控、可回滚的 GPT 应用开发范式:从 prompt 分层设计、上下文生命周期管理、到输出结构化校验与 fallback 降级策略。它适合两类人:一是已写过 3+ 次openai.ChatCompletion.create()却卡在“能跑通但不敢上线”的中级开发者;二是正评估是否将 LLM 接入现有业务系统(如客服工单摘要、内部知识库问答、自动化报告生成)的技术负责人。本文不讲大模型原理,只拆解一个真实项目中反复验证过的最小可行路径:如何让 GPT-4 的能力稳定嵌入你的应用骨架,而不是变成一个黑匣子式的“智能装饰”。


2. 从 PDF 标题反推技术栈:为什么必须绕开“裸调 API”陷阱

这份 PDF 的标题看似平实,但关键词“Developing Apps”已暗含关键约束:它默认读者已有 Web/移动端开发经验,目标不是“体验大模型”,而是“交付可维护的应用”。这意味着不能停留在 Jupyter Notebook 里写几行response = client.chat.completions.create(...)就收工。真实场景中,你会立刻撞上三类硬性瓶颈:

  • 状态不可控:ChatGPT 的对话历史(messages)需由应用自行维护,API 不保存会话。若用户刷新页面或切换设备,上下文全丢;
  • 输出不可信:GPT-4 可能编造 JSON 字段、返回未要求的 Markdown 格式、甚至插入无关的免责声明,前端直接渲染会翻车;
  • 成本不可管:单次请求 token 数波动极大(尤其带长文档摘要时),裸调 API 无法预估单次调用成本,更无法按用户/会话做配额控制。

因此,PDF 中隐含的工程实践必然包含三层抽象:

  1. 接入层:封装原始 API 调用,注入重试、超时、token 统计、错误分类(如rate_limit_exceededvscontext_length_exceeded);
  2. 编排层:管理messages生命周期(截断策略、摘要压缩、敏感信息过滤)、定义 prompt 模板版本(v1_prompt_summary.json / v2_prompt_summary.json);
  3. 契约层:强制输出符合 Schema 的 JSON(如{ "summary": "...", "key_points": ["...", "..."] }),失败时自动触发规则引擎 fallback(如调用本地小模型或返回缓存结果)。

提示:不要试图用一个systemmessage 解决所有问题。我们团队在模拟项目X中测试过,当 prompt 超过 800 字且混用指令/示例/约束时,GPT-4 的结构化输出成功率从 92% 降至 63%。正确做法是分治——用system定角色,user传数据,assistant预填充格式占位符。

2.1 构建可审计的 API 封装层:不只是加个 retry

裸调 API 最常见的“省事”写法是直接 import openai 并调用,但这导致日志无上下文、错误难归因、配额统计缺失。我们采用中间件式封装,核心逻辑如下:

# llm_client.py import openai import time import json from typing import List, Dict, Optional, Any from dataclasses import dataclass @dataclass class LLMResponse: content: str input_tokens: int output_tokens: int model: str timestamp: float error: Optional[str] = None class SafeLLMClient: def __init__(self, api_key: str, base_url: str = None): self.client = openai.OpenAI(api_key=api_key, base_url=base_url) self._request_counter = 0 def chat_completion( self, messages: List[Dict[str, str]], model: str = "gpt-4-turbo", temperature: float = 0.3, max_tokens: int = 2048, timeout: float = 30.0, max_retries: int = 2, ) -> LLMResponse: # 步骤1:预计算输入 token 数(避免调用后才发现超限) input_tokens = self._count_tokens(messages, model) if input_tokens > 12000: # gpt-4-turbo 上下文上限 128K,但预留安全余量 return LLMResponse( content="INPUT_TOO_LONG", input_tokens=input_tokens, output_tokens=0, model=model, timestamp=time.time(), error="input_exceeds_context_window" ) # 步骤2:带退避的重试(指数退避 + jitter) for attempt in range(max_retries + 1): try: start_time = time.time() response = self.client.chat.completions.create( model=model, messages=messages, temperature=temperature, max_tokens=max_tokens, timeout=timeout, ) end_time = time.time() # 步骤3:提取并校验 token 使用量(OpenAI 响应中 guaranteed 存在 usage 字段) usage = response.usage return LLMResponse( content=response.choices[0].message.content, input_tokens=usage.prompt_tokens, output_tokens=usage.completion_tokens, model=model, timestamp=end_time, error=None ) except openai.RateLimitError as e: if attempt < max_retries: sleep_time = (2 ** attempt) + (0.1 * attempt) # jitter time.sleep(sleep_time) continue else: return LLMResponse( content="RATE_LIMIT_EXCEEDED", input_tokens=input_tokens, output_tokens=0, model=model, timestamp=time.time(), error=str(e) ) except Exception as e: return LLMResponse( content="UNKNOWN_ERROR", input_tokens=input_tokens, output_tokens=0, model=model, timestamp=time.time(), error=f"{type(e).__name__}: {str(e)}" ) return LLMResponse( content="RETRY_EXHAUSTED", input_tokens=input_tokens, output_tokens=0, model=model, timestamp=time.time(), error="max_retries_reached" ) def _count_tokens(self, messages: List[Dict[str, str]], model: str) -> int: # 真实项目中此处应调用 tiktoken 库精确计算 # 为简化演示,此处用估算:每字符≈0.75 token(英文为主场景) total_chars = sum(len(m["content"]) for m in messages) return int(total_chars * 0.75)

参数说明与踩坑点:

  • max_retries=2是血泪经验:设为 0 则偶发网络抖动直接失败;设为 5 会导致用户等待超时。GPT-4 API 的 99% P99 延迟在 3.2s 内,2 次重试足够覆盖瞬时抖动。
  • timeout=30.0必须显式设置:OpenAI Python SDK 默认无 timeout,DNS 故障时可能卡死 60s+。
  • _count_tokens方法虽为估算,但足够用于前置拦截。真实项目中必须替换为tiktoken.encoding_for_model(model).encode_ordinary(text),否则长文本场景 token 预估偏差可达 ±20%。

2.2 设计可版本化的 Prompt 编排层:让提示词像代码一样可测试

PDF 中提到的“prompt engineering”绝非手写字符串拼接。我们将其拆解为三个可独立迭代的模块:

模块职责存储位置更新频率
Role Template定义模型角色与基础约束(如“你是一个严谨的法律文书摘要助手,不编造条款”)prompts/role/legal_summarizer_v1.txt低(月级)
Data Adapter将原始输入(如 HTML 文档、PDF 文本块)转换为模型可理解的结构化片段adapters/pdf_to_chunks.py中(周级)
Output Schema强制 JSON 输出格式,含字段类型、长度限制、必填项schemas/legal_summary_v1.json高(日级)

这种分层让 prompt 修改可测试:修改role后,用固定data adapter输入和output schema断言,即可验证是否影响结构化输出率。

# prompt_manager.py import json from pathlib import Path class PromptManager: def __init__(self, base_path: str = "prompts"): self.base_path = Path(base_path) def build_messages( self, role_version: str, user_input: str, schema_version: str, examples: List[Dict] = None ) -> List[Dict[str, str]]: # 1. 加载 Role Template role_path = self.base_path / f"role/{role_version}.txt" with open(role_path, "r", encoding="utf-8") as f: system_content = f.read().strip() # 2. 加载 Output Schema(作为 assistant 的预填充示例) schema_path = self.base_path / f"schemas/{schema_version}.json" with open(schema_path, "r", encoding="utf-8") as f: schema_json = json.load(f) # 3. 构建 messages:system + (examples) + user + assistant(预填充 schema) messages = [ {"role": "system", "content": system_content}, ] if examples: for ex in examples: messages.append({"role": "user", "content": ex["input"]}) messages.append({"role": "assistant", "content": json.dumps(ex["output"], ensure_ascii=False)}) messages.append({"role": "user", "content": user_input}) # 关键:预填充 assistant 消息,引导模型严格遵循 schema messages.append({ "role": "assistant", "content": f"请严格按以下 JSON Schema 输出,不要添加任何额外说明:{json.dumps(schema_json, ensure_ascii=False)}" }) return messages # 使用示例 pm = PromptManager() messages = pm.build_messages( role_version="legal_summarizer_v1", user_input="【合同正文】...(2000字)...", schema_version="legal_summary_v1", examples=[ { "input": "【合同正文】甲方委托乙方开发XX系统,总金额120万元...", "output": {"summary": "甲方委托乙方开发XX系统,总金额120万元,验收标准为...", "key_points": ["金额120万元", "验收标准明确"]} } ] )

为什么预填充assistant消息比只在system里写“请输出 JSON”更有效?
因为 GPT-4 的 few-shot 学习机制对assistant角色的示范更敏感。我们在某跨平台系统中对比测试:纯system指令下 JSON 合法率 78%,加入预填充assistant消息后升至 94.3%。这不是玄学,是模型对对话轮次中“最近一次 assistant 回复”的强注意力机制所致。


3. 结构化输出的强制校验与降级:别让 GPT 的“自信胡说”毁掉用户体验

GPT-4 的幻觉(hallucination)不是 bug,是 feature——它被训练成“给出最流畅的回答”,而非“给出最准确的回答”。PDF 中强调的“Developing Apps”意味着你必须为这种不确定性设计防御层。我们采用三级校验策略:

  1. 语法层校验:确保输出是合法 JSON,且符合预定义 Schema;
  2. 语义层校验:检查关键字段值是否在合理范围内(如summary字数是否 >50 字,key_points是否为空);
  3. 业务层校验:调用轻量规则引擎(如正则匹配合同编号格式、日期是否为 YYYY-MM-DD)。

当任一层失败,触发对应降级策略,而非直接报错。

3.1 用 Pydantic V2 实现零配置 Schema 校验

相比手写json.loads()+if isinstance(...),Pydantic V2 的RootModel可自动完成类型转换、字段校验、错误定位,且性能损失可忽略(百万级 QPS 场景下延迟增加 <0.3ms)。

# schemas/legal_summary.py from pydantic import BaseModel, Field, field_validator from typing import List, Optional class LegalSummary(BaseModel): summary: str = Field( ..., min_length=50, max_length=1000, description="合同核心内容摘要,50-1000字" ) key_points: List[str] = Field( ..., min_length=2, max_length=10, description="关键条款列表,2-10项" ) contract_id: Optional[str] = Field( None, pattern=r"^CONTRACT-\d{6}-\d{4}$", # 强制合同编号格式 description="合同唯一编号,格式:CONTRACT-XXXXXX-YYYY" ) @field_validator('key_points') @classmethod def key_points_no_empty(cls, v): if any(not point.strip() for point in v): raise ValueError("key_points items cannot be empty or whitespace-only") return v # validator.py import json from pydantic import ValidationError from schemas.legal_summary import LegalSummary def validate_and_parse_json(raw_output: str) -> tuple[Optional[LegalSummary], Optional[str]]: """ 返回 (parsed_model, error_message) error_message 为 None 表示校验通过 """ try: # Step 1: JSON 语法校验 parsed_dict = json.loads(raw_output.strip()) except json.JSONDecodeError as e: return None, f"JSON_PARSE_ERROR: {str(e)}" try: # Step 2: Schema 语义校验(Pydantic 自动处理) model = LegalSummary.model_validate(parsed_dict) return model, None except ValidationError as e: # Pydantic 错误信息极友好,直接返回给监控系统 errors = [] for err in e.errors(): loc = ".".join(str(x) for x in err["loc"]) errors.append(f"{loc}: {err['msg']} ({err['type']})") return None, f"SCHEMA_VALIDATION_ERROR: {'; '.join(errors)}"

关键参数说明:

  • min_length=50不是拍脑袋:我们分析了 10 万份真实法律摘要,95% 的有效摘要长度 >62 字。设为 50 是留出 3 字容错(如换行符、空格);
  • pattern正则直接复用业务系统已有合同编号规则,避免校验与业务脱节;
  • field_validator确保key_points无空字符串——这是 GPT-4 在长文本摘要中最常犯的错误(因注意力衰减,末尾生成空项)。

3.2 降级策略矩阵:按错误类型选择最优 fallback

不是所有失败都该重试。我们根据错误类型定义降级动作,避免“越救越错”:

错误类型触发条件降级动作依据
JSON_PARSE_ERRORjson.loads()失败用正则提取{...}片段再解析;若仍失败,返回{"summary": "[GPT输出异常,已启用备用摘要]", "key_points": ["内容不可用"]}83% 的 parse error 是因模型在 JSON 外多加了说明文字(如“以下是摘要:”),正则可剥离
SCHEMA_VALIDATION_ERROR(字段长度)summary<50 字调用本地微调模型(如 Phi-3-mini)重摘要,仅输入前 500 字小模型对短文本更稳定,且无 token 成本
SCHEMA_VALIDATION_ERROR(正则不匹配)contract_id格式错误从原始输入中用正则提取CONTRACT-\d{6}-\d{4},填入结果业务数据源中合同编号 100% 存在且格式一致
RATE_LIMIT_EXCEEDEDAPI 限流返回缓存的最近 3 次同类请求结果(LRU Cache),并标记cached:true用户感知无延迟,且缓存命中率在 42%(基于 7 天日志)
# fallback_engine.py import re from functools import lru_cache from typing import Dict, Any from llm_client import SafeLLMClient from schemas.legal_summary import LegalSummary class FallbackEngine: def __init__(self, llm_client: SafeLLMClient): self.llm_client = llm_client # LRU 缓存:key=hash(user_input), value=LegalSummary dict self.cache = lru_cache(maxsize=1000)(self._get_cached_summary) def handle_validation_error( self, raw_output: str, error_type: str, user_input: str ) -> Dict[str, Any]: if error_type == "JSON_PARSE_ERROR": # 尝试提取 JSON 块 json_match = re.search(r"\{.*?\}", raw_output, re.DOTALL) if json_match: try: parsed = json.loads(json_match.group(0)) # 再次尝试 Pydantic 校验 return LegalSummary.model_validate(parsed).model_dump() except: pass return { "summary": "[GPT输出异常,已启用备用摘要]", "key_points": ["内容不可用"], "fallback_used": "json_parse_fallback" } elif "SCHEMA_VALIDATION_ERROR" in error_type: if "summary" in error_type and "min_length" in error_type: # 调用本地小模型(此处简化为 mock) return self._local_mini_summary(user_input[:500]) elif "contract_id" in error_type: # 从原始输入提取合同号 cid_match = re.search(r"CONTRACT-\d{6}-\d{4}", user_input) contract_id = cid_match.group(0) if cid_match else None return { "summary": "[GPT摘要长度不足,已启用规则摘要]", "key_points": ["摘要长度校验未通过"], "contract_id": contract_id, "fallback_used": "rule_based_contract_id" } # 默认兜底 return { "summary": "[系统繁忙,请稍后重试]", "key_points": ["服务暂时不可用"], "fallback_used": "default_error" } def _local_mini_summary(self, text: str) -> Dict[str, Any]: # 真实项目中此处调用 ONNX Runtime 加载的 Phi-3-mini 模型 # 为演示,返回确定性结果 return { "summary": f"[Phi-3-mini摘要] {text[:100]}...", "key_points": [f"要点1: {text[:20]}...", f"要点2: {text[20:40]}..."], "fallback_used": "phi3_mini" }

注意:lru_cache的maxsize=1000是经过压测的平衡点——内存占用 <12MB,缓存命中率 42%,且不会因缓存过大导致 GC 延迟飙升。盲目设为None(无限缓存)在高并发下会引发 OOM。


4. 上下文管理的生死线:为什么 90% 的 GPT 应用在第三天就崩于 context_length_exceeded

PDF 中反复出现的messages参数,是 GPT 应用最脆弱的环节。新手常犯的致命错误是:把整个对话历史无脑塞进messages,直到某次请求突然报context_length_exceeded,然后开始疯狂删systemmessage 或砍用户输入。这暴露了对上下文本质的误解——上下文不是“历史记录”,而是“当前任务所需的最小信息集”。

GPT-4 的上下文窗口(128K token)是共享资源,必须精打细算。我们团队在某图像处理Demo中总结出上下文管理的黄金法则:三明治压缩法——将长对话历史压缩为三层信息:

  1. 顶层摘要(Top-level Summary):1-2 句话概括整个会话目标(如“用户正在审核一份采购合同,重点关注付款条款和违约责任”);
  2. 中层锚点(Anchor Points):3-5 个关键决策点(如“用户确认第3.2条付款方式为电汇”“用户质疑第5.1条违约金比例过高”);
  3. 底层快照(Snapshot):最近 2 轮完整消息(保证模型理解当前轮次的语气和指代)。

这样,10 轮对话的历史可从 8000+ token 压缩至 <300 token。

4.1 动态截断策略:按 token 预算反向分配空间

不能等len(messages)超了才截断。我们采用“预算驱动”策略:为每次请求设定max_context_tokens = 10000(预留 28K 给输出),然后按优先级反向分配:

消息类型保留策略Token 预算说明
最近 1 条user+assistant全部保留动态(通常 500-2000)保证当前轮次完整语义
systemmessage全部保留≤300角色定义不可删
历史user消息按重要性降序,逐条压缩≤4000用 GPT-4 自身压缩(见下文)
历史assistant消息仅保留key_points字段≤200避免冗余描述
# context_manager.py import json from llm_client import SafeLLMClient class ContextManager: def __init__(self, llm_client: SafeLLMClient): self.llm_client = llm_client def compress_history( self, history: List[Dict[str, str]], target_tokens: int = 4000 ) -> List[Dict[str, str]]: """ 将历史消息压缩至 target_tokens 以内 history: [{"role":"user","content":"..."}, ...] """ if not history: return [] # Step 1: 提取所有 user 消息(跳过 system 和 assistant) user_messages = [msg for msg in history if msg["role"] == "user"] # Step 2: 若已满足,直接返回 current_tokens = self._estimate_tokens(history) if current_tokens <= target_tokens: return history # Step 3: 用 GPT-4 压缩长 user 消息(仅对 >500 字的消息) compressed_history = [] for msg in history: if msg["role"] == "user" and len(msg["content"]) > 500: compressed_content = self._compress_single_message(msg["content"]) compressed_history.append({ "role": "user", "content": f"[压缩摘要] {compressed_content}" }) else: compressed_history.append(msg) # Step 4: 若仍超限,移除最旧的非关键消息(保留最近3轮) if self._estimate_tokens(compressed_history) > target_tokens: # 保留最近3轮(6条消息:user+assistant 交替) keep_count = min(6, len(compressed_history)) compressed_history = compressed_history[-keep_count:] return compressed_history def _compress_single_message(self, content: str) -> str: """ 用 GPT-4 自身压缩单条长消息 提示词经过 27 次 A/B 测试优化,压缩后信息保留率 >92% """ compression_prompt = f"""你是一个专业的文本压缩助手。请将以下内容压缩为不超过150字的精准摘要,保留所有关键实体(人名、地名、数字、专有名词)和逻辑关系,删除举例、修饰语和重复表述。不要添加任何原文没有的信息。 原文:{content}""" response = self.llm_client.chat_completion( messages=[{"role": "user", "content": compression_prompt}], model="gpt-4-turbo", temperature=0.1, max_tokens=200, ) if response.error: # 压缩失败则用简单截断 return content[:150] + "..." return response.content.strip() def _estimate_tokens(self, messages: List[Dict[str, str]]) -> int: # 简化估算:每字符 ≈ 0.75 token total_chars = sum(len(msg["content"]) for msg in messages) return int(total_chars * 0.75)

为什么用 GPT-4 压缩 GPT-4 的输入?
因为人工规则(如“删去所有形容词”)会破坏法律文本的严谨性。GPT-4 对“关键实体”的识别远超正则,且其压缩结果天然适配自身理解模式。我们在 500 份合同摘要测试中,GPT-4 压缩版的下游任务(条款抽取准确率)比人工规则压缩版高 11.3%。

4.2 避坑:上下文管理的 4 个血泪教训

现象 → 原因 → 解决

  1. 现象:用户连续追问 5 轮后,模型开始重复回答同一句话。
    原因:前端未同步messages状态,每次请求都从初始messages开始,导致模型“失忆”。
    解决:在客户端(Web/APP)用sessionStorage或SecureStore持久化messages数组,并在每次请求前合并最新轮次。服务端只负责校验和压缩,不存储会话。

  2. 现象:压缩后的摘要中,合同金额数字被改成“约XX万元”。
    原因:压缩 prompt 未强调“保留所有数字和单位”,GPT-4 默认对数字做模糊化处理。
    解决:在 compression_prompt 中强制加入:“必须原样保留所有数字、百分比、货币符号、单位(如‘万元’‘USD’‘%’),不得替换为‘约’‘左右’等模糊表述。”

  3. 现象:context_length_exceeded错误日志中,input_tokens显示 13200,但模型明明是gpt-4-turbo(128K)。
    原因:tiktoken计算时未指定encoding_for_model("gpt-4-turbo"),误用了cl100k_base编码,导致 token 数高估 12%。
    解决:所有 token 计算必须使用tiktoken.get_encoding("o200k_base")(gpt-4-turbo 对应编码),并在初始化时校验encoding.encode("test")长度是否为 2。

  4. 现象:用户上传 10MB PDF,服务端内存暴涨后 OOM。
    原因:PDF 解析未做页数/大小限制,pymupdf加载全量文本到内存。
    解决:在 PDF 解析层强制添加max_pages=50和max_text_length=200000参数,超限时返回{"error": "document_too_large", "suggestion": "请上传前50页或提取关键章节"}。


5. 生产环境监控与可观测性:把 GPT 调用变成可调试的普通服务

PDF 中的“Developing Apps”最终要落地到生产环境。而 GPT 应用最大的运维挑战是:错误不可重现、延迟不可预测、成本不可追溯。我们放弃“看日志排查”的传统思路,构建三层可观测性:

  • 指标层(Metrics):采集input_tokens、output_tokens、latency_ms、error_rate、fallback_rate,按model、prompt_version、user_tier(免费/付费)多维聚合;
  • 追踪层(Tracing):为每次请求生成唯一request_id,串联 LLM 调用、Schema 校验、Fallback 执行全过程;
  • 日志层(Logging):结构化日志,强制包含prompt_hash(SHA256 of messages)、response_hash(SHA256 of output)、fallback_used字段。

5.1 用 Prometheus + Grafana 实现成本与质量双监控

我们导出的核心指标(全部通过/metrics端点暴露):

指标名类型说明报警阈值
llm_request_totalCounter总请求数-
llm_token_usage_totalCounter累计 token 数(分input/output)input_token_usage_total24h 增长 > 500M 触发告警
llm_latency_secondsHistogram请求延迟分布P95 > 8s 触发告警
llm_fallback_totalCounter各类 fallback 触发次数json_parse_fallback1h > 100 次触发告警
llm_schema_validation_failure_totalCounterSchema 校验失败次数summary_min_length1h > 50 次触发告警
# metrics_exporter.py from prometheus_client import Counter, Histogram, Gauge, CollectorRegistry, generate_latest import time # 创建 registry(避免与主应用冲突) registry = CollectorRegistry() # 指标定义 REQUEST_TOTAL = Counter( 'llm_request_total', 'Total number of LLM requests', ['model', 'prompt_version', 'user_tier'], registry=registry ) TOKEN_USAGE_TOTAL = Counter( 'llm_token_usage_total', 'Total token usage', ['model', 'direction', 'prompt_version'], # direction: input/output registry=registry ) LATENCY_SECONDS = Histogram( 'llm_latency_seconds', 'LLM request latency', ['model', 'prompt_version'], buckets=(0.1, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0), registry=registry ) FALLBACK_TOTAL = Counter( 'llm_fallback_total', 'Total fallback invocations', ['fallback_type', 'model'], registry=registry ) # 在 LLMClient.chat_completion 方法末尾添加 def record_metrics( model: str, prompt_version: str, input_tokens: int, output_tokens: int, latency_ms: float, fallback_used: str = None, user_tier: str = "free" ): REQUEST_TOTAL.labels( model=model, prompt_version=prompt_version, user_tier=user_tier ).inc() TOKEN_USAGE_TOTAL.labels( model=model, direction="input", prompt_version=prompt_version ).inc(input_tokens) TOKEN_USAGE_TOTAL.labels( model=model, direction="output", prompt_version=prompt_version ).inc(output_tokens) LATENCY_SECONDS.labels( model=model, prompt_version=prompt_version ).observe(latency_ms / 1000.0) # 转为秒 if fallback_used: FALLBACK_TOTAL.labels( fallback_type=fallback_used, model=model ).inc()

为什么buckets设置为(0.1, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0)?
因为 GPT-4-turbo 的 P99 延迟在 3.2s,P999 在 7.8s。这个 bucket 分布能精准捕捉 95% 的延迟异常(如某次部署后 P95 从 1.2s 升至 5.3s),而均匀 bucket(如0.5, 1.0, 1.5...)会淹没关键拐点。

5.2 请求级全链路追踪:从 prompt 到 fallback 的因果图

当用户投诉“摘要不准”,传统日志只能看到{"error": null, "content": "..."}。我们通过request_id串联所有环节:

# tracing.py import uuid import time from contextvars import ContextVar # 全局 contextvar 存储当前请求上下文 current_trace_ctx = ContextVar('trace_ctx', default=None) class TraceContext: def __init__(self, request_id: str = None): self.request_id = request_id or str(uuid.uuid4()) self.start_time = time.time() self.spans = [] def add_span(self, name: str, **kwargs): span = { "name": name, "start_time": time.time(), "end_time": time.time(), "duration_ms": 0, "kwargs": kwargs } # 记录结束时间 span <p> <a href="https://download.csdn.net/download/Oliver9987/87971512" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 15:58:14

WMI英文开发文档实战指南:从查询到远程采集

简介&#xff1a;这份资源是面向 Windows 平台开发者与 IT 管理员的 WMI 英文官方开发文档&#xff0c;适合需要编写 C、C、Visual Basic 或脚本程序来自动化管理任务的工程师&#xff0c;也适合希望系统理解 WMI 体系结构、类模型与查询语言的技术人员。文档围绕 WMI 基本概念…

作者头像 李华
网站建设 2026/10/24 20:51:06

Sunshine 自托管游戏串流主机:10 分钟搭好低延迟串流服务器

Sunshine 自托管游戏串流主机&#xff1a;10 分钟搭好低延迟串流服务器 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine Sunshine 是一款自托管的游戏串流宿主程序&#xff0c;配合…

作者头像 李华
网站建设 2026/10/23 15:38:17

TTSR解析:用Transformer为图像超分迁移参考纹理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/23 14:59:18

Qt环境下bsdiff增量更新:从移植到qlz压缩的完整实践

简介&#xff1a;面向需要在 Qt 应用中实现差分升级的开发者&#xff0c;这份资源将开源工具 bsdiff 移植到 Qt 环境&#xff0c;并使用快速压缩算法 qlz 生成 patch 包。其基本思路是比较新旧版本文件&#xff0c;只提取差异部分并压缩输出&#xff0c;特别适合嵌入式、网络带…

作者头像 李华
网站建设 2026/10/23 14:14:02

Codex 生成前端组件实战:从提示词到工程化对齐的秒级开发

1. 从"等组件"到"秒级出活"&#xff1a;前端开发节奏正在被什么改变前端开发这行有个很微妙的痛点&#xff1a;真正写业务逻辑的时间&#xff0c;往往不到一半。剩下的一半&#xff0c;全耗在"搭组件"上——一个带搜索、分页、空状态、加载骨架的…

作者头像 李华