提示工程架构师必看:7个多场景提示系统的可观测性设计技巧——从黑盒到透明的实践指南
关键词
提示工程、可观测性、多场景提示系统、Prompt生命周期追踪、上下文快照、模型推理透视、用户反馈闭环
摘要
当你为客服AI设计动态Prompt时,用户投诉“回答的物流时间不对”,但日志里只显示“调用了物流工具”;当你为代码生成AI优化Prompt时,生成的代码突然无法运行,却找不到是Prompt模板错了还是上下文漏了;当你为数据分析AI扩展多模态输出时,生成的图表和数据不符,却不知道问题出在Prompt还是模型——这些痛点的根源,是提示系统的“黑盒性”。
提示系统的核心是“Prompt+模型+上下文”的动态交互,但传统软件的可观测性方法(日志、指标、链路追踪)无法直接适配其“Prompt作为代码、模型作为 runtime、上下文作为状态”的特性。本文将结合多场景实践(客服、代码生成、数据分析),分享7个可落地的可观测性设计技巧,帮你把提示系统从“黑盒”变成“透明玻璃盒”:从Prompt的全生命周期追踪,到上下文的版本控制,再到模型推理的Token级透视,最终实现“观测-分析-优化”的闭环。
无论你是刚入门的提示工程架构师,还是正在优化AI系统的运维人员,这篇文章都会给你一套从0到1的可观测性设计框架——让你不再“猜问题”,而是“用数据定位问题”。
1. 背景:为什么提示系统的可观测性比你想的更重要?
在讨论技巧之前,我们需要先回答一个问题:为什么提示系统需要专门的可观测性设计?
1.1 提示系统的“3大特殊属性”
传统软件的核心是“代码→运行→输出”,而提示系统的核心是“Prompt→模型→上下文→输出”——这三者的动态交互,让提示系统的可观测性变得更复杂:
- Prompt的动态性:很多Prompt不是静态模板,而是结合用户输入、工具返回、历史对话生成的。比如客服AI的Prompt可能是:
“根据用户订单ID {order_id} 查询最新物流状态,参考用户历史问题‘{user_history}’,用口语化回复”
——你看到的“最终Prompt”是多个变量拼接的结果,静态模板无法反映真实执行逻辑。 - 模型的黑盒性:大语言模型(LLM)的输出是概率性的,即使Prompt完全相同,模型也可能生成不同结果。比如“写一个稳定排序函数”,模型可能生成
sorted(arr, key=lambda x: (x, id(x)))
,也可能漏掉id(x)
——你无法从输入直接推断输出。 - 上下文的累积性:多轮对话中,上下文是逐步累加的。比如用户先问“怎么退款”,再问“我的订单啥时候到”,上下文会包含这两个问题——如果AI回答物流时间时参考了退款问题的上下文,你需要知道“上下文是否被正确传递”。
1.2 传统可观测性的“3大失效场景”
传统软件的可观测性依赖“日志+指标+链路追踪”,但在提示系统中会失效:
- 日志失效:传统日志记录“输入→输出”,但提示系统的“输入”是动态Prompt+上下文,“输出”是模型生成的文本/代码/图像——离散的日志无法关联“Prompt如何生成”“上下文如何变化”“模型如何推理”。
- 指标失效:传统指标(如QPS、延迟)只能反映“系统有没有运行”,但无法反映“系统运行得好不好”——比如AI的回答准确率、代码运行成功率、图表相关性,这些是提示系统的核心指标,但传统指标体系无法覆盖。
- 链路追踪失效:传统链路追踪跟踪“服务间调用”,但提示系统的核心链路是“Prompt生成→模型推理→上下文更新”——这是系统内部的逻辑链路,而非服务间调用,传统链路追踪工具(如Zipkin)无法覆盖。
1.3 目标读者与核心挑战
目标读者:提示工程架构师、AI系统开发者、AI运维人员。
核心挑战:
- 如何追踪动态Prompt的全生命周期?
- 如何管理多轮对话的上下文变化?
- 如何透视模型推理的黑盒?
- 如何量化提示系统的“效果”而非“运行状态”?
- 如何在多场景(客服、代码生成、数据分析)下适配观测策略?
2. 核心概念:提示系统的可观测性到底是什么?
在讲技巧之前,我们需要先明确提示系统可观测性的定义——它不是“收集更多数据”,而是“收集能回答以下4个问题的数据”:
- What:Prompt是怎么生成的?(模板、参数、上下文追加)
- Why:模型为什么生成这个输出?(Token概率、注意力权重)
- How:上下文是怎么变化的?(每轮对话的快照)
- So What:这个输出的效果怎么样?(用户满意度、代码运行率、图表相关性)
2.1 用“餐厅类比”理解核心概念
我们可以把提示系统比作一家智能餐厅,用餐厅的运营逻辑理解可观测性:
提示系统组件 | 餐厅类比 | 可观测性需求 |
---|---|---|
Prompt模板 | 菜单 | 菜单有没有写错?(模板是否正确) |
动态Prompt | 顾客定制菜单 | 顾客的特殊要求有没有被加入?(参数填充、上下文追加) |
模型 | 厨师 | 厨师有没有按菜单做菜?(模型是否遵循Prompt) |
上下文 | 顾客历史订单 | 顾客之前的偏好有没有被参考?(上下文是否正确传递) |
输出 | 菜品 | 菜品是否符合顾客要求?(输出效果) |
用户反馈 | 顾客评价 | 顾客对菜品满不满意?(反馈闭环) |
提示系统的可观测性,就是餐厅的“智能监控系统”——它能告诉你:
- 菜单(Prompt模板)有没有写错;
- 顾客的特殊要求(动态Prompt)有没有被加入;
- 厨师(模型)有没有按菜单做菜;
- 顾客之前的偏好(上下文)有没有被参考;
- 菜品(输出)有没有符合要求;
- 顾客(用户)对菜品满不满意。
2.2 提示系统可观测性的“4层架构”
基于上述类比,我们可以将提示系统的可观测性拆解为4层,从“基础追踪”到“效果闭环”逐步深入:
graph TD
A[基础层:Prompt全生命周期追踪] --> B[状态层:上下文动态快照]
B --> C[推理层:模型黑盒透视]
C --> D[效果层:用户反馈闭环]
- 基础层:追踪Prompt从“模板→参数填充→动态修改→模型输入”的全流程;
- 状态层:记录上下文的每一次变化,像Git一样管理对话状态;
- 推理层:透视模型的Token生成过程,理解“模型为什么这么输出”;
- 效果层:将用户反馈与观测数据关联,实现“观测-优化”的闭环。
3. 7个可观测性设计技巧:从理论到实践
接下来,我们将逐个讲解7个技巧——每个技巧都包含问题背景、设计思路、实现代码、多场景适配,帮你快速落地。
技巧1:构建Prompt全生命周期追踪——从静态模板到动态执行的链路画像
问题背景
你为客服AI设计了一个Prompt模板:“根据用户订单ID {order_id} 查询物流状态”
。但实际运行中,用户的订单ID是动态填充的,而且会追加“用户之前问过退款政策”的上下文——当AI回答错误时,你不知道是模板错了、参数填错了,还是上下文追加错了。
痛点:静态模板与动态Prompt的差异,导致“问题定位无迹可寻”。
设计思路
给每个Prompt实例分配唯一ID,追踪从“模板定义→参数填充→动态修改→模型输入”的全流程——就像给每个快递包裹贴一个“快递单号”,你可以通过单号查看包裹的“出发→中转→签收”全链路。
实现方法:用OpenTelemetry做链路追踪
OpenTelemetry(OTel)是开源的链路追踪标准,支持跨语言、跨系统的链路追踪。我们可以用OTel给每个Prompt实例打标,记录以下信息:
- 模板ID(区分不同的Prompt模板);
- 填充的参数(比如order_id=123456);
- 上下文追加内容(比如“用户之前问过退款政策”);
- 最终的Prompt内容(模型实际输入的文本)。
代码示例:Python+OpenTelemetry
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
import uuid
# 初始化OTel Tracer
trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer("prompt-lifecycle-tracing")
otlp_exporter = OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces")
span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)
def generate_dynamic_prompt(template: dict, params: dict, context_updates: list) -> tuple[str, str]:
"""生成动态Prompt并记录全生命周期"""
# 生成唯一Prompt实例ID
prompt_instance_id = str(uuid.uuid4())
with tracer.start_as_current_span("prompt-lifecycle") as span:
# 记录基础信息
span.set_attribute("prompt.instance_id", prompt_instance_id)
span.set_attribute("prompt.template_id", template["id"])
span.set_attribute("prompt.template_content", template["content"])
# 1. 参数填充:生成初始Prompt
filled_prompt = template["content"].format(**params)
span.add_event("prompt.filled", attributes={"content": filled_prompt})
# 2. 上下文追加:修改Prompt
for update in context_updates:
filled_prompt += f"\n{update['type']}: {update['content']}"
span.add_event("prompt.context_updated", attributes={
"update_type": update["type"],
"update_content": update["content"]
})
# 3. 最终Prompt:模型输入
span.set_attribute("prompt.final_content", filled_prompt)
return prompt_instance_id, filled_prompt
# 示例使用
template = {
"id": "customer_service_logistics_001",
"content": "根据用户订单ID {order_id} 查询物流状态,用口语化回复。"
}
params = {"order_id": "123456"}
context_updates = [
{"type": "user_history", "content": "用户10分钟前问过退款政策"},
{"type": "tool_response", "content": "物流系统返回:已发货,预计明日18点前到达"}
]
prompt_id, final_prompt = generate_dynamic_prompt(template, params, context_updates)
print(f"Prompt实例ID:{prompt_id}")
print(f"最终Prompt:{final_prompt}")
多场景适配
- 客服场景:追踪“订单ID填充+历史对话追加”的流程;
- 代码生成场景:追踪“用户需求填充+示例代码追加”的流程;
- 数据分析场景:追踪“数据字段填充+之前的分析结论追加”的流程。
技巧2:上下文动态快照——像Git一样管理对话状态
问题背景
多轮对话中,上下文是逐步累加的。比如:
- 用户:“我的订单啥时候到?”
- 系统:“请提供订单ID。”
- 用户:“123456。”
- 系统:“已发货,预计明日到达。”
当系统回答错误时,你需要知道“第三步的订单ID有没有被正确加入上下文”——但传统日志只会记录“最终上下文”,无法查看每一步的变化。
痛点:上下文的“增量变化”无法追踪,导致“问题定位断档”。
设计思路
给每个对话轮次的上下文做“快照”——就像Git的commit
,每一步变化都有记录,你可以回滚到任意版本查看上下文状态。
每个快照需要包含以下信息:
- 快照ID(唯一标识);
- 对话ID(关联到具体用户的对话);
- 轮次号(第1轮、第2轮…);
- 上下文内容(当前轮次的上下文);
- 内容类型(用户输入/系统输出/工具返回);
- 时间戳(记录变化时间)。
实现方法:用Redis存储快照序列
Redis的List
结构适合存储“有序的快照序列”——每个对话ID对应一个List,List中的每个元素是该轮次的快照。
代码示例:Python+Redis
import redis
import uuid
from datetime import datetime
from typing import List, Dict
# 初始化Redis连接
r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)
def save_context_snapshot(
conversation_id: str,
turn_number: int,
content: str,
content_type: str # user_input/system_output/tool_response
) -> str:
"""保存上下文快照"""
snapshot_id = str(uuid.uuid4())
snapshot = {
"snapshot_id": snapshot_id,
"conversation_id": conversation_id,
"turn_number": turn_number,
"content": content,
"content_type": content_type,
"timestamp": datetime.utcnow().isoformat()
}
# 将快照存入Redis:key=conversation:{conversation_id}:snapshots,值为快照JSON字符串
r.rpush(f"conversation:{conversation_id}:snapshots", str(snapshot))
return snapshot_id
def get_context_snapshots(conversation_id: str) -> List[Dict]:
"""获取对话的所有上下文快照"""
snapshots = r.lrange(f"conversation:{conversation_id}:snapshots", 0, -1)
# 将字符串转换为字典
return [eval(snap) for snap in snapshots]
# 示例使用
conversation_id = "user_001_convo_001"
# 第1轮:用户输入
save_context_snapshot(
conversation_id=conversation_id,
turn_number=1,
content="我的订单啥时候到?",
content_type="user_input"
)
# 第2轮:系统输出
save_context_snapshot(
conversation_id=conversation_id,
turn_number=2,
content="请提供你的订单ID。",
content_type="system_output"
)
# 第3轮:用户输入
save_context_snapshot(
conversation_id=conversation_id,
turn_number=3,
content="订单ID是123456。",
content_type="user_input"
)
# 第4轮:系统输出
save_context_snapshot(
conversation_id=conversation_id,
turn_number=4,
content="你的订单已发货,预计明日18点前到达。",
content_type="system_output"
)
# 获取所有快照
snapshots = get_context_snapshots(conversation_id)
for snap in snapshots:
print(f"轮次 {snap['turn_number']} | 类型 {snap['content_type']} | 内容:{snap['content']}")
多场景适配
- 客服场景:追踪“用户输入→系统追问→用户回复→系统回答”的上下文变化;
- 代码生成场景:追踪“用户需求→系统请求示例→用户提供示例→系统生成代码”的上下文变化;
- 数据分析场景:追踪“用户问题→系统请求数据字段→用户提供字段→系统生成分析”的上下文变化。
技巧3:模型推理黑盒透视——关联Prompt输入与Token级输出解析
问题背景
你为代码生成AI设计了Prompt:“写一个Python的稳定排序函数”
,但模型生成的代码是:
def stable_sort(arr):
return sorted(arr) # 缺少id(x),不是稳定排序
你不知道是模型“没理解Prompt”,还是“生成时犯了错误”——因为模型的推理过程是黑盒。
痛点:模型输出的“决策逻辑”无法查看,导致“问题归因模糊”。
设计思路
记录模型的输入Prompt、输出文本,以及Token级生成过程(比如每个Token的概率、候选词、注意力权重)——就像打开汽车的引擎盖,看发动机的每个零件怎么工作。
实现方法:用LLM API的logprobs参数
大部分LLM API(如OpenAI、Anthropic)都支持返回logprobs
(对数概率)——它能告诉你模型生成每个Token时,考虑了哪些候选词,以及每个候选词的概率。
代码示例:OpenAI API+logprobs
from openai import OpenAI
import matplotlib.pyplot as plt
import numpy as np
# 初始化OpenAI客户端
client = OpenAI()
def get_model_inference_details(prompt: str, max_tokens: int = 100) -> dict:
"""获取模型推理的Token级细节"""
response = client.completions.create(
model="gpt-3.5-turbo-instruct",
prompt=prompt,
max_tokens=max_tokens,
logprobs=5, # 返回每个Token的前5个候选词
temperature=0.1 # 降低随机性,方便测试
)
return {
"prompt": prompt,
"output": response.choices[0].text.strip(),
"tokens": response.choices[0].logprobs.tokens,
"token_logprobs": response.choices[0].logprobs.token_logprobs,
"top_logprobs": response.choices[0].logprobs.top_logprobs
}
def visualize_token_probabilities(tokens: list, token_logprobs: list):
"""可视化Token的生成概率"""
# 将logprob转换为概率(logprob是自然对数,exp后得到概率)
probabilities = np.exp(token_logprobs)[:10] # 取前10个Token
tokens = tokens[:10]
plt.figure(figsize=(12, 6))
bars = plt.bar(tokens, probabilities, color="#1f77b4")
plt.xlabel("Token", fontsize=12)
plt.ylabel("Probability", fontsize=12)
plt.title("Model Token Generation Probabilities", fontsize=14)
plt.xticks(rotation=45)
# 在柱子上标注概率值
for bar in bars:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2, height,
f"{height:.4f}", ha="center", va="bottom")
plt.tight_layout()
plt.show()
# 示例使用
prompt = "写一个Python的稳定排序函数。"
inference_details = get_model_inference_details(prompt)
print(f"Prompt:{inference_details['prompt']}")
print(f"模型输出:{inference_details['output']}")
print(f"生成的Token:{inference_details['tokens'][:10]}")
# 可视化Token概率
visualize_token_probabilities(
inference_details["tokens"],
inference_details["token_logprobs"]
)
# 打印前3个Token的候选词
for i in range(3):
print(f"\nToken {i+1}:{inference_details['tokens'][i]}")
print("候选词及概率:")
for candidate, lp in inference_details["top_logprobs"][i].items():
prob = np.exp(lp)
print(f" {candidate}: {prob:.4f}")
结果分析
假设模型生成的第一个Token是def
,其候选词及概率可能是:
def
: 0.9998To
: 0.0001A
: 0.00005
这说明模型很确定第一个Token应该是def
。
如果生成的第5个Token是sorted
,而候选词中stable_sort
的概率只有0.001,说明模型“没理解Prompt中的‘稳定’要求”——这时候你需要优化Prompt,比如明确要求“使用sorted
函数并添加key=lambda x: (x, id(x))
”。
多场景适配
- 客服场景:查看模型生成“物流时间”时的Token概率,判断是否“不确定”;
- 代码生成场景:查看模型生成“稳定排序”相关Token的概率,判断是否“理解要求”;
- 数据分析场景:查看模型生成“结论”时的Token概率,判断是否“有信心”。
技巧4:多维度指标体系——从“有没有用”到“有多好用”的量化评估
问题背景
你为客服AI设计了Prompt,运行一段时间后,你想知道“这个Prompt好不好用”——但传统指标(如QPS、延迟)只能告诉你“系统有没有运行”,无法告诉你“回答准不准确”“用户满不满意”。
痛点:缺乏“效果指标”,导致“优化方向模糊”。
设计思路
建立分层指标体系,覆盖“基础运行→质量效果→用户反馈”三个层面——就像电商的商品评价,不仅要看“有没有货”(基础指标),还要看“质量好不好”(质量指标),更要看“用户满不满意”(用户指标)。
指标体系设计
我们将指标分为3层,每层包含具体的指标项和计算方法:
层级 | 指标项 | 计算方法 |
---|---|---|
基础运行层 | Prompt执行成功率 | 成功执行的Prompt数 / 总Prompt数 |
模型响应时间 | 模型从接收Prompt到返回结果的平均时间 | |
上下文长度 | 每轮对话的上下文平均token数 | |
质量效果层 | 回答相关性 | LLM评估:输出是否与Prompt和上下文相关(0-1分) |
回答准确性 | 领域专家/LLM评估:输出是否符合事实(0-1分) | |
代码运行成功率 | 生成的代码能成功运行的比例(仅代码生成场景) | |
图表相关性 | CLIP模型评估:生成的图表是否与Prompt和数据相关(0-1分,仅多模态场景) | |
用户反馈层 | 用户满意度 | 用户打分的平均值(1-5分) |
反馈整改率 | 收到用户反馈后,能通过优化Prompt解决的比例 | |
二次询问率 | 用户对回答不满意,再次提问的比例 |
实现方法:用Prometheus+Grafana做指标监控
Prometheus是开源的监控系统,Grafana是开源的可视化工具——两者结合可以实现指标的“收集→存储→可视化”。
代码示例:FastAPI+Prometheus
from fastapi import FastAPI, Query
from prometheus_fastapi_instrumentator import Instrumentator
import prometheus_client as pc
from typing import Optional
app = FastAPI(title="Prompt System Metrics")
Instrumentator().instrument(app).expose(app)
# 1. 基础运行层指标
prompt_execution_total = pc.Counter(
"prompt_execution_total",
"Total number of prompt executions",
["template_id", "scene"]
)
prompt_execution_success = pc.Counter(
"prompt_execution_success_total",
"Total number of successful prompt executions",
["template_id", "scene"]
)
prompt_execution_failure = pc.Counter(
"prompt_execution_failure_total",
"Total number of failed prompt executions",
["template_id", "scene"]
)
model_response_time = pc.Histogram(
"model_response_time_seconds",
"Time taken for model to respond",
["template_id", "scene"]
)
context_length = pc.Histogram(
"context_length_tokens",
"Length of context in tokens",
["scene"]
)
# 2. 质量效果层指标
answer_relevance = pc.Gauge(
"answer_relevance_score",
"Relevance of model answer to prompt and context (0-1)",
["template_id", "scene"]
)
code_run_success_rate = pc.Gauge(
"code_run_success_rate",
"Rate of successfully running generated code (0-1)",
["template_id", "scene"]
)
# 3. 用户反馈层指标
user_satisfaction = pc.Gauge(
"user_satisfaction_score",
"User satisfaction score (1-5)",
["template_id", "scene"]
)
second_query_rate = pc.Gauge(
"second_query_rate",
"Rate of users asking a second question (0-1)",
["scene"]
)
@app.post("/execute_prompt")
async def execute_prompt(
template_id: str = Query(...),
scene: str = Query(...),
prompt: str = Query(...),
context_tokens: int = Query(...),
relevance_score: Optional[float] = Query(None),
code_success: Optional[bool] = Query(None)
):
"""模拟执行Prompt并记录指标"""
prompt_execution_total.labels(template_id=template_id, scene=scene).inc()
try:
# 模拟模型调用(记录响应时间)
start_time = pc.utils.time.time()
# model.generate(prompt)
end_time = pc.utils.time.time()
model_response_time.labels(template_id=template_id, scene=scene).observe(end_time - start_time)
# 记录上下文长度
context_length.labels(scene=scene).observe(context_tokens)
# 记录质量效果指标
if relevance_score is not None:
answer_relevance.labels(template_id=template_id, scene=scene).set(relevance_score)
if code_success is not None:
code_run_success_rate.labels(template_id=template_id, scene=scene).set(1 if code_success else 0)
prompt_execution_success.labels(template_id=template_id, scene=scene).inc()
return {"status": "success"}
except Exception as e:
prompt_execution_failure.labels(template_id=template_id, scene=scene).inc()
return {"status": "failure", "error": str(e)}
@app.post("/record_feedback")
async def record_feedback(
template_id: str = Query(...),
scene: str = Query(...),
satisfaction: int = Query(..., ge=1, le=5),
is_second_query: bool = Query(...)
):
"""记录用户反馈指标"""
user_satisfaction.labels(template_id=template_id, scene=scene).set(satisfaction)
second_query_rate.labels(scene=scene).set(1 if is_second_query else 0)
return {"status": "success"}
可视化示例:Grafana Dashboard
你可以用Grafana创建一个Dashboard,展示以下图表:
- 基础运行层:Prompt执行成功率趋势图、模型响应时间直方图;
- 质量效果层:回答相关性得分雷达图、代码运行成功率饼图;
- 用户反馈层:用户满意度折线图、二次询问率热力图。
多场景适配
- 客服场景:重点监控“回答准确性”“用户满意度”“二次询问率”;
- 代码生成场景:重点监控“代码运行成功率”“回答相关性”;
- 数据分析场景:重点监控“图表相关性”“用户满意度”。
技巧5:场景化观测规则——从通用到个性化的异常检测
问题背景
你为客服AI设计了通用的异常检测规则:“如果回答中包含‘不知道’,则标记为异常”。但在代码生成场景中,“不知道”不是异常,“生成的代码有语法错误”才是异常——通用规则无法覆盖多场景的差异。
痛点:通用异常检测规则“水土不服”,导致“误报/漏报”。
设计思路
针对每个场景定义个性化的观测规则——就像医院的科室,内科和外科的检查项目不一样,提示系统的不同场景也需要不同的异常检测规则。
规则设计框架
我们将场景化规则分为3类:语法规则、逻辑规则、业务规则:
规则类型 | 说明 | 示例(代码生成场景) | 示例(客服场景) |
---|---|---|---|
语法规则 | 检查输出的语法正确性 | 生成的代码是否有语法错误 | 回答是否符合口语化要求 |
逻辑规则 | 检查输出的逻辑合理性 | 生成的排序函数是否是稳定排序 | 回答的物流时间是否与工具返回一致 |
业务规则 | 检查输出的业务合规性 | 生成的代码是否调用了禁止的库(如os) | 回答是否包含敏感信息(如手机号) |
实现方法:用规则引擎+LLM做场景化检测
对于语法规则(如代码语法检查),可以用传统的规则引擎(如ast模块、正则表达式);对于逻辑/业务规则(如回答是否符合业务知识库),可以用LLM做语义分析。
代码示例:代码生成场景的语法+逻辑规则检测
import ast
from openai import OpenAI
client = OpenAI()
def check_code_syntax(code: str) -> dict:
"""检查代码语法(语法规则)"""
try:
ast.parse(code)
return {"is_valid": True, "error": None}
except SyntaxError as e:
return {
"is_valid": False,
"error": f"Syntax error at line {e.lineno}, column {e.offset}: {e.msg}"
}
def check_stable_sort_logic(code: str) -> dict:
"""检查稳定排序逻辑(逻辑规则)"""
prompt = f"""
请分析以下Python代码是否实现了稳定排序:
{code}
要求:
1. 稳定排序的定义是“保持相等元素的相对顺序”;
2. 回答“是”或“否”,并说明原因。
"""
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0
)
result = response.choices[0].message.content.strip()
is_stable = "是" in result
return {"is_stable": is_stable, "reason": result}
# 示例使用
good_code = """
def stable_sort(arr):
return sorted(arr, key=lambda x: (x, id(x)))
"""
bad_code = """
def stable_sort(arr):
return sorted(arr) # 缺少id(x),不是稳定排序
"""
# 检查语法
print(check_code_syntax(good_code)) # {"is_valid": True, "error": None}
print(check_code_syntax(bad_code)) # {"is_valid": True, "error": None}(语法正确,但逻辑错误)
# 检查逻辑
print(check_stable_sort_logic(good_code)) # {"is_stable": True, "reason": "..."}
print(check_stable_sort_logic(bad_code)) # {"is_stable": False, "reason": "..."}
多场景适配
- 客服场景:用LLM检查回答是否符合业务知识库(比如“退款政策是7天无理由”);
- 代码生成场景:用ast模块检查语法,用LLM检查逻辑;
- 数据分析场景:用Pandas检查生成的分析代码是否能运行,用LLM检查结论是否符合数据逻辑。
技巧6:用户反馈闭环——从观测到优化的快速迭代
问题背景
你收集了很多用户反馈(比如“回答不准确”“代码不能运行”),但这些反馈没有和观测数据关联——你不知道“这个反馈对应的Prompt实例是哪个”“上下文是什么”“模型输出是什么”,导致无法快速定位问题。
痛点:反馈与观测数据“脱节”,导致“优化效率低”。
设计思路
将用户反馈与观测数据(Prompt实例、上下文快照、模型输出)关联,形成“观测→反馈→定位→优化”的闭环——就像电商的售后系统,用户反馈商品有问题,客服可以关联订单信息、物流信息,快速解决问题。
闭环流程设计
实现方法:用反馈ID关联观测数据
每个用户反馈都分配一个反馈ID,并关联对应的:
- Prompt实例ID(来自技巧1的全生命周期追踪);
- 对话ID(来自技巧2的上下文快照);
- 模型输出(来自技巧3的推理透视)。
代码示例:反馈系统与观测数据关联
import redis
from typing import Dict, Optional
r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)
def submit_feedback(
feedback_id: str,
user_id: str,
prompt_instance_id: str,
conversation_id: str,
satisfaction: int,
comment: str
):
"""提交用户反馈并关联观测数据"""
feedback = {
"feedback_id": feedback_id,
"user_id": user_id,
"prompt_instance_id": prompt_instance_id,
"conversation_id": conversation_id,
"satisfaction": satisfaction,
"comment": comment,
"timestamp": datetime.utcnow().isoformat()
}
# 存储反馈到Redis
r.hset(f"feedback:{feedback_id}", mapping=feedback)
def get_feedback_with_observability(feedback_id: str) -> Optional[Dict]:
"""获取反馈及关联的观测数据"""
feedback = r.hgetall(f"feedback:{feedback_id}")
if not feedback:
return None
# 关联Prompt实例数据(假设用技巧1的OTel数据存储在数据库中)
prompt_instance = get_prompt_instance(feedback["prompt_instance_id"])
# 关联上下文快照(来自技巧2)
context_snapshots = get_context_snapshots(feedback["conversation_id"])
# 关联模型输出(来自技巧3)
model_output = get_model_output(feedback["prompt_instance_id"])
feedback["prompt_instance"] = prompt_instance
feedback["context_snapshots"] = context_snapshots
feedback["model_output"] = model_output
return feedback
# 示例使用
feedback_id = "feedback_001"
submit_feedback(
feedback_id=feedback_id,
user_id="user_001",
prompt_instance_id="prompt_001",
conversation_id="convo_001",
satisfaction=2,
comment="回答的物流时间不对,实际还没发货"
)
# 获取反馈及关联数据
feedback_with_data = get_feedback_with_observability(feedback_id)
print(feedback_with_data)
优化示例:从反馈到Prompt优化
假设用户反馈“回答的物流时间不对”,关联的观测数据显示:
- Prompt实例ID:
prompt_001
; - 最终Prompt:
“根据用户订单ID 123456 查询物流状态”
; - 模型输出:
“你的订单已发货,预计明日到达”
; - 工具返回:
“物流系统返回:未发货”
(但Prompt中没有要求“使用最新的工具返回结果”)。
工程师可以快速定位问题:Prompt没有要求模型使用最新的工具返回结果。于是优化Prompt为:“根据用户订单ID {order_id} 查询最新的物流状态(参考工具返回:{tool_response}),用口语化回复”
。
技巧7:多模态观测——覆盖文本、代码、图像等复杂输出的全维度监控
问题背景
你为数据分析AI扩展了多模态输出(生成图表),但生成的图表与数据不符——比如用户要求“分析月度销量趋势”,模型生成的图表是“季度销量”。你不知道是Prompt没说明“月度”,还是模型没理解,或者是图表生成工具的问题。
痛点:多模态输出(图像、代码、语音)的观测方法缺失,导致“问题定位困难”。
设计思路
针对不同模态的输出,设计针对性的观测方法——就像博物馆的监控系统,不仅要监控人员流动(文本),还要监控展品的状态(图像)、环境的声音(语音)。
多模态观测方法
模态 | 观测方法 | 工具/技术 |
---|---|---|
文本 | 相关性、准确性、合规性 | LLM、正则表达式 |
代码 | 语法检查、运行结果验证 | ast模块、Docker、单元测试 |
图像 | 分辨率、内容相关性、准确性 | OpenCV、CLIP模型、OCR |
语音 | 转文本准确性、情感分析 | Whisper、 librosa |
代码示例:图像生成场景的内容相关性检测
我们用CLIP模型(OpenAI开发的多模态模型)判断生成的图像是否与Prompt相关——CLIP能计算“图像”与“文本Prompt”的相似度,相似度越高,说明图像越符合要求。
import torch
from PIL import Image
from clip import clip
import requests
from io import BytesIO
# 加载CLIP模型
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device)
def check_image_relevance(image_path: str, prompt: str) -> dict:
"""检查图像与Prompt的相关性"""
# 加载图像(支持本地路径或URL)
if image_path.startswith("http"):
response = requests.get(image_path)
image = Image.open(BytesIO(response.content))
else:
image = Image.open(image_path)
# 预处理图像和文本
image_input = preprocess(image).unsqueeze(0).to(device)
text_input = clip.tokenize([prompt]).to(device)
# 计算相似度
with torch.no_grad():
image_features = model.encode_image(image_input)
text_features = model.encode_text(text_input)
# 归一化特征向量
image_features /= image_features.norm(dim=-1, keepdim=True)
text_features /= text_features.norm(dim=-1, keepdim=True)
# 相似度=点积(CLIP的设计)
similarity = (image_features @ text_features.T).item()
# 设定相似度阈值(如0.3,可根据场景调整)
is_relevant = similarity > 0.3
return {
"similarity": similarity,
"is_relevant": is_relevant,
"prompt": prompt,
"image_path": image_path
}
# 示例使用
# 符合要求的图像(猫在海边)
image1 = "https://example.com/cat_beach.jpg"
prompt1 = "一只猫在海边的图片"
result1 = check_image_relevance(image1, prompt1)
print(result1) # {"similarity": 0.45, "is_relevant": True, ...}
# 不符合要求的图像(狗在公园)
image2 = "https://example.com/dog_park.jpg"
prompt2 = "一只猫在海边的图片"
result2 = check_image_relevance(image2, prompt2)
print(result2) # {"similarity": 0.15, "is_relevant": False, ...}
多场景适配
- 代码生成场景:用Docker运行生成的代码,检查是否能成功执行;
- 数据分析场景:用CLIP检查生成的图表是否与Prompt相关;
- 语音助手场景:用Whisper将语音转文本,检查转写准确性。
4. 实际应用:搭建多场景AI助手的可观测性系统
现在,我们将上述7个技巧整合起来,搭建一个多场景AI助手的可观测性系统——覆盖客服、代码生成、数据分析三个场景。
4.1 系统架构
graph TD
A[用户] --> B[AI助手]
B --> C[Prompt生成模块]
C --> D[OTel链路追踪] # 技巧1
C --> E[Redis上下文快照] # 技巧2
B --> F[LLM模型]
F --> G[logprobs推理透视] # 技巧3
B --> H[多模态输出模块]
H --> I[CLIP图像检测] # 技巧7
H --> J[ast代码检查] # 技巧5
B --> K[Prometheus指标收集] # 技巧4
K --> L[Grafana可视化]
A --> M[反馈系统]
M --> N[观测数据关联] # 技巧6
N --> O[工程师优化Prompt]
O --> C
4.2 实现步骤
- 搭建Prompt生成模块:用技巧1的OTel做全生命周期追踪,记录Prompt的生成流程;
- 搭建上下文管理模块:用技巧2的Redis存储上下文快照;
- 集成LLM模型:用技巧3的logprobs参数获取模型推理细节;
- 搭建多模态输出模块:用技巧7的CLIP检查图像相关性,用ast检查代码语法;
- 搭建指标监控系统:用技巧4的Prometheus+Grafana收集并可视化指标;
- 搭建反馈系统:用技巧6的反馈ID关联观测数据,实现闭环;
- 设计场景化规则:用技巧5的规则引擎+LLM做异常检测。
4.3 常见问题及解决方案
- 观测数据量太大:
- 解决方案:用采样策略(比如只记录异常实例、按比例采样),或者用数据归档(将旧数据存储到S3等低成本存储)。
- LLM评估质量指标太慢:
- 解决方案:用轻量化LLM(比如Llama 2 7B、Mistral 7B)做初步评估,或者用批量处理(将多个评估任务合并为一个请求)。
- 多模态观测成本高:
- 解决方案:用异步处理(比如生成图像后,后台异步