大疆无人机:如何革新雪崩救援行动
2026-07-14 3398501
2026-07-14 0
第 02 章能自动提取全库 Schema 了。但开放题已经点破:全库注入在大库上直接崩。

核心矛盾:用户一次提问通常只涉及 2~5 张表,却付了全库的代价。
解法:先召回相关的表,再注入。这是从"全量"到"检索"的关键跃迁。
思路和 RAG 一样:
复制代码离线:把每张表的描述 → 向量 → 存进向量库
在线:用户问题 → 向量 → 检索最相似的 K 张表 → 只注入这 K 张
问题"上月各城市中止率"和表 drives(含 status/drive_date)、vehicles(含 city) 语义相近,会被召回;和 scenarios 关系弱,被过滤。
检索器仍在 L1/L2 边界,它消费第 02 章的 Table,产出"被选中的子集":
复制代码L2 SQL 生成 ← 只拿到相关的 K 张表
▲
L1 SchemaRetriever(本章)← 向量召回
▲
SchemaProvider(第 02 章)
先把每张表压成一段用于检索的"文档"。检索文档和注入文本可以不同 —— 检索用的可以更侧重语义关键词。
retriever.py:
复制代码"""L1: Schema 检索 —— 大库场景只召回相关表。"""
from dataclasses import dataclass
import anthropic
from schema import Table, extract_schema, render_schemaclient = anthropic.Anthropic()
def table_to_doc(t: Table) -> str:
"""把表压成一段适合语义检索的文档。"""
col_names = ", ".join(c.name for c in t.columns)
# 把样例值也拼进去,让"北京""中止"这类词能命中
samples = []
for c in t.columns:
if c.sample_values:
samples.extend(str(v) for v in c.sample_values)
sample_text = ", ".join(samples[:20])
return f"表 {t.name},字段:{col_names}。数据样例:{sample_text}"
生产会用专门的 embedding 服务(如 Voyage、OpenAI embeddings)。这里为了聚焦原理,用一个可替换的 embedder 接口,并给一个基于 embedding API 的实现。
在 retriever.py 追加:
复制代码import math
def embed(texts: list[str]) -> list[list[float]]:
"""把文本批量转成向量。生产可换任意 embedding 服务。 这里用 Voyage/OpenAI 兼容接口示意;教学也可换成本地模型。
"""
# 伪代码占位:实际调用你的 embedding 服务
# resp = embed_client.embeddings.create(model=..., input=texts)
# return [d.embedding for d in resp.data]
raise NotImplementedError("接入你的 embedding 服务")
def cosine(a: list[float], b: list[float]) -> float:
dot = sum(x * y for x, y in zip(a, b))
na = math.sqrt(sum(x * x for x in a))
nb = math.sqrt(sum(y * y for y in b))
return dot / (na * nb + 1e-9)
@dataclass(frozen=True)
class IndexedTable:
table: Table
vector: tuple[float, ...]
class SchemaRetriever:
"""离线建索引,在线按问题召回 top-k 表。""" def __init__(self, tables: tuple[Table, ...]):
docs = [table_to_doc(t) for t in tables]
vectors = embed(docs)
self._index = tuple(
IndexedTable(table=t, vector=tuple(v))
for t, v in zip(tables, vectors)
) def retrieve(self, question: str, k: int = 5) -> tuple[Table, ...]:
qv = embed([question])[0]
scored = sorted(
self._index,
key=lambda it: cosine(qv, list(it.vector)),
reverse=True,
)
return tuple(it.table for it in scored[:k])
如果你暂时没有 embedding API,用一个关键词重叠的 fallback 也能演示效果(生产不要用,仅用于本地体验流程):
复制代码class KeywordRetriever:
"""零依赖的关键词召回 —— 仅用于本地跑通流程,非生产方案。""" def __init__(self, tables: tuple[Table, ...]):
self._tables = tables def retrieve(self, question: str, k: int = 5) -> tuple[Table, ...]:
def score(t: Table) -> int:
doc = table_to_doc(t)
# 命中的字符越多分越高(极简,仅演示)
return sum(1 for ch in set(question) if ch in doc) ranked = sorted(self._tables, key=score, reverse=True)
return tuple(ranked[:k])
真实项目务必用向量检索 —— 关键词召回处理不了"接管/脱离/里程"这类同义词。这正是语义检索的价值。
agent_v3.py:
复制代码"""v3: 带 Schema 检索的问数器。"""
import sqlite3
import anthropic
from schema import extract_schema, render_schema
from retriever import SchemaRetriever # 或 KeywordRetrieverclient = anthropic.Anthropic()PROMPT = """你是 SQL 专家。下面是与问题最相关的表结构(已从大库中检索)。
若你发现缺少必要的表,请只回答:NEED_MORE_TABLES数据库结构:
{schema}规则:只输出 SQL 或 NEED_MORE_TABLES,不要解释。只允许 SELECT。用户问题:{question}
"""
class QueryAgent:
def __init__(self, db: str = "drive.db"):
self.db = db
tables = extract_schema(db)
self.retriever = SchemaRetriever(tables) def ask(self, question: str, k: int = 5) -> list:
relevant = self.retriever.retrieve(question, k=k)
print(f"召回的表: {[t.name for t in relevant]}")
schema_text = render_schema(relevant) resp = client.messages.create(
model="claude-opus-4-8",
max_tokens=500,
messages=[{
"role": "user",
"content": PROMPT.format(schema=schema_text, question=question),
}],
)
sql = resp.content[0].text.strip()
if sql == "NEED_MORE_TABLES":
# 兜底:召回不足时放大 k 或降级为全量
print("召回不足,放大检索范围重试")
return self.ask(question, k=k * 2) print(f"SQL: {sql}")
conn = sqlite3.connect(self.db)
try:
return conn.execute(sql).fetchall()
finally:
conn.close()
if __name__ == "__main__":
agent = QueryAgent()
print(agent.ask("北京有多少次中止的路测?"))
注意两个生产级设计细节:
NEED_MORE_TABLES),触发放大检索。这是从"盲目生成"到"知之为知之"的关键 —— 第 06 章的自愈会大量用到这种"承认失败"的信号。单纯向量召回在生产往往不够,常见增强:
interventions 但漏了 drives,用外键图自动把中间表补上,否则 JOIN 断链。这些不必现在全实现,但要知道它们存在 —— 它们是问数准确率从 60% 到 90% 的关键。
一家自动驾驶路测数据平台的问数系统在演示时准确率 90%,上线后掉到 55%。排查发现:
miles_per_intervention、disengagement_rate)。请思考:
下一章:04 — Few-shot 与生成质量,解决"SQL 语义写不对"的准确率问题。
复制代码项目地址:https://github.com/qiuqiuqiu123/text2sql-agent.git
如果你也在做 AI Agent 编排相关的事情,欢迎交流。
微信公众号「夜猫子agent工坊」
感谢小木API的赞助:https://xiaomuai.cn