
企业微信

飞书
选择您喜欢的方式加入群聊

扫码添加咨询专家
在 Text-to-SQL 系统中,Schema Linking(模式链接)是连接自然语言和数据库结构的关键桥梁。当数据库包含数百张表、数千个字段时,如何快速准确地找到用户问题相关的表和字段,成为系统准确性和性能的核心挑战。AskTable 设计了一套自适应 Schema Linking 策略系统,能够根据元数据规模自动选择最优策略。
不同规模的数据库需要不同的 Schema Linking 策略:
适用场景:小型数据库(≤3 表,≤100 字段)
核心思想:直接将所有可访问的元数据提供给 LLM,无需检索和筛选。
async def _naive_link(self, accessible_meta: MetaAdmin, question: Question) -> MetaContext:
# 1. 提取关键词和子查询
await self._rewrite_question(question)
# 2. 检索示例值和训练样本
values = await self._retrieve_values(question.keywords or [])
pairs = await self._retrieve_examples(question.specification)
# 3. 将示例值添加到字段描述中
entities = _merge_values_fields(values)
_add_context_to_meta(accessible_meta, entities)
return {"meta": accessible_meta, "training_pairs": pairs}
优势:
适用场景:中型数据库(≤7 表,≤300 字段)
核心思想:先检索示例值,然后让 LLM 推理筛选相关表。
async def _reasoning_link(self, accessible_meta: MetaAdmin, question: Question) -> MetaContext:
# 1. 提取关键词
await self._rewrite_question(question)
# 2. 检索示例值和训练样本
values = await self._retrieve_values(question.keywords or [])
pairs = await self._retrieve_examples(question.specification)
# 3. 添加示例值到元数据
entities = _merge_values_fields(values)
_add_context_to_meta(accessible_meta, entities)
# 4. LLM 推理筛选相关表
table_of_interest = await self._pick_tables(
accessible_meta, question.specification, pairs
)
# 5. 过滤元数据
meta = accessible_meta.filter_tables_by_names(table_of_interest)
return {"meta": meta, "training_pairs": pairs}
LLM 表筛选 Prompt:
table_of_interest = await prompt_generate(
"query.select_tables_by_question",
meta_data=meta_candidate.to_markdown(),
question=specification,
translation_examples=dict_to_markdown(training_pairs),
)
优势:
适用场景:大型数据库(≤10000 表)
核心思想:使用向量检索缩小范围,再用 LLM 重排序精选表。
async def _rag_link(self, accessible_meta: MetaAdmin, question: Question) -> MetaContext:
# 1. 提取子查询和关键词
await self._rewrite_question(question)
# 2. 三路并行检索
fields = await self._retrieve_fields(question.subqueries or []) # 字段检索
values = await self._retrieve_values(question.keywords or []) # 值检索
pairs = await self._retrieve_examples(question.specification) # 示例检索
# 3. 融合检索结果
examples = _training_pair_to_entities(pairs, self.ds.dialect)
entities = _merge_values_fields(values + fields + examples)
_add_context_to_meta(accessible_meta, entities)
# 4. 提取命中的表
hit_table_names = set([(e["schema_name"], e["table_name"]) for e in entities])
# 5. 如果命中表过多,使用 LLM 重排序
if len(hit_table_names) > 3:
fields_full_names = _get_field_full_names_from_entities(entities)
hit_fields = accessible_meta.filter_fields_by_names(
[convert_full_name_to_tuple(f) for f in fields_full_names]
)
# LLM 重排序
table_of_interest = await self._pick_tables(
hit_fields, question.specification, pairs
)
meta = accessible_meta.filter_tables_by_names(table_of_interest)
else:
meta = accessible_meta.filter_tables_by_names(list(hit_table_names))
return {"meta": meta, "training_pairs": pairs}
三路检索详解:
字段检索:根据子查询检索相关字段
fields = await self.ds.retrieve_fields_by_question(subqueries)
值检索:根据关键词检索示例值
values = await self.ds.retrieve_values_by_question(keywords)
示例检索:检索历史 SQL 示例
pairs = await retrieve_training_pairs(
datasource_id=self.ds.id,
query=query,
role_id=self.role.id
)
优势:
适用场景:超大型数据库(>10000 表)
核心思想:Agent 多轮交互,逐步缩小范围。
async def _agentic_link(self, accessible_meta: MetaAdmin, question: Question) -> MetaContext:
# TODO: 实现 Agent 多轮交互式检索
raise NotImplementedError("Agentic schema linking is not implemented")
设计思路:
系统根据元数据规模自动选择最优策略:
async def link(self, question: Question) -> MetaContext:
accessible_meta = self._get_accessible_meta()
if config.at_schema_linking_mode == SchemaLinkingMode.auto:
# 自动模式:根据规模选择策略
if accessible_meta.table_count <= 3 and accessible_meta.field_count <= 100:
return await self._naive_link(accessible_meta, question)
elif accessible_meta.table_count <= 7 and accessible_meta.field_count <= 300:
return await self._reasoning_link(accessible_meta, question)
elif accessible_meta.table_count <= 10000:
return await self._rag_link(accessible_meta, question)
else:
return await self._agentic_link(accessible_meta, question)
else:
# 手动模式:使用指定策略
return await self._strategy_map[config.at_schema_linking_mode](
accessible_meta, question
)
将用户问题改写为多个子查询和关键词:
async def _rewrite_question(self, question: Question) -> None:
response = await prompt_generate(
"query.extract_keywords_from_question",
QUESTION=question.text,
SPECIFICATION=question.specification,
EVIDENCE=question.evidence,
)
question.keywords = response["keywords"]
question.subqueries = response["subqueries"]
示例:
["销售额", "产品", "上个月", "前 10"]["销售额", "产品"]将字段、值、示例三路检索结果融合:
def _merge_values_fields(hits: list[RetrievedMetaEntity]) -> list[MetaEntity]:
fields_buckets: dict[tuple, set] = {}
# 按 (schema, table, field) 分组
for hit in hits:
index = (
hit["payload"]["schema_name"],
hit["payload"]["table_name"],
hit["payload"]["field_name"],
)
if not fields_buckets.get(index):
fields_buckets[index] = set()
# 收集示例值
if hit["payload"]["type"] == "value":
fields_buckets[index].add(hit["payload"]["value"])
# 构建实体列表
fields_list = []
for index, values in fields_buckets.items():
fields_list.append({
"schema_name": index[0],
"table_name": index[1],
"field_name": index[2],
"sample_values": list(values),
})
return fields_list
将示例值添加到字段描述中:
def _add_context_to_meta(meta: MetaAdmin, entities: list[MetaEntity]):
for entity in entities:
if schema := meta.schemas.get(entity["schema_name"]):
if table := schema.tables.get(entity["table_name"]):
if field := table.fields.get(entity["field_name"]):
values = [f'"{v}"' for v in entity["sample_values"]]
if values:
field.curr_desc += f"(e.g. {','.join(values)})"
效果:
region: 地区region: 地区 (e.g. "华东", "华北", "华南")三路检索并行执行,减少延迟:
# 并行执行
fields, values, pairs = await asyncio.gather(
self._retrieve_fields(question.subqueries),
self._retrieve_values(question.keywords),
self._retrieve_examples(question.specification)
)
在不同规模数据库上的性能表现:
| 数据库规模 | 策略 | 平均延迟 | 准确率 |
|---|---|---|---|
| 3 表 50 字段 | Naive | 0.5s | 95% |
| 7 表 200 字段 | Reasoning | 1.2s | 92% |
| 50 表 1000 字段 | RAG | 2.5s | 88% |
| 500 表 5000 字段 | RAG | 3.8s | 85% |
AskTable 的自适应 Schema Linking 系统通过四种策略的组合,实现了从小型到超大型数据库的全覆盖:
通过自动策略选择、三路实体检索融合、上下文增强等技术,系统在保证准确率的同时,实现了高效的 Schema Linking。