Files
ThearchyHelios 1a6082f033 大库的对象索引、探查范围和后台探查(next-round 03 A1),schema_view 预筛与按需加入、剖析事实的后端(A2、遗留 e 的底座)
为什么:几千张表的库以前用不了——探查每个源最多取 200 个对象、按数据库列出的顺序截断,同步请求、整份替换、不排队;
落在窗口外的表在目录、look、剖析里被说成「表已不在」。

做了什么:
- db/models_sources.py:source_objects(对象索引一行一个对象:范围、原因、结构指纹与 struct_version、first_seen / last_seen /
  removed_at、reflected_at、按需加入的留痕)、introspect_jobs / introspect_tasks(后台探查任务与逐表状态)。
- data/source_objects.py:每种方言一条字典查询建对象索引(失败退回 Inspector,上限 5 万,截断时不判删除);探查范围规则
  (options.introspect_scope:通配、按 name_key、排除优先、显式列出 / 移出;有目录的表、已发布模板写死 SQL 用到的表 pinned;
  命中超上限拒绝并说出命中数);唯一的 rebuild_schema_cache 从范围内已反射的行物化 schema_cache(形状不变,另加 scope 摘要);
  三种「不在」分开说(removed / missing / out_of_scope / reflect_failed);按需加入(当场只读反射、落盘、留痕、进程内按源加锁)。
- data/introspect_jobs.py:探查改成后台任务(core/job_runtime,lane kind introspect,和同源起草、剖析串行;按批反射、逐表记状态、
  可停止、读完的照样物化;重启标中断不续跑)。
- api/datasources.py:POST /introspect 返回 202 和任务,dry_run 同步只刷新索引、返回命中数;/introspect-jobs 读任务、停止;
  /introspect-scope 读、预览、保存规则;/schema?table= 带剖析事实、说清三种「不在」;DataSourceOut 加 schema_scope、introspect_job。
- schema_view:search_tables(BM25 倒排,遮罩列只用列名,范围外只按名字和注释、权重低一档)、profile_lines(剖析事实一节,
  附「只供写查询参考」,遮罩按渲染那一刻重滤,上传表格快照不一致时不给)、absent_detail。
- look:候选超过 PREFILTER_LIMIT 时只给预筛结果 + 已用到的表 + 一跳关联;点名或挑中范围外的表按需加入(每次查看最多 4 张),
  look 记录加 added_to_scope、prefiltered;表详情带剖析事实。
- tools/datasource:db_schema__ 加 keyword、不传 table 时只列前 50 张;点名范围外的表按需加入(每次运行最多 8 张);查询结果
  照实列「未按目录检查的表」(unchecked_tables,不新增检查规则);建工具时读剖析事实。
- 剖析:_relation_ends 分清「范围外,可以加入」和「表结构里没有」;任务记上传表格的 snapshot_id;last_facts。表助手、/schema
  用同一段剖析事实。
- 目录接口:清单只列范围内,absent_reason 分开说;GET /catalog/search 查对象索引,POST /catalog/scope/add 加入范围。
- 第 1 波留下的一处:check_valid_filter 和 stored_valid_filter 拼有效记录条件时按 quote_name 强制加引号。
- 工具参数纠错:空着的可选参数不止一个时,只认拼写明显接近的那一个(db_schema 多了 keyword 以后 tabel 仍能纠成 table)。
- 测试:3,000 张表的合成库(对象索引、缺省范围超上限拒绝、规则圈 400 张后台读完、停止、重启标中断、和剖析排队、同步起草
  409)、删除与结构版本、Inspector 回退、截断不判删除、范围规则、pinned、方言金样;改了同步探查的老测试改为等任务做完。
2026-10-16 14:17:06 +08:00

