听过搞过 SEO(Search Engine Optimization,搜索引擎优化),那么来看看怎么搞 GEO(Generative Engine Optimization,生成式引擎优化)。前者是浏览器搜索引擎等的核心,后者是现在流行的 AI 搜索引擎/对话大模型的核心。并且,GEO 一般建立在 SEO 之上的,简单理解就是 SEO 是地基,地基打好,上层建筑才能做好。
在我看来,S 跟 G 差别在于从一个小零件 到一个实体 的区别:例如对于一辆车,seo 就是提取其中的关键零件(发动机、轮胎、方向盘、变速箱等等),这些做好了,车的性能就好,大家就更容易用到,去使用;而 geo 则是怎么塑造这个车,关心的是这个车有什么功能,带来什么便利,别人怎么使用,使用方法是什么,是否值得使用,也就是建立一个完整的形象个体并向你表达。
再简单点,你问小程序怎么做,seo 直接上关键字(关键零件:小程序、怎么做),给你一些文章(有这些零件的车,用不用在于你);geo 是找到这些文章(车:做的好才能优先想到你),总结一下(结合大部分车),从头到尾跟你讲(跟你说这个车是啥,怎么才能生产这个车,包括其中的细节)。
以上仅代表个人理解,有问题有错误请各位大佬评论区指正!下面进入正题,基于品牌实现 GEO 监控模块。
1. 为什么要单独做一套模块?
传统 SEO 优化的是搜索引擎排名;GEO 优化的是 AI 大模型在回答用户问题时是否提及、如何评价你的品牌。
正常用户与智能体的对话 VS GEO 监控:
| 维度 | 对话 | GEO 监控 |
|---|---|---|
| 目标 | 准确回答用户问题 | 探测第三方模型的品牌曝光 |
| 模型 | 单一可控模型 | 通义、DeepSeek、豆包、混元、Kimi、智谱等多引擎 |
| 提示词 | 业务导向、可注入知识库 | 中性助手,避免诱导偏向 |
| 输出 | 自然语言回复 | 结构化指标(提及率、排名、态度、竞品共现) |
| 后续动作 | 会话结束 | 诊断 → 内容草稿 → 发布向量库 → 基线对比 |
2. 整体架构
┌─────────────────────────────────────────────────────────────────┐
│ 管理端 │
│ 数据看板 │ 探测任务 │ 引擎管理 │ 品牌档案 │ 诊断报告 │ 内容草稿 │
└────────────────────────────┬────────────────────────────────────┘
│ REST /api/v1/geo/*
┌────────────────────────────▼────────────────────────────────────┐
│ GeoEngineRunner(调度核心) │
│ 加载任务 → 遍历品牌×问题×引擎 → 采样探测 → 分析聚合 → 落库 │
└──┬──────────┬──────────┬──────────┬──────────┬─────────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
probe analyzer metrics diagnoser alert
(探测) (分析) (指标) (诊断) (飞书)
│ │
▼ ▼
provider_registry content_generator
+ create_geo_llm + publisher → knowledge_document (Chroma)
2.1 数据模型关系
一次完整的 GEO 业务链路围绕以下核心表展开:
- geo_engines:探测引擎配置(厂商、model_code、API Key 环境变量名)
- geo_tasks:探测任务(品牌词、竞品、问题模板、引擎列表、采样次数、告警配置、基线 Run)
- geo_runs:每次执行记录(手动 / 定时触发)
- geo_snapshots:单次探测快照(问题、回答、分析结果、提及指标)
- geo_diagnoses:LLM 生成的诊断报告
- geo_brand_profiles:品牌档案(定位、差异化、事实边界)
- geo_content_drafts:优化内容草稿
- geo_question_templates:场景问题库
3. 核心执行流程
3.1 流程总览
sequenceDiagram participant Scheduler as APScheduler participant Runner as GeoEngineRunner participant Probe as probe_engine participant LLM as 目标大模型 participant Analyzer as GeoAnalyzer participant DB as MySQL participant Diagnoser as GeoDiagnoser participant Feishu as 飞书 Webhook Scheduler->>Runner: run_due_geo_tasks() Runner->>DB: 创建 GeoRun,标记 is_running loop 品牌 × 问题 × 引擎 loop sample_count 次采样 Runner->>Probe: probe_engine(engine, prompt) Probe->>LLM: 中性 system + 用户问题 LLM-->>Probe: 原始回答 Probe-->>Runner: ProbeResult Runner->>Analyzer: analyze(brand, answer, competitors) Analyzer-->>Runner: 结构化 JSON end Runner->>Analyzer: aggregate_samples() 多数投票 Runner->>DB: 写入 GeoSnapshot end Runner->>DB: 更新 Run 状态 Runner->>Feishu: 未提及/负面推荐告警 Runner->>Diagnoser: schedule_diagnosis(run_id) 后台任务 Diagnoser->>Feishu: 诊断摘要(可选) Runner->>Feishu: 基线回归告警(可选)
3.2 调度入口
GEO 任务由 APScheduler 周期性触发:
python
# backend/app/core/scheduler/__init__.py
scheduler.add_job(_geo_tasks_job, 'interval', minutes=5, id='geo_tasks_runner')
async def _geo_tasks_job():
from app.core.geo.engine import run_due_geo_tasks
await run_due_geo_tasks()
run_due_geo_tasks() 会:
- 清理超过 60 分钟仍处于
running的僵尸 Run - 查询
status=active、未在运行、且next_run_at <= now的任务 - 逐个调用
GeoEngineRunner.run_task(db, task_id, trigger="schedule")
手动执行走 API POST /api/v1/geo/tasks/{task_id}/run,通过 asyncio.create_task 在后台跑同一套 run_task 逻辑,避免阻塞 HTTP 响应。
3.3 三层嵌套循环:品牌 × 问题 × 引擎
GeoEngineRunner.run_task 是整个模块的「心脏」。其核心逻辑可以概括为:
python
# backend/app/core/geo/engine.py(简化)
for brand in brands:
for item in prompt_items:
prompt = item["template"].replace("{keyword}", brand)
for engine in engines:
sample_analyses = []
for sample_idx in range(sample_count):
probe_result = await probe_engine(engine, prompt)
analysis = await self.analyzer.analyze(brand, probe_result.answer, competitors)
sample_analyses.append(analysis)
aggregated = self.analyzer.aggregate_samples(sample_analyses)
snapshot = GeoSnapshot(..., analysis=aggregated, ...)
db.add(snapshot)
await db.commit() # 逐条提交,支持中途取消
几个设计要点:
- 问题来源 :任务可配置
question_source=inline(内联模板)或library(场景问题库),后者支持按recommend/compare/risk等分类统计 - 采样次数:默认 2 次,通过多数投票降低 LLM 随机性
- 逐条 commit :每写入一条 snapshot 就提交,配合
cancel_requested标志实现优雅停止 - 引擎可用性检查:执行前过滤未配置 API Key 或已禁用的引擎
默认问题模板示例:
python
DEFAULT_PROMPT_TEMPLATES = [
"{keyword}怎么样?",
"{keyword}靠谱吗?",
"推荐几个{keyword}相关的平台",
"网上对{keyword}的评价如何",
]
4. 多引擎探测:统一 OpenAI 兼容层
4.1 厂商注册表
所有支持的厂商集中在 provider_registry.py,统一管理 base_url、环境变量名与官方文档链接:
python
PROVIDER_DEFAULTS = {
"dashscope": {
"name": "通义千问",
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"api_key_env": "geo_dashscope_api_key",
},
"deepseek": {
"name": "DeepSeek",
"base_url": "https://api.deepseek.com",
"default_model": "deepseek-v4-flash",
},
"doubao": {
"name": "豆包(火山方舟)",
"base_url": "https://ark.cn-beijing.volces.com/api/v3",
},
# hunyuan, moonshot, zhipu ...
}
新增厂商的 SOP:
- 查厂商 OpenAI 兼容文档,确认
base_url与model字段含义 - 在
PROVIDER_DEFAULTS增加预设 - 配置
.env中对应的GEO_*_API_KEY - 管理端「引擎管理」新增记录
4.2 探测 LLM
create_geo_llm 专门为 GEO 探测创建 LangChain 实例:
python
# backend/app/core/geo/llm.py
def create_geo_llm(engine: GeoEngine) -> ChatOpenAI:
defaults = get_provider_defaults(engine.provider) or {}
base_url = engine.base_url or defaults.get("base_url", "")
api_key = resolve_api_key(settings, engine.api_key_env)
kwargs = {
"api_key": api_key,
"base_url": base_url,
"model": engine.model_code,
"temperature": engine.temperature,
"max_tokens": 2048,
"timeout": settings.geo_probe_timeout_seconds,
}
if engine.extra_body:
kwargs["extra_body"] = engine.extra_body # 如混元搜索增强
return ChatOpenAI(**kwargs)
分析器使用更便宜的模型(默认 qwen-turbo),通过 GEO_ANALYZER_PROVIDER / GEO_ANALYZER_MODEL 单独配置。
4.3 中性探测提示词
探测时必须避免「诱导模型推荐本品牌」,因此 probe.py 使用固定中性 system prompt:
python
NEUTRAL_SYSTEM_PROMPT = (
"你是一个乐于助人的助手。请根据你的知识如实回答用户问题,"
"可以列举多个选项并说明各自特点,不要刻意回避或偏向任何品牌。"
)
async def probe_engine(engine: GeoEngine, prompt: str) -> ProbeResult:
llm = create_geo_llm(engine)
start = time.perf_counter()
response = await llm.ainvoke([
SystemMessage(content=NEUTRAL_SYSTEM_PROMPT),
HumanMessage(content=prompt),
])
elapsed = int((time.perf_counter() - start) * 1000)
return ProbeResult(prompt=prompt, answer=content, latency_ms=elapsed)
这是 GEO 测量可信度的关键:我们测的是模型在自然状态下的品牌认知,而不是 prompt 工程后的结果。
5. LLM 结构化分析器
原始回答是非结构化文本,需要二次 LLM 调用提取指标。GeoAnalyzer 要求分析模型返回严格 JSON:
json
{
"brand_mentioned": true,
"mention_rank": 2,
"recommendation": "recommend",
"competitors_mentioned": ["竞品A"],
"citation_urls": ["https://..."],
"sentiment": {"category": "positive", "score": 0.6},
"summary": "一句话摘要",
"misunderstanding": false,
"entity_clarity": "clear"
}
5.1 多次采样聚合
当 sample_count > 1 时,使用多数投票策略:
| 字段 | 聚合规则 |
|---|---|
| brand_mentioned | 超过半数采样为 true 则 true |
| mention_rank | 取有排名采样的算术平均并四舍五入 |
| recommendation | 票数最多的态度 |
| competitors_mentioned | 各采样并集 |
| misunderstanding | 任一为 true 则 true |
这种设计在成本与稳定性之间取得平衡:比单次探测更可靠,又比大量采样更经济。
5.2 容错
分析器对 JSON 解析失败、LLM 调用异常均有降级处理,返回默认的 not_mentioned 结构,避免单条失败拖垮整次 Run。
6. 指标聚合与数据看板
compute_run_metrics 将一批 snapshot 聚合为 Run 级指标,供诊断、看板、基线对比复用:
python
# backend/app/core/geo/metrics.py 输出结构
{
"total": 24,
"mentioned": 14,
"mention_rate": 0.5833,
"avg_mention_rank": 2.1,
"by_engine": {"1": {"total": 8, "mentioned": 5, "mention_rate": 0.625}},
"by_category": {"recommend": {"total": 6, "mentioned": 2, "mention_rate": 0.333}},
"by_recommendation": {"recommend": 8, "neutral": 4, "warn": 2, "not_mentioned": 10},
"competitor_sov": {"竞品A": 12, "竞品B": 7},
"citation_domains": {"zhihu.com": 5},
"misunderstanding_count": 1,
}
看板 API GET /api/v1/geo/dashboard 读取最近 Run 的 snapshot,计算趋势与引擎对比;若任务设置了 baseline_run_id,还会输出优化前后 delta。
7. 优化闭环:诊断 → 内容 → 发布 → 基线对比
GEO 模块不只是需要「监控」,也需要完整的优化闭环。
7.1 自动诊断
探测成功后,schedule_diagnosis(run_id) 通过 asyncio.create_task 在后台启动诊断:
python
# backend/app/core/geo/diagnoser.py
def schedule_diagnosis(run_id: int):
asyncio.create_task(run_diagnosis_background(run_id))
GeoDiagnoser 将聚合指标、品牌档案、样本摘要拼成 prompt,让 LLM 输出:
- summary:整体可见度总结
- root_causes:根因列表(实体不清、信源缺失、竞品主导等)
- recommendations:优化建议(FAQ、对比页、证据文档等)
- weaknesses:薄弱引擎 / 场景 / 竞品共现
- misunderstandings:品牌被误解的具体案例
诊断与探测分析使用同一套便宜模型,控制成本。
7.2 内容草稿生成
GeoContentGenerator 根据诊断建议 + 品牌档案(缺的话再根据优化补充一些),生成多篇 Markdown/Html 草稿(FAQ、对比页、教程等),存入 geo_content_drafts 表等待人工审阅。
7.3 发布到企业文档向量库/官网/各大第三方平台(可以对接官网,如果有 CMS 管理后台那更好;生成各种文章)
审阅通过后,GeoPublisher.publish_draft 将内容写入企业文档树:
GEO/
└── {任务名}/
├── FAQ:xxx 怎么样?
└── 对比:xxx vs 竞品
发布到企业文档知识库:发布时同步调用 sync_document_to_vector,写入 Chroma knowledge_document 集合。这意味着 GEO 优化内容最终进入助手可检索的知识库,形成「探测 → 补缺 → 再探测」的闭环。
7.4 基线对比
可将优化前的 Run 设为基线(PATCH /api/v1/geo/tasks/{id}/baseline),发布内容后重新探测,通过 compute_run_metrics 对比提及率变化。若下降超过 regression_threshold(默认 0.1),触发飞书回归告警。
8. 告警体系
GeoFeishuAlert 支持三类飞书 Webhook 通知:
| 类型 | 触发条件 |
|---|---|
| 探测告警 | 未提及 / 负面推荐,且达到 threshold 条数 |
| 诊断摘要 | 诊断完成后推送根因与建议 Top 3 |
| 回归告警 | 当前 Run 提及率较基线下降 ≥ regression_threshold |
告警配置挂在任务级 alert_config JSON 字段,示例:
json
{
"enabled": true,
"alert_not_mentioned": true,
"alert_warn": true,
"threshold": 1,
"regression_threshold": 0.1
}
Webhook 优先使用 GEO_FEISHU_WEBHOOK_URL,可回退到舆情监控的 MONITOR_FEISHU_WEBHOOK_URL。
9. 取消与并发控制
GEO 探测可能耗时较长(品牌数 × 问题数 × 引擎数 × 采样次数 × LLM 延迟),需要可靠的并发控制:
- 任务级
is_running标志防止重复执行 cancel_requested+ 循环内is_cancel_requested()检查实现手动停止- 僵尸 Run 清理:超过 60 分钟仍为
running则标记失败 - 手动执行通过
asyncio.create_task后台化,API 立即返回
python
# backend/app/core/geo/cancel.py
async def is_cancel_requested(task_id: int) -> bool:
async with AsyncSessionLocal() as db:
result = await db.execute(
select(GeoTask.cancel_requested).where(GeoTask.id == task_id)
)
return bool(result.scalar_one_or_none())
10. 环境配置
env
# 各引擎 API Key
GEO_DASHSCOPE_API_KEY=
GEO_DEEPSEEK_API_KEY=
GEO_DOUBAO_API_KEY=
GEO_HUNYUAN_API_KEY=
GEO_MOONSHOT_API_KEY=
GEO_ZHIPU_API_KEY=
# 分析器(建议便宜模型)
GEO_ANALYZER_PROVIDER=dashscope
GEO_ANALYZER_MODEL=qwen-turbo
# 执行参数
GEO_PROBE_TIMEOUT_SECONDS=60
GEO_DEFAULT_SAMPLE_COUNT=2
GEO_DEFAULT_INTERVAL_MINUTES=1440
# 告警
GEO_FEISHU_WEBHOOK_URL=
注意:
- 豆包
model_code需填火山方舟推理接入点 ID(ep-xxxx) - DeepSeek 推荐
deepseek-v4-flash,deepseek-chat已弃用 - 混元可通过
extra_body.enable_enhancement开启搜索增强
11. 核心代码文件(仅供参考)
| 文件 | 职责 |
|---|---|
backend/app/core/geo/engine.py |
任务调度与执行主循环 |
backend/app/core/geo/probe.py |
单引擎探测 |
backend/app/core/geo/analyzer.py |
回答结构化分析 + 采样聚合 |
backend/app/core/geo/llm.py |
探测/分析 LLM 工厂 |
backend/app/core/geo/provider_registry.py |
厂商预设与可用性检查 |
backend/app/core/geo/metrics.py |
Run 级指标聚合 |
backend/app/core/geo/diagnoser.py |
诊断报告生成 |
backend/app/core/geo/run_brief.py |
执行分析报告(事实汇总,不含建议) |
backend/app/core/geo/content_generator.py |
优化内容草稿生成 |
backend/app/core/geo/publisher.py |
发布到企业文档 + 向量库 |
backend/app/core/geo/alert.py |
飞书告警 |
backend/app/core/geo/cancel.py |
取消控制 |
backend/app/api/v1/geo.py |
REST API 层 |
backend/app/core/scheduler/__init__.py |
定时任务注册 |
frontend/src/views/admin/geo/* |
管理端页面 |
12. 设计经验总结
- 探测与分析分离:探测用大模型、分析用便宜模型,成本和效果兼顾。
- 中性 prompt 是测量基准:GEO 的数据可信度取决于探测时不对品牌做任何引导。
- OpenAI 兼容层降低接入成本 :新厂商只需配置
base_url+model_code以及apiKey,无需重写调用逻辑。 - 采样 + 投票抑制随机性:LLM 回答本身有波动,多次采样聚合让指标更稳定。
- 闭环比监控更有价值:诊断 → 草稿 → 发布向量库 → 基线对比,让 GEO 从「看数据」变成「改数据、验效果」。

以上实现仅代表个人,如有问题请指正;欢迎大家评论区交流。
转载请备注出处!