diff --git a/crates/utopia-server/src/api/auth_routes.rs b/crates/utopia-server/src/api/auth_routes.rs index a9a532d46..464a99120 100644 --- a/crates/utopia-server/src/api/auth_routes.rs +++ b/crates/utopia-server/src/api/auth_routes.rs @@ -7,7 +7,6 @@ use serde_json::json; use utopia_core::models::User; use utopia_core::AppError; -use super::kbs::{install_packs, DEFAULT_PACK}; use crate::auth::{self, AuthUser}; use crate::error::ApiResult; use crate::state::AppState; @@ -66,7 +65,7 @@ pub async fn register( let utopia_store::accounts::Registered { user, workspace, - general_kb, + general_kb: _, } = utopia_store::accounts::register( &state.pool, req.email.trim(), @@ -77,23 +76,11 @@ pub async fn register( ) .await?; - /* 首个用户那个 General 库也要有词汇表(#322)。 - 建库对话框里 schema.org 是预勾选的默认(0008、0009),而这条路径绕过了 - 对话框——于是每个部署的第一个库、也就是新用户落地的那个,本体是空的: - 没有 domain/range,抽取的方向就定不住,事实退化成 related_to, - 而这正是 0008 装包要解决的事。 - - **装不上不能挡住注册。** 事务已经提交,账号已经存在;这一步失败只是 - 回到今天的行为(库空着,日后手动装包再重跑类型消解即可,见 0009), - 让人注册不进来则是把小事变成大事 */ - if let Some(kb_id) = general_kb { - if install_packs(&state, kb_id, user.id, &[DEFAULT_PACK.to_string()]) - .await - .is_err() - { - tracing::warn!(kb_id = %kb_id, pack = DEFAULT_PACK, "默认库的冷启动本体包没装上"); - } - } + // 首个用户那个 General 库**空着起步**(#580)。从前这里给它装 schema.org(#322), + // 理由是没有 domain/range 事实就没方向;量过之后(#580 的对照):包给的是实体 + // 类型,不是谓词——装了包八成事实照样没谓词;空库靠 bootstrap 从文档里长出 + // 十几个类、上百条关系,都是语料自己的说法,每块的提示词从 18k tokens 回到 2k。 + // 包还在建库对话框里,要的人一键装 let token = auth::issue_token(&state, user.id)?; let secure = auth::behind_tls(&headers, state.cookie_secure); diff --git a/crates/utopia-server/src/api/kbs.rs b/crates/utopia-server/src/api/kbs.rs index 95d2ad077..89a4a71d5 100644 --- a/crates/utopia-server/src/api/kbs.rs +++ b/crates/utopia-server/src/api/kbs.rs @@ -391,10 +391,9 @@ pub async fn audit_log( /// **一个包失败不回滚已装的**:本体是加法,装了一半的库仍然可用, /// 而回滚要撤已经建好的类——那正是 0008 决定不做导入撤销的理由。 /// 失败信息里带上是哪个包,让人知道从哪补。 -/// 建库对话框里预勾选的那个包(0008、0009)。注册时建出来的 General 库 -/// 绕过了对话框,所以它在那条路径上也要用同一个默认(#322)。 -pub(super) const DEFAULT_PACK: &str = "schema-org"; - +/// +/// 没有默认包(#580):注册建的 General 库与对话框里新建的库都空着起步, +/// 装哪个包是建库的人选的。 pub(super) async fn install_packs( state: &AppState, kb_id: Uuid, diff --git a/docs/decisions/0008-ontology-packs-as-cold-start.md b/docs/decisions/0008-ontology-packs-as-cold-start.md index 53216bd55..9b56d3bc6 100644 --- a/docs/decisions/0008-ontology-packs-as-cold-start.md +++ b/docs/decisions/0008-ontology-packs-as-cold-start.md @@ -1,9 +1,10 @@ # 0008 · Ontology packs as the cold start - **Status**: Built · five packs embedded in the binary (gzip, 1.7 MB → 316 KB), - multi-select at KB creation with schema.org checked by default, 22-row static alignment - table · a new KB seeds no relations: the packs are the whole ontology · the three open - questions stay open; the Chinese-label one got worse (2026-09-02 check) + multi-select at KB creation, 22-row static alignment table · a new KB seeds no + relations: the packs are the whole ontology · **no pack is checked by default and the + base made at registration installs none** (2026-09-10, #580) · the three open questions + stay open; the Chinese-label one got worse (2026-09-02 check) - **Written**: 2026-08-30 · condensed into English 2026-09-03 - **Related**: [0001](0001-ontology-import-and-governance.md) criteria and IRI/key split; [0006](0006-ontology-scale-and-the-prompt.md) prompt budget; @@ -99,6 +100,21 @@ order status). of packs is not prompt length but **choice**: many names are generic with narrow meanings (`affectedBy` is a medical test, `competitor` a sports event), and picking by name or vector will hit them. +- 2026-09-10 (#580): **the default is no pack.** schema.org was pre-selected in the creation + dialog and installed into the base made at registration (#322), on the argument that a + base without domain and range has no direction for its facts. Measured on the ai-timeline + corpus (15 documents, DeepSeek-V3.2, `auto_extend_ontology` on, bootstrap allowed to + finish): the pack gives types, not predicates. With schema.org, 98% of entities were typed + at 90% accuracy against the Wikidata sheet, but 81% of facts still had no predicate before + bootstrap and 65% after, and 342 facts were dropped for `domain_mismatch`; the empty base + ended with 13 classes and 175 relations in the corpus's own words, 80% of entities typed + at 77%, and *more* entity-to-entity edges with a predicate (56% against 49%). Each + extraction call carried ~2k tokens instead of ~18k (the retrieved slice of a 915-class + pack; the full list would be ~105k and never goes in). The pack costs ten points of type + accuracy to leave out and buys an ontology the base grows itself; that is the trade this + record now makes by default, and a base that wants schema.org's vocabulary picks it in + the dialog. Still unmeasured: how many edges in the empty base run backwards, which the + direction check cannot catch without signatures. ## Open questions diff --git a/docs/decisions/README.md b/docs/decisions/README.md index c35d78677..13e43913d 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -31,7 +31,7 @@ The test for writing one: if someone (including us) looks at a piece of code in | 0005 | [The alert center](0005-alert-center.md) | Built · five alert kinds live, search and paging in the panel · `document.no_text_layer` still unwired | | 0006 | [Ontology scale and the extraction prompt](0006-ontology-scale-and-the-prompt.md) | Built · the character budget (24,000) and per-chunk retrieval live, values untested · answer keys still hand-filled | | 0007 | [Counting decides what becomes a relation](0007-who-decides-what-becomes-a-relation.md) | Built · adoption decided by counting (`MIN_DOCS = 2`, `MIN_SIGNALS = 3`), proposals persist (#112) · narrative verbs and `_by` folding still open | -| 0008 | [Ontology packs as the cold start](0008-ontology-packs-as-cold-start.md) | Built · five packs embedded, multi-select at creation, schema.org by default · three open questions stay open; Chinese labels got worse | +| 0008 | [Ontology packs as the cold start](0008-ontology-packs-as-cold-start.md) | Built · five packs embedded, multi-select at creation · no pack by default and none at registration (#580, measured) · three open questions stay open; Chinese labels got worse | | 0009 | [An undecided type stays empty](0009-no-type-is-a-type.md) | Implemented · `type_id` nullable, builtin classes gone · kin classes go to Review (#226), declared `disjointWith` keeps them apart (0016 B3) · `metric` / `dimension` builtin on demand (#231) · `metric` / `dimension` to retire under 0036 | | 0010 | [An unnamed relation stays empty](0010-no-relation-is-no-relation.md) | Implemented · `predicate_id` nullable, `related_to` gone, wording recovered by `fact_surface_predicate` · follow-ups done with 0011 | | 0011 | [A mapping is configuration](0011-a-mapping-is-not-a-fact.md) | Implemented (#126 / #140 / #148) · Review flow and revision history rebuilt · the evidence chain not built · revised in part by 0036: the concept becomes an attribute or a rule, the table becomes a rendered one | diff --git a/docs/pipeline.md b/docs/pipeline.md index 34cb2b9e9..42ccef3ae 100644 --- a/docs/pipeline.md +++ b/docs/pipeline.md @@ -38,7 +38,7 @@ flowchart TB **The dotted line back into extraction is the loop.** Extraction uses the ontology; when it meets a term the ontology lacks, it records the original wording; proposals flow back into the ontology; the next batch of documents is extracted with it. See [0003](decisions/0003-ontology-growth-loop.md). -**Where the ontology comes from.** A new database seeds nothing. The starting point is an optional prebuilt pack (schema.org checked by default; also W3C Org, PROV-O, FOAF, IOF Core), a user-imported OWL file, or nothing at all. An empty ontology still extracts; an entity without a type simply has no type. See [0008](decisions/0008-ontology-packs-as-cold-start.md) and [0009](decisions/0009-no-type-is-a-type.md). +**Where the ontology comes from.** A new database seeds nothing. The starting point is an optional prebuilt pack (schema.org, W3C Org, PROV-O, FOAF, IOF Core; none is checked by default), a user-imported OWL file, or nothing at all, which is the default. An empty ontology still extracts; an entity without a type simply has no type. See [0008](decisions/0008-ontology-packs-as-cold-start.md) and [0009](decisions/0009-no-type-is-a-type.md). **The bottom two boxes are the ontology's axioms at work.** The consistency check never writes `facts`; it only surfaces contradictions (the Review page's two tiers, violations and defects). Materialized inference is off by default; when on, derived facts go to a separate table, show as gold on the graph, and never close any asserted fact. See section 4. diff --git a/web/src/api.ts b/web/src/api.ts index 713fe5e6e..3d3425e0e 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -1243,10 +1243,6 @@ export type AlertGroup = { lines: { name?: string; error?: string; job?: string }[]; }; -/** 新库默认装的本体包。建库对话框和自动建出的第一个库都从这里取, - * 两处只能有一个答案:README 承诺的是「默认 schema.org」,不是「默认没有词表」 */ -export const DEFAULT_ONTOLOGY_PACKS = ["schema-org"]; - export const api = { health: () => request<{ status: string; name: string; version: string }>( diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 311c0b007..375cc8910 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1064,7 +1064,7 @@ export const en = { packsLabel: "Bundled ontologies", packsHint: "Optional, and more can be imported later.", packsPick: "Search packs…", - packsNone: "None — start from the ten seed relations", + packsNone: "None — the ontology grows out of the documents", packsCount: (c: number, p: number) => `${c} classes · ${p} properties`, name: "Name", description: "Description", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 2dea0b0d7..7581bda38 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -949,7 +949,7 @@ export const zh: Strings = { packsLabel: "预置本体", packsHint: "可不选,之后还能再导入。", packsPick: "搜索本体包…", - packsNone: "不选 —— 从十个种子关系起步", + packsNone: "不选 —— 本体从文档里长出来", packsCount: (c: number, p: number) => `${c} 个类 · ${p} 个属性`, name: "名称", description: "描述", diff --git a/web/src/kb.tsx b/web/src/kb.tsx index fa560480a..594420e73 100644 --- a/web/src/kb.tsx +++ b/web/src/kb.tsx @@ -3,7 +3,7 @@ import { useCallback, useSyncExternalStore } from "react"; import { useLocation, useNavigate, useParams } from "@tanstack/react-router"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { STREAM_KEYS } from "./queryDefaults"; -import { api, DEFAULT_ONTOLOGY_PACKS, type Kb, type Workspace } from "./api"; +import { api, type Kb, type Workspace } from "./api"; import { kbStore, wsStore } from "./wsStore"; /** 当前路径里的知识库 id。**页面都在 /kb/$kbId 之下,所以直接从路径取**—— @@ -55,13 +55,13 @@ export function useKb(): { if (existing.length > 0) return existing; // 空工作区自动创建 General——建库现在是管理员动作,非管理员会 403: // 静默等管理员来创建(现实中首个用户即管理员,General 总在)。 - // **带默认本体包。** 从前这里不传 packs,于是一个新部署的第一个库一个类 - // 都没有,第一批文档抽出来全是未分类实体;建库对话框里那个"默认 schema.org" - // 只对第二个库起效。第一个库恰恰是大多数人唯一会用的那个 + // **空着起步,不带包**(#580)。从前这里装 schema.org,怕第一批文档抽出来全是 + // 未分类实体;量过之后(#580 的对照)本体从文档里自己长出来,装包换来的类型 + // 准确率十个点,代价是每块提示词 18k 对 2k。要包的人在建库对话框里选 try { const created = await api.createKb(ws!.id, { name: "General", - ontology_packs: DEFAULT_ONTOLOGY_PACKS, + ontology_packs: [], }); queryClient.invalidateQueries({ queryKey: ["kbs", ws!.id] }); return [created]; diff --git a/web/src/pages/MyKbs.tsx b/web/src/pages/MyKbs.tsx index fd036daec..c73775444 100644 --- a/web/src/pages/MyKbs.tsx +++ b/web/src/pages/MyKbs.tsx @@ -6,7 +6,7 @@ import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useNavigate, useSearch } from "@tanstack/react-router"; import { Lock, Plus, Search } from "lucide-react"; -import { api, DEFAULT_ONTOLOGY_PACKS, type MyKb } from "../api"; +import { api, type MyKb } from "../api"; import { S } from "../i18n"; import { useKb } from "../kb"; import { @@ -192,10 +192,10 @@ function NewKbModal({ const [name, setName] = useState(""); const [desc, setDesc] = useState(""); const [restricted, setRestricted] = useState(true); - // schema.org 默认勾选,可反选(0009)。删掉内置类之后不选任何包的库是真的空, - // 而空库仍然能用——但绝大多数人要的是一个已经能认出人、组织、产品的起点。 - // 一秒装完(0008 的批量插入),所以默认装得起 - const [packs, setPacks] = useState([...DEFAULT_ONTOLOGY_PACKS]); + // 默认一个包都不勾(#580)。从前预勾 schema.org,理由是"要一个已经能认出人、 + // 组织、产品的起点";量过之后本体从文档里长出来的是库自己的十几个类,而 915 个 + // 类的包让每块抽取提示词从 2k 涨到 18k tokens。要包的人在这里勾 + const [packs, setPacks] = useState([]); const available = useQuery({ queryKey: ["ontologyPacks"],