3.1 信息环境设计原则#
3.1.1 信息环境的概念#
信息环境(Information Environment)是指模型在执行任务时可获取的全部信息的集合与组织方式
一个优秀的信息环境的应该满足以下特性:
- 包含任务所需的充分信息
- 排除无关的噪声信息
- 以模型易于理解的方式组织
- 能够动态适应任务需求
3.1.2 注意力预算与上下文腐烂#
注意力预算(Attention budget)#
上下文应该视为有限资源,投入每个token的注意力不是均等的;边界效应递减;需要在上下文大小和注意力集中度之间权衡
上下文腐烂(Context rot)#
随着上下文大小的增长,有效利用率下降
原因:长上下文增加位置偏置,噪声和冲突证据的暴露面;模型训练方式和推理系统差异很大
核心指导原则:优秀的上下文工程意味着找到最小可能的高信号token集
3.1.3 设计原则一: 相关性优先#
核心理念:只包含和当前任务直接相关的信息
相关性的筛选:
将全量信息按照下面几种方式处理
进行相关性评测:
- 高相关: 纳入上下文
- 低相关: 排除
- 可能相关: 条件纳入
3.1.4 设计原则二: 信息密度优先#
核心理念: 用最少的token传达最多的有效信息
信息密度定义为:有效信息/token数量
提高信息密度的方法:
- 删除冗余和重复内容
- 使用结构化格式代替散文
- 压缩长文本为摘要
- 提取关键事实而非全文
3.1.5 设计原则三: 结构化组织#
核心理念:用清晰的结构组织信息,便于模型理解和定位
常见的结构化方法:
| 方法 | 适用场景 | 示例 |
|---|
| XML 标签 | 区分不同类型内容,明确内容边界 | <instructions>...</instructions> |
| Markdown | 层级化文档,组织标题、列表、代码等内容 | 标题、列表、代码块 |
| JSON | 结构化数据,传递配置、元数据等 | {"name":"model","temperature":0.7} |
| 分隔符 | 简单内容分隔,区分不同段落或区域 | ---、=== |
3.1.6 设计原则四: 上下文分层#
核心理念: 根据信息的重要性和稳定性,分层组织上下文
典型的分层结构:
- 系统层:最稳定,定义模型的基本角色和行为边界
- 知识层:相对稳定,提供任务所需的背景知识
- 任务层:随任务变化,描述当前的具体目标
- 交互层:最动态,反应即时的用户输入和对话状态
3.1.7 设计原则五: 显式优于隐式#
核心理念:明确说明期望而非依赖模型预测
模型擅长那个遵循明确的指令,对于隐含期望的推测可能不可靠
- 明确输出格式要求
- 显式说明限制条件
- 清楚定义术语含义
- 提供具体的正例和反例
3.1.8 设计原则六: 渐进式提供#
核心理念:按需提供信息,避免一次性加载全部内容
渐进式提供的方式有:
3.1.9 设计原则七: 系统提示词的高度校准#
系统提示词是上下文的基础,定义了模型的基本角色和行为
两个常见的失败模式:
- 过低的高度(Brittle logic):在系统提示词中硬编码if-else逻辑,试图通过冗长的条件语句精确指定每种情况下的行为
- 过高的高度(Vague Assumptions): 系统提示词过于宽泛或者依赖隐含理解,导致模型无法获得足够的具体信息
校准方法:
- 从最小提示词开始,测试模型在你的目标任务上的表现
- 识别失败模式,添加明确的指导原则(不是规则)
- 测试新版本,迭代改进
- 定期审视,移除不必要的条件和过度说明
- 优先使用示例和启发式原则,而非 if-then 条件
3.2 四大核心策略概览#
可以归纳成四大核心策略:
写入策略(write),选择策略(select).压缩策略(compaction),隔离策略(isolate)
3.2.2 写入策略#
回答的问题是:如何在上下文窗口外存储和管理信息
主要技术:
- 外部记忆系统:将信息持久化到外部
- 知识库构建:组织结构化的领域知识
- 向量数据库:将文本转化为向量进行存储和检索
- 记忆框架:设计多层次的记忆结构
应用场景:
- 构建企业知识库
- 实现跨对话的长期记忆
- 存储用户偏好和历史
3.2.3 选择策略#
回答的问题:如何从海量的信息中获取与当前任务最相关的内容
选择策略是RAG(检索增强生成)的核心
主要技术:
- 语义检索:基于语义相似度
- 关键词检索:基于关键词匹配
- 混合检索:结合前两者
- 重排序:对检索结果进行二次排序优化
应用场景
3.2.4 压缩策略#
要回答的问题:如何在有限的上下文空间中容纳更多的有效信息
旨在提高信息密度
主要技术:
- 文本摘要:长文档压缩为精炼摘要
- 信息提取:提取关键事实和实体
- 对话压缩:压缩冗长的对话历史
- 递进式摘要:分层次压缩
应用场景:
3.2.5 隔离策略#
如何组织和结构化上下文中的不同信息
主要技术:
- XML/标签标记:用标签区分不同类型的内容
- 指令分层
- 任务隔离:为不同的任务单独创建上下文
- 多智能体编排:在多智能体间分配上下文
应用场景:
3.2.6 策略之间的关系#
策略之间可能需要迭代执行,例如:
- 检索后发现信息不足,触发写入
- 压缩后发现关键信息确实,调整压缩策略
- 隔离过程中发现结构不合理,重新组织
同时要警惕 边界泄漏:四种策略之间相互污染
写入层存储的外部内容、选择层召回的文档、压缩层生成的摘要都可能混入看似“指令”的文本。进入隔离层前应保留来源、权限、时间戳和可信等级;进入最终上下文后,也要明确把外部内容标记为“数据”而不是“系统指令”。否则四种策略会在边界处相互污染,导致提示注入、旧事实覆盖新事实或跨用户数据串扰。
3.3 上下文质量评估#
3.3.1 为什么需要评估#
上下文质量直接影响模型的输出质量(Garbage in Garbage out)
评估的难点在于:
- 主观性:说明是好的上下文取决于具体任务
- 多维性:需要同时考虑多个维度
- 动态性:随着知识库的更新,评估结果也会发生变化
3.3.2 检索质量评估#
在RAG系统中,检索是第一道关卡
常见的指标为:
召回率指标:#
常见写法: Recall@K
定义:在前K个检索结果中,包含了多少个相关文档
$$
Recall@K=
前 K 个结果中的相关文档数/总相关文档数
$$
意义:衡量检索系统"找全"能力
适用场景:需要保证关键信息不遗漏时
平均倒数排名指标:#
常见写法: MMR(Mean Reciprocal Rank)
定义:第一个相关文档出现位置的倒数的平均数(如果相关文档排在第 1 位,倒数得分为 1/1=1;排在第 2 位,倒数为 1/2=0.5,依次类推)
$$
MRR = \frac{1}{|Q|}\sum_{i=1}^{|Q|}\frac{1}{\operatorname{rank}_i}
$$
意义:衡量检索系统"首中"的能力.因为模型对于靠前的信息关注度最高
适用场景:问答系统
归一化折损累计增益指标:#
常见写法:NDCG(Normalized Discounted Cumulative Gain)
定义:考虑了所有相关文档的排名位置,排名越靠前权重越高
意义:最全面的排序指标,不仅看"有没有",还看排得"对不对"
适用场景:复杂的检索任务,需要对对各相关文档进行优先级排序
3.3.3 生成质量评估#
传统指标#
BLEU/ROUGE:基于n-gram重叠度的字面匹配指标
局限性:对语义的理解能力弱,LLM的回答可能字面不同但是意思完全正确,因此在LLM评估中权重逐渐降低
基于模型的评估#
利用更强的LLM作为裁判(LLM as a Judge):
- G-Eval:让LLM基于明确的评分标准对输出进行打分
- BERTScore(基于嵌入而非LLM裁判): 用上下文嵌入的token级cos相似度计算生成文本与参考文本的语义相似度,输出P/R/F1标量分数(参考文本可能是更强的LLM生成的,也可能是人工生成的)
评估框架指标#
Ragas是目前流行的RAG评估框架之一,常见的指标包括
- 信实度 (Faithfulness):
- 答案是否完全基于上下文生成?
- 用于检测幻觉(Hallucinations)。
- 答案相关性 (Answer Relevance):
- 上下文精确度 (Context Precision):
- 检索到的上下文中有多少是真正有用的?
- 用于评估信噪比。
- 上下文召回率 (Context Recall):
- 黄金答案所需证据是否被检索到?
- 用于定位“没找全”而不是“找太多”的问题
LLM-as-a-Judge 和自动指标都需要校准。关键发布前应与人工黄金集、反例集和线上反馈交叉验证,避免只优化指标而忽略真实用户问题。
3.3.4 端到端系统评估#
人工评估(Human Evaluation)
- Likert Scale:1-5分打分
- Pairwise Comparison: A/B 两个答案选更好的
- 优点:更准确 缺点:贵,慢,不可拓展
A/B测试:
在生产环境中分流,比较不同策略下的用户反馈(点击率,点赞/点踩,后续追问率)
3.3.5 构建评估数据集#
构建高质量的评估集(Golden Dataset)是上下文工程的重要一环
数据集的结构:#
一个标准的评估样本通常包含:
1
2
3
4
5
6
| {
"question": "如何配置 Redis 缓存?",
"ground_truth": "需要在配置文件中设置...",
"contexts": ["doc_id_1", "doc_id_2"], // 期望检索到的文档ID
"answer": "配置 Redis 缓存的步骤如下..." // 期望的标准答案
}
|
构建方法#
人工构建(Human Curated)
- 挑选真实的用户高频问题
- 由领域专家撰写标准答案和标注相关文档
- 质量很高,但成本昂贵
合成数据生成(Synthetic Data Generation)
- 利用LLM"逆向"生成数据
- Doc2Query:给一段文档,让LLM生成"用户可能问的问题"
- 流程:
- 分块文档
- Prompt:请针对这段文字生成三个相关的问题和标准答案
- 人工抽检生成的质量
- 这种方法可以快速低成本的构建数千个QA对
3.3.6 评估与迭代闭环#

