# geoAutoPublish **Repository Path**: hackerSnail/geo-auto-publish ## Basic Information - **Project Name**: geoAutoPublish - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-11-09 - **Last Updated**: 2026-11-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # GEO AI 内容创作与多平台分发系统 面向**单个运营者**的 GEO(Generative Engine Optimization)内容生产与分发工具: `品牌资料/关键词 → 写作与来源核验 → 人工编辑审核 → 平台适配 → 授权草稿/人工发布 → 真实 URL 与效果记录`。 核心原则:**内容质量和可追溯事实高于发文数量**。GEO 的“被 AI 提及/引用”只能通过真实观测验证, 系统不会承诺效果,也不会伪造指标。 当前状态:**P0 + P1 + P2 + P3 + 公众号真实联调**。品牌库、mock / 本地 Ollama 写作流程、事实核查、 人工改稿与审批、来源核验、三平台适配、导出包、分发台账与效果台账均已可用; **公众号草稿接口已真实联调通过(只建草稿,不发布)**,百家号/小红书仍为纯人工路径。 进度见 [`docs/IMPLEMENTATION_PLAN.md`](docs/IMPLEMENTATION_PLAN.md)。 --- ## 1. 环境要求 - Python >= 3.11(开发环境实测 3.15.2) - 无需数据库服务:默认 SQLite 单文件 - 可选:本机安装 [Ollama](https://ollama.com/)(本地模型,数据不出本机) ## 2. 安装 ```powershell # Windows PowerShell cd <项目目录> python -m venv .venv .\.venv\Scripts\python.exe -m pip install --upgrade pip .\.venv\Scripts\python.exe -m pip install -e ".[dev]" ``` macOS / Linux: ```bash python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install -e ".[dev]" ``` ## 3. 配置 ```powershell copy .env.example .env # Windows # cp .env.example .env # macOS / Linux ``` `.env` 已在 `.gitignore` 中,**不要提交**。默认配置即可直接运行(`LLM_PROVIDER=mock`,不需要任何 Key)。 关键开关见 `.env.example`: | 变量 | 默认值 | 说明 | | --- | --- | --- | | `LLM_PROVIDER` | `mock` | `mock` / `ollama`;其他取值会明确报错(不静默降级) | | `LLM_API_KEY` | 空 | 供应商正式 API Key,禁止用会话令牌代替 | | `OLLAMA_BASE_URL` | `http://127.0.0.1:11434` | 本地模型服务地址 | | `OLLAMA_MODEL` | 空 | 本机已安装的模型名(先 `ollama list` 查看) | | `OLLAMA_THINK` | `false` | 思考型模型必须关,否则正文被思考内容占满而变空 | | `WECHAT_APP_ID` / `WECHAT_APP_SECRET` | 空 | 微信公众号凭证,只放环境变量或密钥服务 | | `WECHAT_DRAFT_ENABLED` | `false` | 允许调用“新建草稿”接口(还需账号具备该权限) | | `WECHAT_PUBLISH_ENABLED` | `false` | 终局发布开关,默认关闭 | | `REAL_PLATFORM_WRITES_ENABLED` | `false` | 全局真实写开关,单独打开**不会**触发任何对外写入 | ## 4. 初始化数据库与启动 ```powershell .\.venv\Scripts\python.exe -m alembic upgrade head .\.venv\Scripts\python.exe -m uvicorn app.main:app --reload --port 8000 ``` 打开: - 管理界面: - API 文档(Swagger): - 健康检查: ## 5. 五分钟演示(不需要任何账号凭证) 1. 打开 ,创建一个品牌档案(标准名、别名、已核实事实、**禁用表述**)。 2. 打开 ,填主问题(例如“怎样选择适合中小企业的记账工具?”),点“生成草稿”。 3. 文章详情页会显示:意图分析、证据计划、事实核查结果、GEO 结构自检、来源台账与 Markdown 正文。 4. 注意:**不填来源时系统不会编造任何引用**,正文中的断言会被标记为 `【待核实:…】`, 版本状态为 `needs_verification=true`,不能进入分发。 ### 分发台账与效果台账 - **分发台账**(`/distributions`):每个平台独立记录状态与真实链接。 状态机 `NOT_STARTED → READY → DRAFT_CREATED/MANUAL_PENDING → PUBLISHED/FAILED/UNKNOWN`; 幂等键 `version_hash + platform + account_id + operation`,重复提交不会产生第二条记录; `PUBLISHED` 必须由授权接口真实成功或人工登记链接;链接未人工核验会标注「待核实」。 - **效果台账**(`/analytics`):**已发布 / 已被搜索引擎收录 / 被 AI 提及或引用**三件事分开统计,互不推导。 观测记录只写人工真实看到的;标记「有引用」必须给出可验证链接。 ```bash curl -X POST http://127.0.0.1:8000/api/platform-accounts -H "Content-Type: application/json" \ -d '{"platform":"wechat","account_name":"主号"}' curl -X POST http://127.0.0.1:8000/api/articles//distributions -H "Content-Type: application/json" \ -d '{"platform":"wechat","account_id":"","operation":"manual_publish"}' curl http://127.0.0.1:8000/api/analytics?brand_id= ``` ### 微信公众号草稿(写操作,五道闸门) ```bash # 1) 只读权限自检(取 token + 探测接口权限,不创建任何内容) python -m app.cli check-wechat-api # 2) 查出口 IP(加到公众号后台 IP 白名单) python -m app.cli egress-ip # 3) 在界面上:文章详情 →「微信公众号草稿」→ 运行自检 → 勾选确认 → 创建草稿 ``` 创建草稿必须同时满足五个条件(代码强制,缺一不可): 凭证与 `WECHAT_DRAFT_ENABLED` 就绪 → `REAL_PLATFORM_WRITES_ENABLED` 开启 → 最近一次权限自检通过 → 文章已审批且无待核实事实 → 用户明确勾选确认。 成功后台账状态为 `DRAFT_CREATED`(**草稿 ≠ 发布**),并持久化远端 `media_id`; 重复触发不会重复创建(幂等键 = 版本哈希 + 平台 + 账号 + 操作); **超时不会自动重发**(非幂等写操作),而是置 `UNKNOWN` 等人工核实远端状态。 **正文内联图片**会在建草稿时自动转存:下载(SSRF 受控、仅图片类型、单图 ≤1MB)→ 上传微信素材库 → 替换正文 `src`。转存失败不阻断草稿,成功/失败张数写进台账 `extra.image_transfer`。 转存意味着图片会上传到微信素材库,只应处理你拥有授权的图片。 ### 平台适配(本地适配已实现,真实账号能力未验证) 所有渠道统一走 `PlatformAdapter`:**只有适配器自己 `capabilities()` 宣告支持的操作才会执行**。 | 平台 | 本地产物 | 校验 | | --- | --- | --- | | 微信公众号 | `variants/wechat.html`(白名单清洗后的 HTML) | 标题 ≤64 字、正文图片需转存素材库、AI 标识、封面比例提示 | | 百家号 | `variants/baijiahao.md` + 发布核对清单 | 标题 ≤30 字、摘要 ≤120 字、AI 标识、封面比例提示 | | 小红书 | `variants/xiaohongshu.md`(标题/短文案/话题/配图清单/人工待办) | 标题 ≤20 字(按标点截断并提醒)、短文案 ≤1000 字、话题 ≤10 个、配图 3:4 | ```bash curl http://127.0.0.1:8000/api/platforms # 各平台能力清单 curl -X POST http://127.0.0.1:8000/api/articles//render/wechat # 生成适配稿 + 校验问题 ``` > `can_create_draft` / `can_publish_via_authorized_api` 目前一律为 `false`: > **本系统不会发起任何真实平台写请求**,适配稿只供人工复制与发布。 > 字数与尺寸限制取自公开常见值,尚未用你的真实后台校验。 ### 抓取来源正文(SSRF 受控) 文章详情页「来源」区可抓取公开网页正文,登记为**待核验**来源;核验后才绑定 claim。 ```bash curl -X POST http://127.0.0.1:8000/api/sources/fetch \ -H "Content-Type: application/json" \ -d '{"brand_id":"notelume","url":"https://hackersnail.github.io/NoteLume/"}' ``` 防护规则(`app/services/fetching.py`):只允许 http/https、不允许 URL 内嵌凭据、只允许 80/443 端口、 解析域名后逐个检查 IP(禁止内网/回环/链路本地/保留地址)、手动处理重定向且每跳重新校验、 大小上限 + 内容类型白名单 + 超时。默认 `FETCH_ENABLED=false`,需显式开启。 ### 运维命令行 ```powershell python -m app.cli list-templates # 查看预置固定问题集模板 python -m app.cli seed-prompts --brand notelume --template notelume # 播种问题集 python -m app.cli check-wechat # 公众号草稿就绪检查(不发任何请求) python -m app.cli check-env # 配置摘要(不含密钥内容) ``` `check-wechat` 只读本地配置:检查 `.env`、`WECHAT_APP_ID` 格式、Secret 是否存在、 两个开关状态,并列出**必须由你在公众号后台确认**的两项(出口 IP 白名单、账号接口权限)。 它不会发起任何网络请求。 ### 各平台「能否直接放入草稿箱」 ```bash python -m app.cli check-platform-drafts ``` | 平台 | 结论 | 依据 | | --- | --- | --- | | 微信公众号 | ✅ 可以 | `draft/add` 已真实联调(2026-11-12),`media_id` 已落库;终局发布不实现 | | 小红书 | ⛔ 不支持 | 官方开放平台 11 个 API 分类全是电商链路,文档搜索「笔记/草稿/内容发布」零命中 | | 百家号 | 🟡 待确认 | 需开发者资质 + 账号授权 + 接口审核;本机无法核实,待你后台确认 | 百家号/小红书因此只走「适配稿 + 一键复制 + 配图清单」的人工路径,**系统不代发**。 这两个平台没有可用的官方发布接口,系统**只帮你把人工发布要用的东西一次准备齐**: - **一键复制发布包**:标题 + 摘要/正文 + 话题 + 配图清单 + 核对清单,整段粘到平台编辑器即可 (公众号还会附带清洗后的富文本 HTML,粘贴时保留格式) - **下载配图清单**:张数、比例、每张图的内容建议、版权与 AI 标识提醒 ```bash curl http://127.0.0.1:8000/api/articles//publish-kit/xiaohongshu # 复制包 JSON curl http://127.0.0.1:8000/api/articles//publish-kit/xiaohongshu/checklist.md # 配图清单 ``` 导出包里也会带上 `variants/<平台>.copy.txt` 与 `variants/<平台>.images.md`。 **系统不代发**:发布动作与责任都在你。 ### 导出素材包 文章详情页点「生成 / 更新导出包」,会写出可交付目录(默认 `data/exports/`,可用 `EXPORT_DIR` 改): ```text data/exports/// master.md # YAML 元数据 + 主稿,顶部横幅标明审批状态 sources.json # 来源 URL/访问时间/核验状态,以及每条主张的核销情况 review.json # 人审意见、修改记录、审批版本、核查结论、审计日志 assets/ # 授权素材目录(保留版权与 AI 标识) export.json # 清单:文件列表、是否可发布、密钥扫描结果 .zip # 手动发布素材包 ``` ```bash curl -X POST http://127.0.0.1:8000/api/articles//export curl http://127.0.0.1:8000/api/articles//export/download # 下载 zip ``` 两条硬约束:**未审批 / 审批已失效**的稿件会被显式标注「仅供内部审阅,不得对外发布」; 导出内容若检测到密钥会**直接拒绝导出**(不静默改写)。 ### 人工审核闭环(同一页面内完成) 文章详情页底部的「人工审核」面板支持完整闭环: 1. **核验来源**:在来源区填写该来源支持的主张编号(如 `C1,C2`)→「标记为已核验」, 对应主张的待核实状态会立刻重算消失。 2. **提交审核** → **批准**(必须逐项勾选「事实准确 / 商业表述合规 / 有真实编辑贡献 / AI 内容标识正确」)。 3. **改稿即失效**:在「修改正文」里保存任何改动 → 自动生成新版本(v+1), 旧审批立即失效、状态回到待核查,必须重新提交与批准。 4. **退回**必须填写修改意见;所有动作都写入审批历史与审计日志(`GET /api/audit-events`)。 等价 API: ```bash curl -X POST http://127.0.0.1:8000/api/brands \ -H "Content-Type: application/json" \ -d '{"id":"demo-brand","canonical_name":"示例科技","prohibited_claims":["行业第一"]}' curl -X POST http://127.0.0.1:8000/api/articles/generate \ -H "Content-Type: application/json" \ -d '{"brand_id":"demo-brand","primary_question":"怎样选择适合中小企业的记账工具?","purpose":"行业科普"}' # 改稿 → 新版本 → 旧审批失效 curl -X POST http://127.0.0.1:8000/api/articles//edit \ -H "Content-Type: application/json" \ -d '{"content_markdown":"# 修订后的正文\n\n本文由 AI 辅助撰写。\n","comment":"删除无来源段落"}' # 批准(四项勾选缺一不可) curl -X POST http://127.0.0.1:8000/api/articles//approve \ -H "Content-Type: application/json" \ -d '{"checks":{"facts_accurate":true,"claims_compliant":true,"has_human_editing":true,"ai_disclosure_ok":true}}' ``` ## 6. 使用本地 Ollama(可选,数据不出本机) ```powershell # 1) 确认本机模型 ollama list # 2) 需要时拉取模型(示例) ollama pull qwen3.5:9b # 3) 写入 .env # LLM_PROVIDER=ollama # OLLAMA_MODEL=qwen3.5:9b # OLLAMA_TIMEOUT_SECONDS=600 # 4) 重启服务后照常在界面生成文章 ``` 实测(Windows + `qwen3.5:9b`,CPU 推理):完整写作流程 4 次模型调用约 **50–65 秒**(含意图分析、证据计划、大纲、正文)。 本地模型接入后特别要注意的三点(均已在代码里处理并有回归测试): | 现象 | 处理方式 | | --- | --- | | 思考型模型把预算花在 `thinking` 上,正文为空 | 强制 `OLLAMA_THINK=false`;仍为空时明确报错并提示调整 | | 模型返回**空的主张清单** | 工作流回退到系统默认主张清单;「空清单」永远判为待核实,不会当成全部通过 | | 模型自称「经人工审核校对」 | 事实核查按 `unsubstantiated_review_claim` 拦下(critical),生成阶段不存在人工审批 | 本地小模型会写出**看起来合理但没有来源**的细节(例如替品牌补出“基础版/同步方案”等官网并未写的内容)。 这不是 bug,而是这类模型的真实水平:因此系统的定位是**写作辅助**,事实必须由人核验来源后才能发布。 ## 7. 测试与静态检查 ```powershell .\.venv\Scripts\python.exe -m pytest -q .\.venv\Scripts\python.exe -m ruff check . .\.venv\Scripts\python.exe -m ruff format --check . ``` 测试全部使用临时 SQLite 与 mock provider(`conftest.py` 固定 `LLM_PROVIDER=mock`,不受本机 `.env` 影响); 同时有一条“阻断对外网络连接”的自动夹具,任何真实平台调用都会直接失败。 ## 8. 目录结构 ```text app/ main.py # 应用入口、路由注册、全局错误兜底 web.py # 中文管理界面(Jinja2 服务端渲染) api/ # REST 接口:health / brands / articles core/ # 配置(.env)与日志脱敏 db.py # 引擎与会话(延迟创建) models/ # ORM:brands / articles / article_versions / sources schemas/ # Pydantic 请求响应模型 services/ writing/ # 写作工作流、LLMProvider 抽象、mock / ollama provider、GEO 自检 fact_check/ # 规则化事实核查(无来源即待核实) distribution.py # 分发闸门(未审批/有待核实 → 拒绝分发) platforms/ # P2 阶段实现:wechat / baijiahao / xiaohongshu templates/ static/ migrations/ # Alembic 迁移 tests/ # pytest docs/ # 实施方案、平台能力、运维说明、进度 data/ # 本地数据库与素材(不入 Git) ``` ## 9. 已知限制与边界(重要) - **百家号/小红书仍无真实写能力**:只做适配与导出,由你在后台人工发布。 - **公众号只做到草稿**:已真实联调通过(`draft/add` 验证、幂等验证、只读核验), **终局发布(`freepublish/submit`)不实现**,也不做无人值守群发。 - **观测数据全靠人工**:GEO 效果台账只汇总人工登记的真实记录,系统不推测、不生成指标; 自动观测需先确认符合平台条款与授权。 - **小红书/百家号适配器尚未实现**:当前只有公众号适配器的设计位(P2)。 - **GEO 自检分数不是收录预测**:它只是内部编辑提示,不能作为“会被 AI 引用”的证据。 - **本系统不做无人值守批量发布**:真实写入必须同时满足账号权限、全局开关、用户明确确认、 已审批版本与适配器能力五个条件。 - **本地/云端大模型都可能编造细节**:系统的防线是「来源登记 + 事实核查 + 人工审批」,不是模型本身。 ## 10. 常见问题 **Q:启动报 `ModuleNotFoundError: app`?** 先 `pip install -e ".[dev]"`,或在项目根目录执行(`alembic.ini` 与 `pyproject.toml` 已配置 `pythonpath`)。 **Q:数据库文件在哪?** 默认 `data/geo.db`(相对项目根目录解析,不随工作目录变化),该目录已被 `.gitignore` 忽略。 **Q:改了 ORM 模型怎么办?** ```powershell .\.venv\Scripts\python.exe -m alembic revision --autogenerate -m "说明" .\.venv\Scripts\python.exe -m alembic upgrade head .\.venv\Scripts\python.exe -m alembic check # 校验模型与迁移是否一致 ``` **Q:能不能接云端大模型?** 当前实现了 `mock` 与 `ollama`。设置 `LLM_PROVIDER=openai` 会明确报错而不是静默失败; 云厂商 provider 属 P1 范围,届时需要你提供供应商正式 API Key。 **Q:本地模型报「模型只返回了思考过程、没有正文」?** 模型是思考型且 `OLLAMA_THINK=true`,把预算耗在思考上了。改成 `OLLAMA_THINK=false` 并重启。 **Q:本地模型报 404 / 模型不存在?** 先 `ollama pull <模型名>`,再把 `OLLAMA_MODEL` 改成与 `ollama list` 完全一致的名称。 **Q:为什么文章里全是“待核实”?** 这是设计目标:没有可核验来源时,宁可标注也不要让模型编造参考文献。 ## 11. 相关文档 - [`docs/CLAUDE.md`](docs/CLAUDE.md):项目持续工作指令与产品边界 - [`docs/GEO_实施方案.md`](docs/GEO_实施方案.md):可执行实施方案 - [`docs/IMPLEMENTATION_PLAN.md`](docs/IMPLEMENTATION_PLAN.md):开发清单与完成进度 - [`docs/PLATFORM_CAPABILITIES.md`](docs/PLATFORM_CAPABILITIES.md):各平台已验证能力清单 - [`docs/OPERATIONS.md`](docs/OPERATIONS.md):安全配置、备份、错误处理与凭证轮换