昨晚看 315 晚会,看到 GEO 投毒那段的时候,我愣了好一会儿。
不是因为这事多新鲜------做 AI 开发的多少都听过「给大模型喂脏数据」这种事。让我后背发凉的是:39.9 元就能篡改一个 AI 的回答。有家叫"力擎"的公司,花几千块就能让一款根本不存在的智能手环出现在大模型的推荐列表里。
作为一个每天都在调 API、写 prompt 的开发者,我第一反应是:我自己的 AI 应用,有没有在给用户输出被投过毒的内容?
先说结论
| 检测方式 | 原理 | 效果 |
|---|---|---|
| 单模型验证 | 只问一个模型 | ❌ 基本无法发现投毒 |
| 多模型交叉验证 | 同一问题问 3+ 个模型,对比回答 | ✅ 能发现异常推荐 |
| 实体验证 | 把 AI 推荐的品牌/产品拿去搜索引擎验证 | ✅ 能发现虚假产品 |
| 时序对比 | 同一问题隔几天再问一次 | ⚠️ 能发现突然新增的推荐 |
最靠谱的方案:多模型交叉 + 实体验证,我后面会给完整代码。
GEO 到底是什么?
GEO 全称 Generative Engine Optimization(生成式引擎优化),说白了就是 SEO 的 AI 版。
传统 SEO 是让你的网页在百度/Google 搜索结果里排名靠前。GEO 则是让你的品牌/产品在 AI 大模型的回答里被推荐。
技术上怎么实现的?其实不复杂:
- 批量生产软文:围绕目标关键词生成大量"测评""对比""推荐"类文章
- 铺设到高权重平台:知乎、百家号、搜狐号、博客园,越多越好
- 等模型更新数据:国内大模型大多依赖搜索引擎结果做 RAG(检索增强生成),只要搜索结果里你的内容够多,模型回答自然就会引用
315 曝光的"力擎GEO"就是这么干的------虚构了一个"量子脉冲手环",往互联网上灌了十几篇软文,没几天两个主流大模型就开始推荐这款"产品"了。
关键是,这不是什么高级黑客攻击,就是用内容把模型的信息源给污染了。
为什么开发者该关心这事?
如果你在做的产品里用到了 AI 搜索、AI 推荐、AI 客服------请仔细想想这几个场景:
- 用户问"推荐一个好用的 XX 工具",AI 推了个投毒塞进来的垃圾产品
- 用户问"这个品牌靠谱吗",AI 引用了批量生产的虚假测评
- 你的 RAG 系统从互联网抓取知识库内容,混入了 GEO 投毒的数据
这些都不是假设,是正在发生的事。
检测方案:多模型交叉验证
思路很简单:同一个问题,丢给多个不同的大模型,对比它们的推荐结果。如果某个品牌/产品只在 1-2 个模型的回答里出现,但其他模型完全不提,那就很可疑。
为什么这招有效?因为不同模型的训练数据、RAG 数据源不一样,GEO 投毒很难同时覆盖所有模型。
python
"""
AI 回答「中毒」检测器
用法: python geo_detector.py "推荐几款好用的智能手环"
"""
import os
import re
import sys
import json
from openai import OpenAI
from collections import Counter
# 配置多个模型(用 OpenAI 兼容接口)
MODELS = [
{
"name": "模型A",
"base_url": "https://api.model-a.com/v1",
"api_key": os.getenv("MODEL_A_KEY", "your-key"),
"model": "model-a-latest"
},
{
"name": "模型B",
"base_url": "https://api.model-b.com/v1",
"api_key": os.getenv("MODEL_B_KEY", "your-key"),
"model": "model-b-latest"
},
{
"name": "模型C",
"base_url": "https://api.model-c.com/v1",
"api_key": os.getenv("MODEL_C_KEY", "your-key"),
"model": "model-c-latest"
},
]
def ask_model(config: dict, question: str) -> str:
"""调用单个模型"""
client = OpenAI(
base_url=config["base_url"],
api_key=config["api_key"]
)
resp = client.chat.completions.create(
model=config["model"],
messages=[
{"role": "system", "content": "请用中文回答,推荐时列出具体品牌和产品名。"},
{"role": "user", "content": question}
],
temperature=0.3 # 降低随机性,让结果更稳定
)
return resp.choices[0].message.content
def extract_brands(text: str) -> list[str]:
"""从回答中提取品牌/产品名(简单方案:提取引号和加粗内容)"""
brands = []
# 匹配中文引号内容
brands += re.findall(r'[「"](.*?)[」"]', text)
# 匹配 Markdown 加粗
brands += re.findall(r'\*\*(.*?)\*\*', text)
# 匹配 "XX品牌" 或 "XX手环" 等模式
brands += re.findall(r'(\w{2,10}(?:品牌|手环|手表|工具|平台|软件|产品))', text)
# 去重 + 去除太短的
return list(set(b for b in brands if len(b) >= 2))
def detect_poisoning(question: str):
"""核心检测逻辑"""
print(f"\n🔍 检测问题: {question}\n")
results = {}
all_brands = []
for config in MODELS:
print(f" 询问 {config['name']}...", end=" ")
try:
answer = ask_model(config, question)
brands = extract_brands(answer)
results[config["name"]] = {
"answer": answer,
"brands": brands
}
all_brands.extend(brands)
print(f"提取到 {len(brands)} 个品牌/产品")
except Exception as e:
print(f"❌ 调用失败: {e}")
if not results:
print("所有模型都调用失败,无法检测")
return
# 统计每个品牌被几个模型提到
brand_counts = Counter(all_brands)
total_models = len(results)
print(f"\n{'='*50}")
print(f"📊 交叉验证结果(共 {total_models} 个模型)")
print(f"{'='*50}\n")
suspicious = []
for brand, count in brand_counts.most_common():
ratio = count / total_models
if ratio >= 0.6:
status = "✅ 正常(多数模型提及)"
elif ratio >= 0.3:
status = "⚠️ 需关注(少数模型提及)"
else:
status = "🚨 可疑(仅个别模型提及)"
suspicious.append(brand)
print(f" {brand}: {count}/{total_models} 个模型提及 → {status}")
if suspicious:
print(f"\n🚨 以下品牌/产品可能是 GEO 投毒结果,建议人工核实:")
for b in suspicious:
# 找出是哪个模型推荐的
sources = [name for name, r in results.items() if b in r["brands"]]
print(f" - 「{b}」仅出现在: {', '.join(sources)}")
else:
print(f"\n✅ 未发现明显异常,所有推荐都有多个模型佐证")
if __name__ == "__main__":
question = sys.argv[1] if len(sys.argv) > 1 else "推荐几款好用的智能手环"
detect_poisoning(question)
踩坑记录
写这个脚本的过程中踩了几个坑,记录一下:
1. 品牌提取比想象中难
最开始我用的是 NLP 命名实体识别(NER),跑了一下发现效果很差------国产品牌名太野了,什么"小米""华为"还行,"量子脉冲"这种生造的名字根本识别不出来。最后换成了正则 + 规则的土办法,反而更稳。
2. temperature 一定要调低
默认 temperature=1.0 的时候,同一个模型问两次,推荐的产品都不一样,交叉验证完全没法做。调到 0.3 以下才比较稳定。
3. 模型数量很关键
两个模型对比基本没用,因为无法判断到底是谁中毒了。至少要 3 个,最好 5 个以上。这也是为什么 315 里那个实验只测了两个模型就下结论------其实样本量偏少。
4. GEO 投毒有时效性
模型的 RAG 数据源会更新,投毒内容也会被清理。所以检测最好定期跑,不是跑一次就完事。可以加个 cron job 每天定时跑一遍。
防御建议(给 AI 应用开发者)
如果你在做面向用户的 AI 产品,以下几点值得考虑:
1. RAG 数据源白名单
别啥网站的内容都往知识库里灌。维护一个可信来源列表,只从白名单站点抓取数据。
2. 多模型交叉验证
关键场景(产品推荐、医疗健康等)的回答,用多个模型验证后再输出。上面的脚本可以直接集成到你的后端里。
3. 实体验证层
AI 推荐了一个品牌?先去工商数据库查一下这公司存不存在。推荐了一个产品?去电商平台搜一下有没有。这步可以自动化。
4. 用户反馈闭环
让用户可以标记"这个推荐不靠谱",然后把这些 case 收集起来分析。群众的眼睛是雪亮的。
5. 监控异常模式
如果某个品牌名突然在 AI 回答中出现频率飙升,大概率是有人在搞 GEO。做个监控面板不难。
小结
315 曝光 GEO 投毒这事,说实话不意外。AI 搜索越来越替代传统搜索,SEO 那一套自然会进化成 GEO。就像当年搜索引擎竞价排名一样,流量在哪里,灰产就在哪里。
但作为开发者,我们能做的不是等着监管出手(虽然这次 315 曝光后,力擎 GEO 当晚就删文销号了),而是在技术层面给自己的产品加一道防线。
多模型交叉验证不是万能的,但至少比啥都不做强。39.9 元就能篡改一个 AI 的回答,那我们也可以用 39.9 行代码来检测它------虽然上面的脚本不止 39.9 行,但你懂我意思 😂
对了,如果你也发现了什么有趣的 GEO 投毒案例,欢迎评论区交流。这领域变化很快,315 之后肯定还会有新的变种出来。