3.4 上下文工程方法论#
3.4.1 方法论的必要性#
方法论提供:
3.4.2 上下文工程生命周期#

阶段一:需求分析#
任务分析:任务类型,复杂度,领域特性
信息分析:信息来源,规模和动态性
约束分析:上下文窗口限制;延迟要求;成本预算
阶段二:设计#
架构设计:
- 确定上下文分层结构
- 设计各层的内容和格式
- 规划动态加载机制
策略选择:
- 确定核心策略
- 设计策略的具体实现方式
- 规划策略之间的协作
工具选型:
阶段三:实现#
基础设施:
上下文构建:
集成测试:
阶段四:评估#
质量评估:
- 按3.3方法评估上下文质量
- 评估端到端任务效果
- 收集用户反馈
性能评估:
成本评估:
阶段五:优化#
识别瓶颈:
迭代改进:
持续监控:
3.4.3 决策框架#
| 问题 | 对应策略 | 关键决策 |
|---|
| 信息太多放不下 | 选择 + 压缩 | 检索 vs 摘要的权衡 |
| 模型缺乏知识 | 写入 + 选择 | 知识库构建方案 |
| 输出不够准确 | 相关性优化 | 检索策略调整 |
| 响应太慢 | 压缩 + 缓存 | 预处理 vs 实时 |
| 成本太高 | 压缩 + 模型选择 | 质量 vs 成本权衡 |
| 结构混乱 | 隔离 | 结构化方案设计 |
3.4.4 最佳实践原则#
1.从简单开始
2.数据驱动:基于数据和评估而非主观;建立评估标准
3.模块化设计:策略单独实现;模块解耦,通过接口交互
4.持续迭代
5.关注端到端:最终优化为用户价值
3.4.5 团队协作#
上下文工程通常涉及多个角色:
- AI 工程师:实现核心策略和系统
- 数据工程师:处理数据流水线和存储
- 产品经理:定义需求和评估标准
- 领域专家:提供专业知识支持
3.4.6 实战案例库#
以下案例展示了上述方法论在真实场景中的应用:
案例一:企业级知识库检索系统#
背景:某大型制造企业拥有海量内部文档(Wiki、技术手册、维修记录),员工需要快速查找解决生产线问题的方案。
挑战:
- 信息孤岛:数据分散在 SharePoint、Confluence 和文件服务器。
- 专业术语多:通用模型难以理解内部缩写(如 “ABC-123 故障”)。
- 权限复杂:不同层级员工可见内容不同。
上下文工程方案:
- 需求分析:确认为“检索密集型”任务,准确性优先于创造性。
- 设计:采用 RAG 架构,配合混合检索(Hybrid Search)。
- 分层:
- 系统层:固定的企业安全规范和输出格式。
- 知识层:动态检索的文档片段(Top-5 chunk)。
- 任务层:用户当前的查询和元数据(部门、设备 ID)。
- 关键实现:
- 术语增强:在写入阶段,利用 LLM 将内部缩写扩展为全称,建立同义词库。
- 权限过滤:在检索阶段(Pre-retrieval),根据用户 ID 过滤索引,确保不越权访问。
- 评估指标(示例口径):
- Recall@5、MRR、NDCG 等检索指标的变化趋势。
- 用户满意度(CSAT)与后续追问率等体验指标。
案例二:多智能体协作编程#
背景:一个致力于自动化代码生成的开源项目,使用多个智能体协同完成从需求分析到代码实现的流程。
挑战:
- 上下文传递丢失:产品经理智能体的详细需求,传到开发者智能体时变得模糊。
- 幻觉累积:早期的错误理解在后续环节被放大。
- 上下文超限:多轮协作后,完整的对话历史迅速撑爆窗口。
上下文工程方案:
- 隔离与结构化:
- 使用标准化的 JSON schema 定义 Agent 间的通信协议。
- 每个 Agent 只接收与其职责相关的上下文片段(Context Slicing)。
- 压缩与检查点:
- 在交接点(Hand-off)引入“总结者 Agent”,将上一步的输出压缩为“已确认的决策点”。
- 必须包含的锚点信息(如技术栈版本、核心 API 签名)始终保留,不被压缩。
- 效果(常见观察):
- 复杂任务的一次通过率与返工率可被明显改善。
- 通过压缩、切片与缓存,Token 消耗在高重复场景下可显著下降。
3.5 上下文管理的量化评估方法#
3.5.1 引言 为什么需要量化评估#
3.5.2 相关性评分指标#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
| from typing import List, Tuple, Set
import numpy as np
class RelevanceScorer:
"""相关性评分工具"""
@staticmethod
def mean_reciprocal_rank(
retrieved_items: List[bool], # True表示相关
threshold_position: int = 10
) -> float:
"""
平均倒数排名 (Mean Reciprocal Rank, MRR)
衡量第一个相关项目出现的位置。
越靠前,分数越高。
公式: MRR = (1/position of first relevant item)
示例:
- [True, False, False] -> 1/1 = 1.0 (最优)
- [False, True, False] -> 1/2 = 0.5
- [False, False, False] -> 0.0 (无相关项)
"""
for i, is_relevant in enumerate(retrieved_items[:threshold_position]):
if is_relevant:
return 1.0 / (i + 1)
return 0.0
@staticmethod
def recall_at_k(
retrieved_items: List[bool],
relevant_item_count: int,
k: int = 10
) -> float:
"""
召回率 (Recall@k)
在前k个结果中,找到了多少个相关项。
公式: Recall@k = (前k个结果中的相关数) / 总相关数
示例:
- 总共10个相关项,前20个结果中找到8个 -> 8/10 = 0.8
- 总共5个相关项,前10个结果中找到3个 -> 3/5 = 0.6
"""
if relevant_item_count == 0:
return 0.0
relevant_in_top_k = sum(retrieved_items[:k])
return relevant_in_top_k / relevant_item_count
@staticmethod
def precision_at_k(
retrieved_items: List[bool],
k: int = 10
) -> float:
"""
精确度 (Precision@k)
前k个结果中有多少比例是相关的。
公式: Precision@k = (前k个中的相关数) / k
示例:
- 前10个结果中有8个相关 -> 8/10 = 0.8
- 前5个结果中有1个相关 -> 1/5 = 0.2
"""
if k == 0:
return 0.0
relevant_in_top_k = sum(retrieved_items[:k])
return relevant_in_top_k / k
@staticmethod
def f1_score(
retrieved_items: List[bool],
relevant_item_count: int,
k: int = 10
) -> float:
"""
F1分数 (F1 Score)
Precision和Recall的调和平均。
在精确度和召回率之间找到平衡。
公式: F1 = 2 * (Precision * Recall) / (Precision + Recall)
"""
precision = RelevanceScorer.precision_at_k(retrieved_items, k)
recall = RelevanceScorer.recall_at_k(retrieved_items, relevant_item_count, k)
if precision + recall == 0:
return 0.0
return 2 * (precision * recall) / (precision + recall)
@staticmethod
def ndcg(
relevance_scores: List[float],
k: int = 10,
ideal_dcg: float = None
) -> float:
"""
归一化折损累计增益 (Normalized Discounted Cumulative Gain, NDCG)
考虑相关性的"程度"(不只是相关/不相关)。
排名靠前的相关项贡献更大。
公式:
DCG@k = sum(relevance[i] / log2(i+2)) for i in 0..k-1
NDCG@k = DCG@k / IDCG@k
其中IDCG是完美排序下的DCG。
示例:
relevance_scores = [0.9, 0.7, 0.0, 0.5, 0.8]
DCG@5 = 0.9/1 + 0.7/log2(3) + 0/log2(4) + 0.5/log2(5) + 0.8/log2(6)
"""
if not relevance_scores or k == 0:
return 0.0
# 计算实际的DCG
dcg = 0.0
for i, score in enumerate(relevance_scores[:k]):
# 注意:log2(i+2)表示从位置1开始计数
dcg += score / np.log2(i + 2)
# 计算理想的DCG(如果没有给出,则用排序后的最优值)
if ideal_dcg is None:
sorted_scores = sorted(relevance_scores, reverse=True)
ideal_dcg = 0.0
for i, score in enumerate(sorted_scores[:k]):
ideal_dcg += score / np.log2(i + 2)
if ideal_dcg == 0:
return 0.0
return dcg / ideal_dcg
# 使用示例
if __name__ == "__main__":
# 场景:搜索"Python教程",返回了10个结果
# 其中标注的相关性:
retrieved = [
True, # 位置1: 很相关
False, # 位置2: 不相关
True, # 位置3: 相关
False, # 位置4: 不相关
False, # 位置5: 不相关
True, # 位置6: 相关
False, # 位置7: 不相关
False, # 位置8: 不相关
True, # 位置9: 相关
False, # 位置10: 不相关
]
total_relevant_count = 6 # 来自黄金标注:语料库中共有6个相关项
scorer = RelevanceScorer()
mrr = scorer.mean_reciprocal_rank(retrieved)
recall = scorer.recall_at_k(retrieved, total_relevant_count, k=10)
precision = scorer.precision_at_k(retrieved, k=10)
f1 = scorer.f1_score(retrieved, total_relevant_count, k=10)
print(f"MRR: {mrr:.2f}") # 应该是1.0(第1个位置)
print(f"Recall@10: {recall:.2f}") # 应该是0.67(找到6个相关项中的4个)
print(f"Precision@10: {precision:.2f}") # 应该是0.4(4/10)
print(f"F1: {f1:.2f}")
|
分级相关性指标#
实际场景中,相关性常常不是二元的(相关/不相关),而是有多个等级
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
| class GradedRelevanceScorer:
"""处理分级相关性的评估工具"""
# 常见的相关性等级
RELEVANCE_GRADES = {
0: "完全不相关",
1: "部分相关",
2: "相关",
3: "高度相关",
}
@staticmethod
def dcg_with_grades(
relevance_grades: List[int], # [3, 2, 0, 1, 3, ...]
k: int = 10
) -> float:
"""
使用分级相关性计算DCG。
"""
dcg = 0.0
for i, grade in enumerate(relevance_grades[:k]):
# 标准NDCG使用 (2^grade - 1) 作为相关性权重
weight = (2 ** grade) - 1
dcg += weight / np.log2(i + 2)
return dcg
@staticmethod
def ndcg_with_grades(
relevance_grades: List[int],
k: int = 10
) -> float:
"""
使用分级相关性计算NDCG。
"""
dcg = GradedRelevanceScorer.dcg_with_grades(relevance_grades, k)
# 理想的DCG:把同一批候选的人工相关性等级按从高到低排序
ideal_grades = sorted(relevance_grades, reverse=True)[:k]
ideal_dcg = GradedRelevanceScorer.dcg_with_grades(ideal_grades, k)
if ideal_dcg == 0:
return 0.0
return dcg / ideal_dcg
@staticmethod
def metric_at_each_level(
relevance_grades: List[int],
k: int = 10
) -> dict:
"""
计算每个相关性等级的指标分布。
"""
grade_counts = {grade: 0 for grade in range(4)}
for grade in relevance_grades[:k]:
grade_counts[grade] += 1
return {
'not_relevant': grade_counts[0],
'partially_relevant': grade_counts[1],
'relevant': grade_counts[2],
'highly_relevant': grade_counts[3],
}
|
3.5.3 检索质量度量#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
| class RetrievalQualityEvaluator:
"""综合的检索质量评估"""
def __init__(self):
self.scorer = RelevanceScorer()
def evaluate_retrieval(
self,
query: str,
retrieved_results: List[dict], # [{'id': '...', 'is_relevant': True/False, 'relevance_score': 0.8}]
total_relevant_count: int,
top_k_values: List[int] = None
) -> dict:
"""
对单个查询的检索结果进行多维度评估。
total_relevant_count 必须来自黄金标注或完整候选集,不能从 retrieved_results 推断。
"""
if top_k_values is None:
top_k_values = [1, 5, 10]
# 提取相关性标签
is_relevant = [r.get('is_relevant', False) for r in retrieved_results]
relevance_scores = [r.get('relevance_score', 0.0) for r in retrieved_results]
evaluation = {
'query': query,
'total_retrieved': len(retrieved_results),
'total_relevant': total_relevant_count,
}
# 计算多个k值下的指标
for k in top_k_values:
k_metrics = {
'mrr': self.scorer.mean_reciprocal_rank(is_relevant, k),
'recall': self.scorer.recall_at_k(is_relevant, total_relevant_count, k),
'precision': self.scorer.precision_at_k(is_relevant, k),
'f1': self.scorer.f1_score(is_relevant, total_relevant_count, k),
}
evaluation[f'metrics@{k}'] = k_metrics
return evaluation
def evaluate_retrieval_batch(
self,
queries: List[str],
retrieved_results_batch: List[List[dict]],
total_relevant_counts: List[int],
top_k_values: List[int] = None
) -> dict:
"""
对多个查询的检索结果进行批量评估,并计算平均指标。
"""
if top_k_values is None:
top_k_values = [5, 10]
individual_results = []
aggregated_metrics = {f'@{k}': {} for k in top_k_values}
for query, results, total_relevant_count in zip(queries, retrieved_results_batch, total_relevant_counts):
indiv = self.evaluate_retrieval(query, results, total_relevant_count, top_k_values)
individual_results.append(indiv)
# 累积指标
for k in top_k_values:
k_str = f'@{k}'
k_metrics = indiv[f'metrics@{k}']
for metric_name, metric_value in k_metrics.items():
if metric_name not in aggregated_metrics[k_str]:
aggregated_metrics[k_str][metric_name] = []
aggregated_metrics[k_str][metric_name].append(metric_value)
# 计算平均值
for k_str in aggregated_metrics:
for metric_name in aggregated_metrics[k_str]:
values = aggregated_metrics[k_str][metric_name]
aggregated_metrics[k_str][metric_name] = {
'mean': np.mean(values),
'std': np.std(values),
'min': np.min(values),
'max': np.max(values),
}
return {
'individual_results': individual_results,
'aggregated_metrics': aggregated_metrics,
'total_queries': len(queries),
'average_retrieval_count': np.mean([r['total_retrieved'] for r in individual_results]),
}
|
3.5.4 上下文效率指标#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
| class ContextEfficiencyAnalyzer:
"""分析上下文的使用效率"""
@staticmethod
def information_density(
context: str,
relevant_spans: List[str]
) -> float:
"""
信息密度 = 相关信息量 / 总信息量
衡量上下文中"有用信息"的比例。
relevant_spans 是上下文中被判定为“与当前任务相关”的片段原文,
来自人工标注或评估模型。密度必须由两侧的实际规模算出:只传入一个
现成的 relevance_score 再乘回总量,分子分母会约掉,得到的永远是
传进来的那个分数本身,与 context 无关。
"""
total_tokens = len(context) // 4 # 粗略估计
if total_tokens <= 0:
return 0.0
useful_tokens = sum(len(span) // 4 for span in relevant_spans)
return min(1.0, useful_tokens / total_tokens)
@staticmethod
def redundancy_ratio(
context: str
) -> float:
"""
冗余度 = 重复内容的比例
通过n-gram重复频率估计冗余度。
"""
# 简化:提取所有5-gram并统计重复
words = context.split()
if len(words) < 5:
return 0.0
ngrams = []
for i in range(len(words) - 4):
ngram = ' '.join(words[i:i+5])
ngrams.append(ngram)
unique_ngrams = len(set(ngrams))
total_ngrams = len(ngrams)
# 冗余度 = 1 - (unique / total)
redundancy = 1 - (unique_ngrams / total_ngrams) if total_ngrams > 0 else 0
return redundancy
@staticmethod
def context_coverage_ratio(
context: str,
query: str
) -> float:
"""
覆盖率 = 查询中被上下文覆盖的关键词比例
衡量上下文对查询的覆盖程度。
"""
query_keywords = set(query.lower().split())
context_lower = context.lower()
covered = sum(1 for kw in query_keywords if kw in context_lower)
total = len(query_keywords)
return covered / total if total > 0 else 0
@staticmethod
def efficiency_score(
context: str,
query: str,
relevant_spans: List[str],
max_tokens: int = 2000
) -> float:
"""
综合效率分数 (0-100)
综合考虑:
- 信息密度(越高越好)
- 冗余度(越低越好)
- 覆盖率(越高越好)
- Token利用率(在预算内)
四项里只有信息密度需要外部标注(relevant_spans),其余三项都能
从 context 和 query 直接算出。权重是可调的工程约定,不是通用常数。
"""
density = ContextEfficiencyAnalyzer.information_density(context, relevant_spans)
redundancy = ContextEfficiencyAnalyzer.redundancy_ratio(context)
coverage = ContextEfficiencyAnalyzer.context_coverage_ratio(context, query)
# Token利用率
context_tokens = len(context) // 4
token_utilization = min(1.0, context_tokens / max_tokens)
# 加权综合
efficiency = (
density * 0.35 + # 信息密度权重35%
(1 - redundancy) * 0.25 + # 非冗余度权重25%
coverage * 0.25 + # 覆盖率权重25%
token_utilization * 0.15 # Token利用率权重15%
)
# 规范化到0-100
return efficiency * 100
# 使用示例
if __name__ == "__main__":
evaluator = RetrievalQualityEvaluator()
# 评估单个查询
query = "Python教程"
results = [
{'id': '1', 'is_relevant': True, 'relevance_score': 0.95},
{'id': '2', 'is_relevant': False, 'relevance_score': 0.2},
{'id': '3', 'is_relevant': True, 'relevance_score': 0.85},
{'id': '4', 'is_relevant': False, 'relevance_score': 0.15},
{'id': '5', 'is_relevant': True, 'relevance_score': 0.8},
]
total_relevant_count = 4 # 来自黄金标注:语料库中共有4个相关文档
evaluation = evaluator.evaluate_retrieval(query, results, total_relevant_count)
print("单个查询评估:")
print(f" 总相关: {evaluation['total_relevant']}")
print(f" Recall@5: {evaluation['metrics@5']['recall']:.2f}")
print(f" Precision@5: {evaluation['metrics@5']['precision']:.2f}")
# 评估效率
context = """
Python是一种高级编程语言,以其简洁易读的语法而闻名。
Python支持多种编程范式,包括过程式、面向对象和函数式编程。
Python广泛用于数据科学、机器学习和Web开发。
Python有一个庞大的标准库和第三方库生态系统。
"""
# 标注出上下文里真正回答了"Python教程"的片段
relevant_spans = [
"Python是一种高级编程语言,以其简洁易读的语法而闻名。",
"Python支持多种编程范式,包括过程式、面向对象和函数式编程。",
]
efficiency = ContextEfficiencyAnalyzer.efficiency_score(context, query, relevant_spans)
print(f"\n上下文效率分数: {efficiency:.1f}/100")
|
3.3.5 A/B测试框架在上下文优化中的作用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
| from scipy import stats
class ABTestFramework:
"""
A/B测试框架
用于统计显著性验证
"""
@staticmethod
def paired_ttest(
control_group: List[float],
treatment_group: List[float],
alpha: float = 0.05
) -> dict:
"""
配对t检验
同一批查询分别跑 control 和 treatment 时,应该比较逐查询差值。
"""
t_stat, p_value = stats.ttest_rel(control_group, treatment_group)
is_significant = p_value < alpha
return {
't_statistic': t_stat,
'p_value': p_value,
'is_significant': is_significant,
'control_mean': np.mean(control_group),
'treatment_mean': np.mean(treatment_group),
'improvement': (
(np.mean(treatment_group) - np.mean(control_group)) /
np.mean(control_group) * 100
if np.mean(control_group) != 0 else 0
),
}
@staticmethod
def cohen_d(
control_group: List[float],
treatment_group: List[float]
) -> float:
"""
计算效果量 (Effect Size)
衡量两组差异的实际大小(不只是统计显著性)。
"""
deltas = np.array(treatment_group) - np.array(control_group)
mean_diff = np.mean(deltas)
pooled_std = np.std(deltas)
if pooled_std == 0:
return 0
return mean_diff / pooled_std
@staticmethod
def calculate_sample_size(
effect_size: float = 0.5, # Cohen's d
alpha: float = 0.05,
power: float = 0.8
) -> int:
"""
计算所需样本量
为了达到统计显著性,需要多少样本?
"""
# 简化公式
z_alpha = stats.norm.ppf(1 - alpha / 2)
z_beta = stats.norm.ppf(power)
sample_size = int(
2 * ((z_alpha + z_beta) / effect_size) ** 2
)
return sample_size
class ContextOptimizationABTest:
"""上下文优化的A/B测试设计"""
def __init__(self, control_strategy: str, treatment_strategy: str):
self.control_strategy = control_strategy
self.treatment_strategy = treatment_strategy
self.control_results = []
self.treatment_results = []
def run_test(
self,
queries: List[str],
ground_truth_answers: List[str],
llm_generate_func
) -> dict:
"""
执行A/B测试。
control_strategy: 原始的上下文工程方案
treatment_strategy: 新的上下文工程方案
"""
print(f"Running A/B Test: {self.control_strategy} vs {self.treatment_strategy}")
# 测试Control组
for query, ground_truth in zip(queries, ground_truth_answers):
context = self._prepare_context(query, strategy=self.control_strategy)
answer = llm_generate_func(context, query)
similarity = self._similarity(answer, ground_truth)
self.control_results.append(similarity)
# 测试Treatment组
for query, ground_truth in zip(queries, ground_truth_answers):
context = self._prepare_context(query, strategy=self.treatment_strategy)
answer = llm_generate_func(context, query)
similarity = self._similarity(answer, ground_truth)
self.treatment_results.append(similarity)
# 统计分析
test_results = ABTestFramework.paired_ttest(
self.control_results,
self.treatment_results
)
effect_size = ABTestFramework.cohen_d(
self.control_results,
self.treatment_results
)
test_results['effect_size'] = effect_size
return test_results
def _prepare_context(self, query: str, strategy: str) -> str:
"""准备上下文(这里是简化实现)"""
# 实际应该调用不同的上下文处理流程
return f"Using {strategy}: {query}"
def _similarity(self, text1: str, text2: str) -> float:
"""计算两个文本的相似度"""
# 简化:基于词重叠。生产评估应换成经过校准的语义指标、
# LLM-as-a-judge 量表或人工黄金标注,避免把复述当成正确性。
words1 = set(text1.lower().split())
words2 = set(text2.lower().split())
if not words1 or not words2:
return 0.0
overlap = len(words1 & words2)
union = len(words1 | words2)
return overlap / union
|