131 lines
5.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""查看的接口与形状(L2、L3),以及生成过程的注入接口(L1)。前端解码(run/decode.ts)按这里的形状认 look 事件。
look 行(模型输出):
{"op":"look","need":"…","targets":[{"kind":"table","source":"库 B","name":"refunds"}],"sources":["库 B"]}
旧写法 tables:[{source, table}] 也认,读进来换成 targets(kind=table)。
look 事件(发给前端、存进 meta.looks):
{"op":"look","id":"L1","by":"model"|"system","need":"…","status":"ok"|"index"|"failed","reason"?,
"elapsed_ms":…,"objects":[{"kind":"table","source":…,"source_id":…,"named":[…],"picked":[…],"joined":[…],
"missing":[…],"total":…,"explored"?:…,"catalog_tables":…,"schema_synced_at":…,"catalog_versions":{…},
"added_to_scope"?:[…],"prefiltered"?:…},
{"kind":"capability","named":[…],"missing":[…],"versions":{…}},
{"kind":"workflow_version","named":[…],"missing":[…]}]}
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Protocol, TypedDict
#: 实现的对象类型。口径(caliber)是已发布口径卡上的指标,蓝图助手复用口径时看(look/calibers.py,normal-user-apps N2)
LOOK_KINDS = ("table", "capability", "workflow_version", "caliber")
LOOK_LIMIT = 3 # 起草阶段每轮最多查看几次
REPAIR_LOOK_LIMIT = 1 # 自查修正阶段另给几次
LOOK_RESULT_CHARS = 12_000 # 一次查看结果的字数上限
PICK_LIMIT = 12 # 挑选最多挑几个;候选不超过这个数时不挑、全给(沿用)
CONTEXT_TABLE_LIMIT = 16 # 一次查看最多给几张表的详情(含中间表,沿用)
#: 挑选时限。经 OpenAI 兼容接入的推理模型关不掉思考(look/pick.PICK_MAX_TOKENS),先想再写:原来的 20 秒常常想到一半就
#: 掐了,交回目录让主模型自己再看一遍反而更慢
PICK_TIMEOUT_S = 45
#: 大库的检索预筛(next-round 03 A2):候选超过这么多张时,挑选模型只看检索(schema_view.search_tables)的前这么多个命中、
#: 工作流已用到的表、命中表的一跳关联表(最多 NEIGHBOR_LIMIT 张),挑选提示词从几万字降到几千字;不超过时照旧给整份目录
PREFILTER_LIMIT = 120
NEIGHBOR_LIMIT = 40
#: 按需加入(目录-D4):点名或挑中探查范围外的表,当场只读反射这一张、落盘、留痕。每次查看最多几张,每次运行最多几张
ON_DEMAND_PER_LOOK = 4
ON_DEMAND_PER_RUN = 8
class LookTarget(TypedDict, total=False):
kind: str # LOOK_KINDS 之一
source: str # 表:库名;能力:所属系统;工作流版本:工作流名
name: str # 表名 / 能力名 / "v3"
@dataclass
class LookResult:
"""一次查看交回给模型的文字,和发给前端、存进 meta.looks 的记录。
记录为 None 的是没真正查看的那几种回话(need 没写、次数用完、重复查看),不发事件。counted:占不占这一轮的次数。
"""
text: str
record: dict[str, Any] | None
counted: bool = True
class LookKind(Protocol):
"""每种可查看的对象都实现同一组方法(L3)。遮罩按类型各自处理。
detail 交回的记录块是 objects[] 里的若干项(表按库各一项,能力、工作流版本各一项);missing 是 resolve 认不出的
点名,由 detail 写进文字和记录。key 是一个对象在这一轮里的身份,重复查看靠它短路。
"""
kind: str
#: 写给模型的类别名:表、能力、工作流版本
label: str
async def overview(self) -> list[str]:
"""概览:进 system,每个来源一行。"""
...
async def index(self, sources: list[str] | None) -> str:
"""目录:挑选失败时交回,每个对象一行。"""
...
async def resolve(self, targets: list[LookTarget]) -> tuple[list[Any], list[LookTarget]]:
"""按点名解析:(认出的对象, 没找到的点名)。"""
...
async def pick(self, need: str, sources: list[str] | None) -> tuple[list[Any], str | None]:
"""按需求挑选:(挑出的对象, 失败原因)。返回空列表且原因为 None 表示「按这个需求没有相关的」。"""
...
async def detail(self, objects: list[Any], budget: int, *,
missing: list[LookTarget] = ()) -> tuple[str, list[dict[str, Any]]]:
"""详情文字(受字数上限约束)和这一类的记录块(objects[] 里的项)。"""
...
def key(self, obj: Any) -> str:
...
# ---- 生成过程的注入接口(L1):循环只管「读到 look 停下、执行、接着写、计次数」,落地和自查由调用方注入 ----
@dataclass
class Applied:
changed: bool
forward: bool = True
problem: str | None = None
class Applier(Protocol):
def apply(self, op: dict[str, Any]) -> Applied: ...
def landed(self) -> list[str]:
"""已经落地的对象 id(接着写的提示里列出来,提醒模型不要重复)。"""
...
def continuing(self) -> None:
"""进入「接着写」:之后和已落地完全相同的操作忽略、不转发(look §2.5.5)。"""
...
def repairing(self) -> None:
"""进入自查修正:plan、done 由循环吃掉;回答、目录修改提案这类只在起草时有意义的不再收。"""
...
class Checker(Protocol):
async def issues(self) -> list[dict[str, Any]]: ...
def repair_request(self, issues: list[dict[str, Any]], looks_text: str) -> str: ...
@dataclass
class LookBudget:
limit: int = LOOK_LIMIT
used: int = 0
seen: set[str] = field(default_factory=set)