# GEO Evidence Kit 1.0.0

把 AI 回答里的实际正文引用、搜索候选和品牌提及分开保存；按实际页面 URL 汇总；准备并可选执行相互独立的 OpenAI Responses API 请求。GPTFav 原创，MIT 许可。

这是一套本地 Python 工具，不是网站评分服务。它不保证被 AI 推荐，不自动抓取 ChatGPT 网页，也不会证明引用页面支持了回答中的事实。

## 下载与要求

- [geo_evidence.py](https://gptfav.com/downloads/geo-evidence/geo_evidence.py)：唯一必需的运行文件。
- [example.json](https://gptfav.com/downloads/geo-evidence/example.json)：**人工构造的测试示例，不是任何真实模型回答**。
- [SKILL.md](https://gptfav.com/downloads/geo-evidence/SKILL.md)：让兼容的 coding agent 使用本地工具的说明。
- [LICENSE](https://gptfav.com/downloads/geo-evidence/LICENSE)：MIT 许可证；再分发时保留。

需要 Python 3.10+，只用标准库，无 pip 依赖。将上述文件保存到同一独立目录，在该目录运行。输入文件最大 16 MiB；输出父目录必须存在，输出文件名必须尚未使用。输出按仅当前用户可读写的方式创建。工具不扫描目录，也不会读取你的其他聊天记录。

## 1. 提取真实正文引用

先用示例练习文件流程：

```sh
python3 geo_evidence.py extract example.json --output evidence-example.json --mention-term GPTFav
python3 geo_evidence.py report evidence-example.json --output report-example.json
```

示例会产生一个 `https://example.com/guide` 正文引用、一个独立搜索候选，以及一个没有引用注释的 URL 提及。示例数据没有研究效力。

对真实 Responses API 结果，将完整的**单个 Response JSON**保存为 `response.json`：

```sh
python3 geo_evidence.py extract response.json --output evidence.json --mention-term 你的品牌
```

工具只将 assistant 的 `output_text.annotations` 中 `type: "url_citation"` 的合法记录视为正文引用，保存：

- 原始 URL、页面 URL、标题、原始注释；
- 正文文本块、起止位置和该位置的文本片段；
- 原始完整 Response、响应 ID、模型和响应完成状态；
- 搜索候选、未注释 URL 提及、字面品牌提及、无效注释及警告。

`web_search_call.action.sources` 进入 `candidates`。一个候选可以同时是正文引用，但候选本身不构成引用证据。纯正文 URL 没有对应注释时进入 `mentions`。品牌字符串命中只表示提及，不表示推荐、正面评价或引用。

**位置解释：**工具按 Python Unicode 码点解释 API 的 `start_index` / `end_index`，同时保存原始位置和完整正文。不同导出工具可能使用 UTF-16 或字节位置；包含 emoji 等非 BMP 字符时要对照原回答核验，不得只凭截取片段认定位置准确。范围非法的注释会进入 `rejected`。

不支持 Chat Completions 的 `choices`、流式事件列表、任意网页 HTML 或任意 ChatGPTExporter JSON。遇到不支持的格式会报错，不从不明字段猜出引用。

## 2. 人工核对 ChatGPT 网页回答

如果研究对象是 **ChatGPT 网页上的独立新聊天**，应在网页中实际完成每次聊天，保存完整问题、日期、模型/模式信息、回答、可查看的引用和截图或导出文件。本工具不会代你创建这些聊天。

逐个检查原回答中实际出现的正文引用后，可手工转换成下面的明确格式。只有末尾来源列表时，将其放入 `candidates`；不要补造正文引用位置。

```json
{
  "schema": "gptfav.manual-citations.v1",
  "response_id": "manual-study-q001",
  "question": "填写这一次独立聊天的实际问题",
  "model": "填写网页实际显示的模型与模式；未知则写 unknown",
  "offset_unit": "unicode_codepoints",
  "answer_text": "资料见此处。",
  "citations": [
    {
      "start_index": 2,
      "end_index": 5,
      "url": "https://example.com/guide",
      "title": "手工核对的来源标题"
    }
  ],
  "candidates": [
    { "url": "https://example.com/another-page", "title": "只有来源列表证据的页面" }
  ],
  "page_types": {
    "https://example.com/guide": "reference"
  },
  "review": {
    "confirmed": true,
    "reviewed_by": "真实核对者",
    "reviewed_at": "2026-10-15T10:00:00Z",
    "evidence_reference": "保存在你本地的原回答导出或截图路径"
  }
}
```

这里的短句与 URL 都是格式示例。实际使用时，用真实回答及实际引用位置替换。`start_index` 包含起点，`end_index` 不包含终点；必须覆盖原回答中能对应到该引用的一段非空文本，不能将整篇回答随意填作每个引用的跨度。工具要求声明核对者、时间和证据位置，但不会访问或认证该证据，人工填写本身不是独立验证。

保存为 `manual.json` 后可用同一个 `extract` / `report` 命令。报告将来源标为 `human-reviewed-manual`，与 `openai-responses` 保持区别。工具不声称自动适配任何浏览器扩展导出格式。

## 3. 按页面汇总，不把域名数量当引用页面

```sh
python3 geo_evidence.py report evidence.json manual.json --output report.json
```

`cited_pages` 以实际页面 URL 分组：保留路径和查询参数，移除 `#fragment`，规范化域名大小写和默认端口。不同路径仍是不同页面；查询参数不做跟踪参数清洗，带参数的 URL 可能仍指向同一内容，需人工判断。每页保留每一次引用的回答文本及注释证据。

`search_candidates`、`mentions`、`rejected` 分开列出。报告重读保存的原始响应，不信任被手工改写的派生引用数量；原始 JSON 自身仍可被编辑，因此这不是防篡改证据系统。运行文件中的每条引用保留题目 ID、原始问题、请求模型、响应模型、运行与请求时间，单独的手工导出可用 `question` 保存问题。

`citation_occurrences` 是注释出现次数，不是独立回答次数、排名、曝光或成功率。重复导出会重复计数。工具不抓取引用页面，也不自动判断来源类别。默认 `page_type` 为 `unclassified`。手工导出可选填 `page_types`：以页面 URL 映射到 `brand`、`retailer`、`forum`、`video`、`reference`、`other` 或 `unclassified`。这只是核对者提供的标签，标为 `human-review`；不是工具验证结果。同页存在不同标签时，汇总保持 `unclassified` 并保留全部 `page_type_labels` 和逐次证据供复核。

## 4. 准备相互独立的 API 问题

创建 `questions.json`：

```json
{
  "model": "填写你有权限使用的实际模型ID",
  "questions": [
    "小型跨境团队如何选择适合的发票工具？",
    "评估订阅账单软件时需要比较哪些真实证据？"
  ],
  "excluded_brand_terms": ["你的品牌名", "你的品牌别名"],
  "web_search": true,
  "max_output_tokens": 2000
}
```

`model` 需要替换为实际的英文 API 模型 ID。工具不自动选模型、不检查你是否有访问权限，也不核实该模型当前是否支持 `web_search` 或所设 token 参数。API 模型支持和价格请在执行前查官方资料。

```sh
python3 geo_evidence.py plan questions.json --output plan.json
python3 geo_evidence.py run plan.json --output dry-run.json
```

第一条命令在本地检查品牌及别名是否泄露到问题里，第二条只保存待执行请求，**不会联网**。查看 `plan.json` 的 `requests`，确认每题只有该题的 `input`。品牌表只在本地验证配置中保留，不发送给 API。

每题构造一个单独的 Responses 请求，不传 `previous_response_id`、`conversation`、消息历史或上一题回答，设置 `store: false`。这不能保证服务商无任何留存，也不保证不同生成结果在统计意义上独立；它仅防止此客户端主动串联会话。

泄漏检查做 Unicode NFKC、大小写及空白/标点归一化，能捕获所列字面别名的一些变体；不能发现未列出的别名、间接暗示或语义泄漏。规范化后相同的问题会被拒绝。默认执行上限 20 题，配置最多 100 题，每题输出 token 上限可设 16–10000。

## 5. 可选：显式执行 API 请求

只有你确认发送内容和成本后才执行。通过你已批准的本地密钥管理方式设置 `OPENAI_API_KEY` 环境变量；不要把密钥写入问题、计划文件、命令历史或聊天。

```sh
python3 geo_evidence.py run plan.json --output run.json --execute
python3 geo_evidence.py report run.json --output run-report.json
```

`run` 默认 dry-run，只有 `--execute` 才联网。实际执行只向 `https://api.openai.com/v1/responses` 发送，拒绝 HTTP 重定向，逐题顺序运行，不自动重试。超过 20 题需显式设置 `--max-requests N`；题数与 token 上限不等于确定金额预算，搜索工具等仍可能收费。

执行记录每题的 payload、原始响应、提取结果或失败类型；每题结束写入一次结果。HTTP 失败只保存状态码与异常类型，不保存错误响应正文、授权头或异常消息。工具不主动抓取答案里给出的 URL。原始 API 回答可能包含个人或业务数据，请按自己的数据要求保管输出文件。

`report run.json` 保留运行的 planned、attempted、completed_recorded、failed_recorded、unattempted，以及无原始响应的失败数。失败题不会悄悄从分母消失；中断时最后一个尚未写入的请求状态可能未知。dry-run 文件会被拒绝作为引用证据。报告不计算引用成功率，也不把 API 测试描述为 ChatGPT 网页测试。

## 验证范围

1.0.0 在项目仓库运行了 21 项离线测试：来源角色隔离、页面分组、非法 URL/位置、不支持的输入、人工核对格式与来源标签、中文/emoji 位置提示、品牌泄漏、请求隔离、计划篡改、dry-run、执行前密钥检查、模拟成功/失败记录及文件覆盖保护。

测试中的网络入口被禁止，执行测试使用模拟传输。**没有真实 API 调用、没有 ChatGPT 网页新聊天、没有 GEO 效果试验。** 真实模型兼容性、账户权限、费用、答案质量与引用准确性均须实际执行后再验证。

## 协议依据

- [OpenAI Web search guide](https://developers.openai.com/api/docs/guides/tools-web-search)：`url_citation` 注释、`web_search_call` 与引用展示要求。若在网页显示这些结果，应给引用提供清晰可点击链接。
- [OpenAI Conversation state guide](https://developers.openai.com/api/docs/guides/conversation-state)：手动提供历史以及 `previous_response_id` 的会话连接方式。

文档读取日期：2026-10-15。公开 API 可能变化；遇到不支持的格式，保留原始文件后更新解析器，不要把猜测结果计入研究。
