ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GEO测试数据怎么存?从 Query、Run、Entity 到 Citation 的数据库建模

GEO测试数据怎么存?从 Query、Run、Entity 到 Citation 的数据库建模 企业开始长期做 GEOGenerative Engine Optimization生成式引擎优化测试后真正棘手的问题往往不是怎么再多测几个AI平台而是测完以后这些数据到底应该怎么存很多项目第一版都是 Excel问题 平台 时间 企业是否出现 AI回答 来源广州有哪些GEO服务公司 某AI平台 2026-08-15 是 …… example.com几十条数据时没有问题。但测试一旦进入第二轮、第三轮甚至形成长期监测很快会遇到一系列工程问题同一个问题测试20次怎么区分每一次执行一个问题只改了几个字还能不能和上一轮直接比较同一个平台的Web和App是不是同一种测试环境平台公开显示的模型发生变化历史数据怎么保留一次提问重新生成了3次回答应该算一个Run还是三个RunAI提到了企业简称但到底是不是目标主体一个回答显示5个来源怎么建立一对多关系一个来源只支持回答中的一句话怎么表示人工复核过的数据以后规则发生变化能不能重新计算如果这些问题没有提前设计好后面再写多少 Python 统计代码都只是建立在不稳定的数据基础上。所以一个真正可长期运行的 GEO 数据系统至少应该解决三件事数据能不能完整保存判断能不能追溯不同阶段结果能不能可靠比较。本文从数据库建模角度设计一套适合 GEO 测试、复测、来源核验和后续统计分析的数据结构。一、先明确一次GEO测试到底包含哪些实体最常见的第一版表结构通常类似问题平台测试时间回答是否出现企业地区是否正确业务是否正确来源1来源2来源3备注这张表的问题不是字段太少。而是它把不同生命周期、不同基数的数据对象塞进了同一行。一次完整测试至少涉及这些实体ProjectQuery IntentQuery VersionPlatformTest RunResponseEntityEntity MentionSource DocumentCitationClaimVerification它们之间并不是简单的一对一关系。更接近PROJECT│├── QUERY_INTENT│ ││ └── QUERY_VERSION│ ││ └── TEST_RUN ───── PLATFORM│ ││ └── RESPONSE│ ││ ┌─────────────┼─────────────┐│ │ │ ││ ▼ ▼ ▼│ ENTITY_MENTION CLAIM CITATION│ │ │ ││ ▼ │ ▼│ ENTITY │ SOURCE_DOCUMENT│ ││ └──── CLAIM_SUPPORT│└── VERIFICATION / ANALYTICS这个关系图先解决一个核心认知GEO测试不是“一行结果”而是一组相互关联的事件和实体。二、不要直接拿问题文本做主键假设第一轮问题是广州有哪些GEO服务公司第二轮有人把它改成广州有哪些做GEO优化的公司第三轮又变成广州有哪些GEO优化服务商三句话的搜索意图接近但文本已经发生变化。如果直接使用query_text作为问题唯一标识就会出现两个极端。一种是把三句话错误地当成完全相同的问题。另一种是把它们拆成三个毫无关联的问题。更合理的设计应该区分问题意图和问题具体版本。因此建议拆成两张表。CREATE TABLE query_intent (query_intent_id TEXT PRIMARY KEY,project_id TEXT NOT NULL,intent_name TEXT NOT NULL,query_type TEXT,business_line TEXT,region TEXT,priority INTEGER DEFAULT 0,created_at TEXT NOT NULL);然后单独保存版本CREATE TABLE query_version (query_version_id TEXT PRIMARY KEY,query_intent_id TEXT NOT NULL,version_no INTEGER NOT NULL,query_text TEXT NOT NULL,is_active INTEGER NOT NULL DEFAULT 1CHECK (is_active IN (0, 1)),created_at TEXT NOT NULL,FOREIGN KEY (query_intent_id) REFERENCES query_intent(query_intent_id), UNIQUE (query_intent_id, version_no));例如query_intent_id QI001intent_name 广州GEO服务商推荐QV001version_no 1query_text 广州有哪些GEO服务公司QV002version_no 2query_text 广州有哪些GEO优化服务商这样既能知道两个问题属于同一个测试意图。又不会丢失每一次正式测试究竟用了哪个文本版本。这对于阶段复测尤其重要。三、问题分类是分析维度不是行业标准问题库规模扩大以后通常需要给问题分类。例如region_servicebrandbusinessscenariocomparisonpurchase_decision这些字段可以放在 query_intent 中。它们的作用不是证明GEO必须使用某套统一的问题分类。而是方便后续统计地区类问题表现如何具体业务问题和品牌问题差多少哪条业务线的主体关联更弱所以这里有一个数据库设计原则分类应该服务分析不要让分类反过来绑死数据模型。如果未来分类体系变化最好调整映射层而不是破坏历史测试记录。四、Platform只保存稳定身份测试环境必须进入Run上一版最容易出错的设计之一是把model_label直接放在平台表。问题在于平台可能会变化。同一个AI产品7月公开显示模型A8月切换到模型BWeb和App可能也不是完全相同的入口。如果直接覆盖platform.model_label历史记录就可能错误地继承新值。因此 platform 更适合只保存相对稳定的信息CREATE TABLE platform (platform_id TEXT PRIMARY KEY,platform_name TEXT NOT NULL,provider_name TEXT,notes TEXT);真正和“当次测试环境”有关的数据放入 test_runsurfacemodel_labellogin_stateapp_versiontested_at换句话说Platform 是“在哪里测”。Run Environment 是“当时在什么条件下测”。这是两个不同概念。五、Test Run应该表示一次实验执行test_run 是整个 GEO 复测体系中最核心的表之一。推荐结构CREATE TABLE test_run (run_id TEXT PRIMARY KEY,query_version_id TEXT NOT NULL,platform_id TEXT NOT NULL,surface TEXT, model_label TEXT, login_state TEXT, app_version TEXT, test_round INTEGER, tested_at TEXT NOT NULL, operator TEXT, environment_json TEXT, FOREIGN KEY (query_version_id) REFERENCES query_version(query_version_id), FOREIGN KEY (platform_id) REFERENCES platform(platform_id));例如run_id RUN_20260815_000001query_version_id QV001platform_id P01surface Webmodel_label 某平台当时公开显示的模型login_state logged_intest_round 2tested_at 2026-08-15T15:32:1808:00这时候query_intent_id表示测试意图query_version_id表示实际问了什么platform_id表示在哪个平台run_id表示这是哪一次实验执行。六、一次Run不一定只有一个Response这是长期做测试时非常容易忽略的一点。假设用户问同一个问题以后第一次生成一个回答点击“重新生成”又得到第二个回答第三次再次重新生成。这三条回答属于同一个实验条件。但它们又不是同一个生成结果。所以 test_run 和 response 最好设计成一对多。CREATE TABLE response (response_id TEXT PRIMARY KEY,run_id TEXT NOT NULL,attempt_no INTEGER NOT NULL DEFAULT 1,raw_text TEXT NOT NULL, raw_payload TEXT, response_status TEXT, captured_at TEXT NOT NULL, screenshot_path TEXT, content_hash TEXT, FOREIGN KEY (run_id) REFERENCES test_run(run_id), UNIQUE (run_id, attempt_no));于是RUN001├── RESPONSE001 attempt_no 1├── RESPONSE002 attempt_no 2└── RESPONSE003 attempt_no 3后续就可以明确区分同一次测试条件下的生成波动和不同时间、不同轮次的阶段变化。这对于分析生成随机性非常重要。七、原始回答应该视为“不可变数据”GEO数据系统里最应该保护的不是统计结果。而是原始观测。例如raw_textraw_payloadscreenshotcaptured_at一旦正式归档就不应该因为后续人工觉得“这句话没用”而修改原文。更稳妥的工程原则是Raw Data Immutable也就是原始层只追加不覆盖。如果后续发现主体判断错误地区判断需要修改来源核验有误应该修改的是verification而不是response.raw_text为了进一步检查原始数据是否被修改还可以保存content_hash例如对回答正文计算 SHA-256。这样以后能够验证当前归档内容是否仍然与最初采集内容一致。八、企业主体不能只设计成一个关键词很多系统最初会直接使用if “某公司” in response:mentioned True这种方法可以做最早期原型但无法承担正式数据判断。因为一家企业可能同时存在公司全称品牌简称旧名称同名主体近似名称。所以首先需要建立entity表。CREATE TABLE entity (entity_id TEXT PRIMARY KEY,legal_name TEXT,brand_name TEXT,entity_type TEXT,region TEXT,is_target INTEGER NOT NULL DEFAULT 0CHECK (is_target IN (0, 1)),created_at TEXT NOT NULL);别名不建议长期塞在aliases_json如果需要规范管理更适合单独拆表CREATE TABLE entity_alias (alias_id TEXT PRIMARY KEY,entity_id TEXT NOT NULL,alias_text TEXT NOT NULL,alias_type TEXT,is_active INTEGER NOT NULL DEFAULT 1CHECK (is_active IN (0, 1)),FOREIGN KEY (entity_id) REFERENCES entity(entity_id), UNIQUE (entity_id, alias_text));这样后续增加别名停用旧别名统计不同名称出现频率都会更容易处理。九、Mention负责保存“模型原文到底写了什么”实体表记录的是我们认定的企业是谁。但模型原文出现的是某段文本。两者必须分开。建议建立CREATE TABLE entity_mention (mention_id TEXT PRIMARY KEY,response_id TEXT NOT NULL,mention_text TEXT NOT NULL,start_offset INTEGER, end_offset INTEGER, candidate_entity_id TEXT, match_status TEXT NOT NULL CHECK ( match_status IN ( exact, alias_confirmed, ambiguous, wrong_entity, unverified ) ), FOREIGN KEY (response_id) REFERENCES response(response_id), FOREIGN KEY (candidate_entity_id) REFERENCES entity(entity_id));例如AI写ABC科技数据库保存mention_text ABC科技人工确认它对应candidate_entity_id E001同时match_status alias_confirmed这比直接保存entity_id E001更完整。因为你永远保留了模型实际输出文本。十、主体命中、业务匹配、地区正确必须拆开企业名称出现并不等于回答质量高。例如回答ABC科技是一家位于深圳的网站建设公司。实际目标企业虽然叫ABC科技但地区是广州主营业务也不是网站建设。这时至少应该产生三类判断维度 结果主体身份 correct地区信息 incorrect业务信息 incorrect因此不要把所有信息压成hit true更专业的模型可以建立verification表。CREATE TABLE verification (verification_id TEXT PRIMARY KEY,response_id TEXT NOT NULL,mention_id TEXT,verification_type TEXT NOT NULL, result TEXT NOT NULL, reason TEXT, reviewer TEXT, reviewed_at TEXT NOT NULL, FOREIGN KEY (response_id) REFERENCES response(response_id), FOREIGN KEY (mention_id) REFERENCES entity_mention(mention_id));例如verification_type entity_identityresult correct或者verification_type business_matchresult partial再或者verification_type region_matchresult incorrect这样主体提及主体准确业务匹配地区准确就成为不同指标。十一、Source Document和Citation必须分开这也是 GEO 来源数据里非常重要的一层。假设https://example.com/about在100次AI回答中被展示过。这个URL本身是一个来源文档。但它在每次回答里出现是一次引用展示事件。所以更标准的设计应该拆成source_document和response_citation先保存文档CREATE TABLE source_document (document_id TEXT PRIMARY KEY,canonical_url TEXT NOT NULL,domain TEXT,title TEXT,source_type TEXT,first_seen_at TEXT,last_seen_at TEXT,UNIQUE (canonical_url));然后保存某次回答中的展示关系CREATE TABLE response_citation (citation_id TEXT PRIMARY KEY,response_id TEXT NOT NULL,document_id TEXT NOT NULL,citation_order INTEGER, displayed_title TEXT, displayed_url TEXT, displayed_text TEXT, captured_at TEXT NOT NULL, FOREIGN KEY (response_id) REFERENCES response(response_id), FOREIGN KEY (document_id) REFERENCES source_document(document_id));这样Document A只存一次。但可以对应Citation 001Citation 017Citation 163…这才符合数据库规范化设计。十二、为什么只能叫Citation不能随便叫Retrieval Source这一层名称尤其重要。如果最终界面显示来源example.com我们能够确认的是这个来源被展示给用户了。所以可以叫citationvisible_sourcedisplayed_source但不能仅凭最终界面直接命名成retrieved_documentmodel_contextretrieval_source因为这些词隐含了我们已经知道模型检索了什么候选集包含什么哪些文档进入上下文。实际往往没有这些内部信息。因此citation_count 0只能表示当前回答没有观察到可见引用。不能推出内部没有发生检索。这是数据建模中的“观测边界”。数据库字段名称应该和你实际能够证明的事实一致。十三、Citation还不够需要继续拆Claim假设AI回答A公司成立于2018年主要提供GEO服务累计服务超过500家企业。这里实际上包含至少三个独立事实Claim 1A公司成立于2018年Claim 2A公司主要提供GEO服务Claim 3A公司累计服务超过500家企业即使回答展示了example.com/about也不能直接认为这个来源支持全部三句话。所以如果项目需要做高质量来源核验可以继续拆CREATE TABLE response_claim (claim_id TEXT PRIMARY KEY,response_id TEXT NOT NULL,claim_text TEXT NOT NULL,start_offset INTEGER,end_offset INTEGER,claim_type TEXT,FOREIGN KEY (response_id) REFERENCES response(response_id));于是Response↓Claim成为明确关系。十四、建立Claim与Citation的证据关系接下来增加claim_supportCREATE TABLE claim_support (support_id TEXT PRIMARY KEY,claim_id TEXT NOT NULL,citation_id TEXT NOT NULL,support_status TEXT NOT NULL CHECK ( support_status IN ( supported, partially_supported, not_supported, unable_to_verify ) ), evidence_text TEXT, reviewer TEXT, reviewed_at TEXT, FOREIGN KEY (claim_id) REFERENCES response_claim(claim_id), FOREIGN KEY (citation_id) REFERENCES response_citation(citation_id), UNIQUE (claim_id, citation_id));于是整个证据链就变成Response↓Claim↓Claim Support↓Citation↓Source Document这套结构能够真正回答AI回答中的哪一句话是被哪个可见来源实际支持的而不是只统计“这个回答有5个来源。”两种分析价值完全不同。十五、原始层、核验层和分析层必须彻底分开到这里整个数据系统可以明确分成三层。数据层 保存内容 是否允许人工判断Raw Layer Query、Run、Response、Citation 否Verification Layer Entity Match、Claim Support、人工核验 是Analytics Layer 提及率、准确率、业务匹配率等 由规则计算Raw Layer负责当时到底发生了什么。Verification Layer负责我们后来如何判断。Analytics Layer负责一批数据最终计算出了什么。这种分层非常重要。例如主体提及率 40%不应该写回每一条 response。因为40%不是原始事实。它是一组记录按照当前判定规则计算出的聚合结果。只要底层数据和核验结果还在指标应该可以随时重新计算。十六、完整SQLite Schema应该补上约束和索引为了突出关系很多教程代码会省略数据库约束。但真正进入工程实现以后至少应该考虑PRAGMA foreign_keys ON;否则 SQLite 默认环境下外键约束可能并没有真正生效。还应该给高频查询字段加索引。例如CREATE INDEX idx_query_version_intentON query_version(query_intent_id);CREATE INDEX idx_test_run_queryON test_run(query_version_id);CREATE INDEX idx_test_run_platform_timeON test_run(platform_id, tested_at);CREATE INDEX idx_response_runON response(run_id);CREATE INDEX idx_mention_responseON entity_mention(response_id);CREATE INDEX idx_citation_responseON response_citation(response_id);CREATE INDEX idx_citation_documentON response_citation(document_id);CREATE INDEX idx_claim_responseON response_claim(response_id);这类索引解决的是后续很常见的查询某个问题所有历史Run某个平台某时间段测试某条Response出现了哪些主体哪个Domain被展示次数最多某个来源支持了哪些Claim。当数据从几百条增长到几十万条以后这些设计就会开始产生明显区别。十七、还需要考虑幂等写入和重复采集真实自动化采集系统还会遇到一个问题同一次结果因为重试被写入两遍怎么办这属于幂等性问题。可以结合run_idattempt_nocontent_hashcaptured_at进行判断。例如UNIQUE (run_id, attempt_no)保证一次Run不会出现两个相同尝试编号。同时content_hash可以辅助检测完全相同的重复响应。对于来源文档则使用UNIQUE (canonical_url)避免相同页面被不断重复创建成新的Document。注意去重规则不能只靠URL字符串。因为真实网站还可能存在UTM参数锚点HTTP/HTTPS尾部 /移动端参数。因此进入规模化采集以后最好增加URL canonicalization。这已经属于正式采集系统的数据工程问题。十八、数据可追溯性比最终百分比更重要一个专业的 GEO 数据系统最终应该支持从指标一直追到原始证据。例如看到某平台主体提及率 40%应该能够继续追到40%↓哪些Response被统计为命中↓对应哪些Entity Mention↓为什么判定为正确主体↓对应哪个Run↓测试时用了哪个Query Version↓当时平台、入口、模型标签是什么↓完整AI原始回答是什么↓当时展示了哪些Citation↓哪些Claim获得了哪些来源支持这就是Data Lineage。或者更直接说数据血缘。如果系统最后只剩平台A40%平台B25%平台C15%却无法回到原始回答和判定依据那么这个统计结果的诊断价值会大幅下降。GEO测试真正需要建设的是可复核的数据链。而不只是一张结果表。十九、CSV、SQLite、PostgreSQL怎么选项目早期并不需要一开始就上复杂数据库。阶段 更适合 原因方法验证 CSV / Excel 简单、业务人员易用本地技术化测试 SQLite 支持SQL、无需服务器、适合Python多项目长期系统 PostgreSQL 并发、权限、复杂查询和在线服务更强SQLite很适合第一套可运行的GEO数据系统。PostgreSQL更适合系统开始出现多人协作API写入任务调度多个客户权限隔离Web管理后台大量并发查询以后再升级。不要因为未来“可能有百万数据”第一天就把系统设计成分布式数据平台。数据库设计专业与否不等于基础设施越复杂越好。二十、最终推荐的数据链经过上面的拆分一套相对完整的 GEO 数据体系可以整理成Project↓Query Intent↓Query Version↓Test Run↓Response├── Entity Mention → Entity├── Claim└── Citation → Source Document│└── Claim Support↓Verification↓Analytics其中最关键的几个设计原则可以压缩成这张表原则 工程意义问题意图和问题版本分开 保证复测可比性每次测试有唯一Run 保存实验执行历史一个Run允许多个Response 记录生成波动原始回答不可覆盖 保证原始事实完整Entity与Mention分离 支持别名、歧义和误匹配Document与Citation分离 正确表达一对多引用关系Claim与Citation建立支持关系 判断来源真正支持什么Raw / Verification / Analytics分层 防止原始数据与判断污染加外键、约束和索引 保证数据完整性与查询效率所有指标必须能回溯 保证数据可复核结语GEO测试真正进入工程化以后问题已经不再只是“这个平台有没有提到企业”而是要能够回答问了什么用的是哪个问题版本在什么平台、入口和环境下测试这是第几次执行一次执行生成了几个回答AI原始回答到底是什么出现的名称是不是正确主体哪些业务和地区信息是准确的页面显示了哪些来源哪个来源真正支持了回答里的哪项事实最终统计指标能不能一路回到这些原始数据所以一个真正适合长期GEO测试的数据模型不应该只有问题 平台 是否出现而应该逐渐形成Query→ Run→ Response→ Entity / Claim / Citation→ Verification→ Analytics当这套数据链建立以后后面无论做多平台提及率统计主体准确率业务匹配率来源Domain分析阶段复测异常检测Python自动报表甚至进一步构建GEO测试后台才真正拥有稳定的数据基础。下一篇再基于这套Schema进入分析层《用Python统计多平台GEO测试结果提及率、准确率和业务命中率怎么计算》到那一步就不再只讨论指标定义而是直接从 Query → Run → Response → Verification 这套数据模型生成可复现的统计结果。
返回列表