Semantic Context Filtering Pattern
问题
原始数据源过于冗长且充斥噪声,无法被LLM有效利用。完整的数据表示包含不可见元素、实现细节和无关信息,会造成context膨胀,干扰推理过程。
这会引发一系列问题:
- Token膨胀:原始数据超出context限制,或使成本高得难以承受
- 信噪比过低:LLM将推理能力浪费在无关细节上
- 推理速度变慢:Token数量越多,生成速度越慢,成本也越高
- 推理混乱:噪声导致生成幻觉或错误结论
该问题在多个领域均有体现:
- 网页爬取:完整的HTML DOM包含脚本、样式、跟踪用iframe
- API响应:包含嵌套元数据、内部字段、调试信息的JSON
- 文档处理:页眉、页脚、导航栏、样板文本
- 代码分析:注释、空白字符、样板代码
方案
从原始数据中仅提取语义、交互或相关元素,过滤掉噪声,为LLM提供能够捕获推理关键信息的干净数据表示。
核心原则
不要向LLM发送原始数据,要发送语义抽象结果。
示例1:浏览器无障碍树
不要发送完整的HTML DOM:
<!-- 原始HTML(10000+个token) -->
<html>
<head>
<script src="analytics.js"></script>
<style>body { margin: 0; }</style>
</head>
<body>
<div class="tracking-pixel" style="display:none"></div>
<iframe src="ad-server.com"></iframe>
<nav aria-label="Navigation">
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main>
<button id="login-button">Login</button>
<input type="email" name="email" placeholder="Email" />
</main>
<footer>Copyright 2024</footer>
</body>
</html>
提取无障碍树(100-200个token):
{
"interactiveElements": [
{
"role": "link",
"name": "Home",
"xpath": "/html/body/nav/a[1]"
},
{
"role": "link",
"name": "About",
"xpath": "/html/body/nav/a[2]"
},
{
"role": "button",
"name": "Login",
"id": "login-button",
"xpath": "/html/body/main/button"
},
{
"role": "textbox",
"name": "Email",
"id": "email",
"xpath": "/html/body/main/input"
}
]
}
实现代码:
// 利用浏览器内置的无障碍树
const tree = await page.accessibility.snapshot({
interestingOnly: true // 仅保留交互元素
});
// 自动过滤以下内容:
// - 带有aria-hidden="true"的元素
// - display:none的元素
// - 广告/追踪类iframe(按域名过滤)
// - 无语义的div和span标签
示例2:API响应过滤
原始API响应通常包含内部元数据:
// 原始API响应(2000个token)
{
"data": {
"users": [
{
"id": "123",
"name": "Alice",
"email": "alice@example.com",
"internalFlags": ["vip", "beta_tester"],
"metadata": {
"created_at": "2024-01-01",
"updated_at": "2024-01-15",
"version": 42
}
}
]
},
"_internal": {
"requestId": "req-abc123",
"latency": 45,
"cache": "HIT",
"debug": []
},
"_links": {
"self": "/users",
"next": "/users?page=2"
}
}
仅保留语义字段:
function filterAPIResponse(response: any, schema: FieldSchema): any {
const filtered = {};
for (const field of schema.relevantFields) {
if (response.data?.[field]) {
filtered[field] = response.data[field];
}
}
return filtered;
}
// 处理结果(200个token):
{
"users": [
{
"name": "Alice",
"email": "alice@example.com"
}
]
}
示例3:文档章节提取
完整文档包含大量模板化内容:
完整文档:
====================
公司机密 [每页重复显示的页眉]
版权所有 2024 Acme公司 保留所有权利。
[长达3页的法律免责声明]
执行摘要
====================
Q4营收增长15%...
[导航菜单]
- 目录
- 索引
- 术语表
实际内容开始
====================
市场趋势分析显示...
[后续50页内容]
附录
========
[技术规格]
[法律免责声明]
[重复显示的联系信息]
提取语义章节:
def extract_semantic_content(document: str) -> dict:
# 跳过页眉、页脚、导航内容
sections = {
"executive_summary": extract_section(document, "EXECUTIVE SUMMARY"),
"analysis": extract_section(document, "ANALYSIS"),
"conclusions": extract_section(document, "CONCLUSIONS"),
}
# 移除模板化内容
for section in sections.values():
section = remove_legal_disclaimers(section)
section = remove_navigation(section)
return sections
# 处理结果:仅保留实际内容,大小约为原始的20%
架构
graph LR
A[原始数据源] --> B[语义过滤器]
B --> C[干净的Context]
subgraph "过滤层"
B --> D[交互元素]
B --> E[相关字段]
B --> F[语义章节]
end
C --> G[LLM处理]
A -. "移除噪声" .-> B
B -. "缩减10-100倍" .-> C
style C fill:#9f9,stroke:#333
style A fill:#f99,stroke:#333
核心优势
| 维度 | 原始数据 | 语义过滤 | 提升效果 | |--------------|----------------|------------------------|------------------------| | Token数量 | 10,000 | 100-1,000 | 缩减10-100倍 | | LLM推理能力 | 受噪声干扰易混淆 | 专注于有效信息 | 决策更准确 | | 成本 | 高昂 | 低廉 | 成本降低10-100倍 | | 延迟 | 缓慢 | 快速 | 速度提升2-5倍 | | 准确性 | 易出错 | 更可靠 | 成功率更高 |
如何使用
1. 识别语义元素
针对你的业务领域,明确真正有价值的内容:
// 网页爬取:仅保留交互元素
const semanticElements = [
'button', 'link', 'textbox', 'checkbox',
'radio', 'combobox', 'slider'
];
// API 响应:仅保留业务数据
const relevantFields = [
'name', 'email', 'status', 'amount'
];
// 文档:仅保留内容章节
const contentSections = [
'executive_summary', 'analysis', 'conclusions'
];
2. 构建过滤层
class SemanticFilter {
filter(data: any, domain: string): any {
switch (domain) {
case 'web':
return this.filterAccessibilityTree(data);
case 'api':
return this.filterAPIResponse(data);
case 'document':
return this.filterDocumentSections(data);
}
}
private filterAccessibilityTree(dom: any): any {
// 仅保留带有ARIA角色的交互元素
return dom
.filter(el => el.interactive) // 筛选交互元素
.filter(el => !el.isHidden) // 剔除隐藏元素
.filter(el => !this.isAdIframe(el)) // 剔除广告iframe
.map(el => ({
role: el.role,
name: el.name,
xpath: el.xpath
}));
}
}
3. 在调用LLM前应用过滤
// 错误做法:直接发送原始数据
const response = await llm.generate({
prompt: `Analyze this page: ${rawHTML}`
});
// 正确做法:先执行过滤
const filtered = semanticFilter.filter(rawHTML, 'web');
const response = await llm.generate({
prompt: `Analyze this page: ${JSON.stringify(filtered)}`
});
4. 维护引用映射
为执行环节记录过滤后元素与原始元素的映射关系:
interface FilteredElement {
semanticId: string; // 语义ID,示例:"login-button"
originalRef: string; // 原始引用标识,示例:"frameIndex-backendNodeId"
xpath: string; // 元素的XPath路径
}
// 过滤后的上下文使用语义ID
const filteredContext = [
{ id: "btn-1", name: "Login", role: "button" }
];
// 执行层将语义ID映射回原始引用
const element = mapToOriginal(filteredContext[0].id);
await page.click(element.xpath);
权衡
优点:
- 大幅减少token用量:context规模缩小10-100倍
- 提升LLM推理能力:聚焦有效信号,而非冗余噪声
- 降低成本:token用量越少,成本越低
- 加快推理速度:context越小,生成速度越快
- 提高可靠性:减少混淆和幻觉
缺点:
- 过滤逻辑复杂度高:需要构建并维护过滤逻辑
- 存在信息丢失风险:可能移除关键context
- 领域特异性强:过滤规则需根据具体用例定制
- 映射开销大:需要跟踪过滤后内容与原始内容的关联关系
- 潜在bug风险:过滤逻辑可能移除重要元素
缓解策略:
- 初始采用保守策略:过滤明显的噪声,纳入边界案例
- 添加过滤绕过机制用于调试
- 监控LLM性能:若准确率下降则扩展过滤规则
- 为过滤规则与数据schema同步做版本管理
- 向LLM提供提示信息:“Context已按相关性过滤”
参考文献
涵盖HyperAgent项目的原始可访问性树实现仓库、WAI-ARIA可访问性树对应的浏览器无障碍API规范,以及上下文窗口焦虑管理、精选上下文窗口两种相关设计模式。
- HyperAgent GitHub 仓库 - 原始可访问性树实现
- WAI-ARIA 可访问性树 - 浏览器无障碍API
- 相关模式:上下文窗口焦虑管理、精选上下文窗口