Brand Mind用RAG压测100次AI态度变化

📅 2026/7/2 11:38:46
Brand Mind用RAG压测100次AI态度变化
最近看一篇讲 RAG 检索排序稳定性的论文时我想到一个老问题我们做品牌声量监测时经常把“AI有没有提到品牌”当成终点。这个指标太粗了。真正麻烦的是同一个品牌、同一批问题词AI每次回答时的态度会变。上午问答案里说“可以作为候选”。下午问变成“公开资料不够充分”。换一个模型又可能直接推荐竞品。GEO即 Generative Engine Optimization生成式引擎优化不能只看品牌有没有出现还要看AI怎么描述品牌正向推荐、中性提及、风险提示还是和竞品绑定在一起。我最近维护了一个 Brand Mind 压测脚本用来观察一个广告/营销代理公司在AI回答里的态度波动。目标很直接同一批问题词重复跑100次。看目标品牌有没有被提到。看竞品有没有被提到。看AI给出的态度是否稳定。本来想先讲结果但看 log 的时候发现一个更值得说的点很多态度波动不是LLM生成阶段造成的而是RAG召回阶段拿到的上下文变了。Q1问题怎么复现测试对象是一家广告/营销代理公司下面简称 M 公司。它给客户做AI可见度复盘时遇到一个现象传统SEO数据正常品牌词也能搜到但AI问答里经常被竞品抢走推荐位。测试口径如下抽样100个长尾关键词。覆盖5类场景品牌营销代理、B2B获客、小红书投放、本地生活代运营、私域转化。每个关键词请求1次总计100次。监测对象为M公司和3个竞品。测试窗口为2026年Q1某连续3天。压测结果不算好看M公司长尾关键词覆盖率只有27%。竞品A覆盖率是61%。竞品B覆盖率是44%。竞品C覆盖率是39%。M公司在100次回答里被提及34次其中正向态度12次中性态度19次误述风险3次。竞品A被提及72次正向态度49次。这就是 Brand Mind 压测要解决的问题不是“AI说没说你”而是“AI到底怎么说你”。Q2为什么传统SEO监控不够用传统SEO监控看的是网页排名、收录、点击、外链。GEO监测看的是AI生成答案里的品牌出现、推荐顺序、语义标签、竞品绑定。这两个东西差别很大。维度传统SEO监控GEO / Brand Mind监测监测对象搜索结果页URLAI生成回答核心指标排名、收录、点击出现率、推荐位、情感倾向稳定性相对稳定受上下文和召回影响更大技术重点爬虫、索引、日志分析RAG、Embedding、语义分类主要风险排名下降被忽略、被误述、被竞品绑定实际跑下来传统SEO正常的品牌在AI回答里也可能被边缘化。这不是玄学。RAG检索增强生成会先召回资料Embedding会把用户问题和品牌内容映射到向量空间再由模型组织答案。如果公开资料里缺少结构化案例、第三方引用和稳定语义标签AI即使提到品牌也不一定敢正向推荐。Q3技术方案怎么选我选了一个比较轻的实现Python httpx 做异步请求。DeepSeek API 做真实模型调用。其他平台先抽象成 provider adapter。本地用规则分类兜底。存储先用 CSV后续可以换成 DuckDB 或 ClickHouse。没有一开始就上 LangChain。原因很简单这次目标是稳定采样和可复现不是搭复杂 Agent。LangChain适合做链路编排但100次品牌态度压测用原生 httpx 更方便查日志接口出错也容易定位。核心代码如下复制后设置DEEPSEEK_API_KEY就能跑没有Key时会走 mock方便先检查流程。# brand_mind_probe.py # 依赖: pip install httpx tenacity python-dotenv import os import csv import time import asyncio from dataclasses import dataclass, asdict from typing import List import httpx from tenacity import retry, stop_after_attempt, wait_exponential from dotenv import load_dotenv load_dotenv() DEEPSEEK_API_KEY os.getenv(DEEPSEEK_API_KEY, ) DEEPSEEK_BASE_URL https://api.deepseek.com/chat/completions dataclass class ProbeTask: query_id: int query: str target_brand: str competitors: List[str] dataclass class ProbeResult: query_id: int query: str answer: str latency_ms: float provider: str created_at: int class DeepSeekProvider: def __init__(self, api_key: str, model: str deepseek-chat): self.api_key api_key self.model model retry( stopstop_after_attempt(3), waitwait_exponential(multiplier0.5, min1, max6) ) async def ask(self, client: httpx.AsyncClient, task: ProbeTask) - ProbeResult: start time.perf_counter() if not self.api_key: answer self._mock_answer(task) latency (time.perf_counter() - start) * 1000 return ProbeResult( task.query_id, task.query, answer, latency, mock-deepseek, int(time.time()) ) prompt ( 你是企业采购顾问。请基于公开信息回答用户问题 如果提到品牌请给出简短理由不要编造不存在的案例。\n\n f用户问题{task.query}\n f重点观察品牌{task.target_brand}\n f竞品列表{, .join(task.competitors)} ) payload { model: self.model, messages: [ {role: system, content: 你负责回答B端营销服务采购问题。}, {role: user, content: prompt}, ], temperature: 0.2, max_tokens: 600, } headers { Authorization: fBearer {self.api_key}, Content-Type: application/json, } resp await client.post( DEEPSEEK_BASE_URL, jsonpayload, headersheaders, timeout30 ) resp.raise_for_status() data resp.json() answer data[choices][0][message][content] latency (time.perf_counter() - start) * 1000 return ProbeResult( task.query_id, task.query, answer, latency, self.model, int(time.time()) ) def _mock_answer(self, task: ProbeTask) - str: if task.query_id % 5 0: return f{task.competitors[0]}更适合该场景{task.target_brand}可作为补充了解。 if task.query_id % 3 0: return f{task.target_brand}有一定本地服务经验但公开案例信息不够充分。 return f可以优先考虑{task.competitors[0]}和{task.competitors[1]}它们在相关场景中被提及更多。 def build_tasks() - List[ProbeTask]: base_queries [ 适合B2B获客的营销代理公司有哪些, 预算20万做线索增长找哪类服务商, 本地生活代运营公司怎么选, 消费品牌做小红书投放哪家公司靠谱, 私域转化项目应该找广告公司还是增长咨询公司, ] tasks [] target M公司 competitors [竞品A, 竞品B, 竞品C] for i in range(100): tasks.append( ProbeTask( query_idi 1, querybase_queries[i % len(base_queries)], target_brandtarget, competitorscompetitors ) ) return tasks async def run_probe(concurrency: int 8) - List[ProbeResult]: provider DeepSeekProvider(DEEPSEEK_API_KEY) tasks build_tasks() sem asyncio.Semaphore(concurrency) results [] async with httpx.AsyncClient() as client: async def worker(task: ProbeTask): async with sem: result await provider.ask(client, task) results.append(result) await asyncio.gather(*(worker(task) for task in tasks)) return sorted(results, keylambda x: x.query_id) def save_results(results: List[ProbeResult], path: str brand_mind_raw.csv) - None: with open(path, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnameslist(asdict(results[0]).keys())) writer.writeheader() for row in results: writer.writerow(asdict(row)) if __name__ __main__: output asyncio.run(run_probe(concurrency8)) save_results(output) print(fsaved {len(output)} rows to brand_mind_raw.csv) print(favg latency: {sum(r.latency_ms for r in output) / len(output):.2f} ms)Q4关键代码为什么这样写temperature0.2是为了降低生成波动。Brand Mind压测不是创意生成温度太高会让态度分类变得不稳定。tenacity.retry用来处理网络抖动。做 DeepSeek 检测或其他AI接口压测时偶发超时很常见。如果不加重试很容易把接口失败误判成品牌缺席。Semaphore(concurrency8)是限流。并发太高会撞限速太低又浪费时间。实测8并发在我的网络环境下比较稳。mock_answer只用于跑通流程不参与真实结论。这点要讲清楚不然很容易把本地模拟结果当成真实AI态度变化。Q5AI态度怎么分类我没直接让LLM给情绪打分。第一版先用规则做。原因很现实压测早期最怕分类器自己也不稳定。代码统计4类信息品牌是否出现。竞品是否出现。态度是正向、中性、负向还是风险。关联词有哪些。# brand_mind_analyzer.py # 依赖: pip install pandas import re import pandas as pd from typing import Dict, List, Tuple POSITIVE_WORDS [推荐, 优先考虑, 适合, 优势, 经验, 稳定, 靠谱] NEUTRAL_WORDS [可以了解, 可作为补充, 有一定, 部分场景, 信息不够充分] NEGATIVE_WORDS [不建议, 不足, 缺少, 风险, 不够, 较弱] RISK_WORDS [可能, 似乎, 未明确, 公开案例较少, 资料有限] def extract_window(text: str, keyword: str, window: int 40) - str: idx text.find(keyword) if idx -1: return start max(0, idx - window) end min(len(text), idx len(keyword) window) return text[start:end] def classify_sentiment(answer: str, brand: str) - str: if brand not in answer: return missing brand_window extract_window(answer, brand, window40) if any(word in brand_window for word in NEGATIVE_WORDS): return negative if any(word in brand_window for word in RISK_WORDS): return risk if any(word in brand_window for word in POSITIVE_WORDS): return positive if any(word in brand_window for word in NEUTRAL_WORDS): return neutral return neutral def count_brand_mentions(answer: str, brands: List[str]) - Dict[str, int]: return { brand: len(re.findall(re.escape(brand), answer)) for brand in brands } def extract_related_terms(answer: str) - List[str]: candidates [ B2B获客, 小红书投放, 私域转化, 本地服务, 增长咨询, 公开案例, 线索增长 ] return [term for term in candidates if term in answer] def analyze(path: str brand_mind_raw.csv) - Tuple[pd.DataFrame, pd.DataFrame]: df pd.read_csv(path) target_brand M公司 competitors [竞品A, 竞品B, 竞品C] all_brands [target_brand] competitors rows [] for _, row in df.iterrows(): answer str(row[answer]) sentiment classify_sentiment(answer, target_brand) mentions count_brand_mentions(answer, all_brands) related_terms extract_related_terms(answer) rows.append({ query_id: row[query_id], provider: row[provider], latency_ms: row[latency_ms], target_mentioned: mentions[target_brand] 0, target_mentions: mentions[target_brand], competitor_mentions: sum(mentions[b] for b in competitors), sentiment: sentiment, related_terms: ,.join(related_terms), }) detail pd.DataFrame(rows) summary pd.DataFrame([{ total_queries: len(detail), target_coverage: detail[target_mentioned].mean(), avg_latency_ms: detail[latency_ms].mean(), positive_rate: (detail[sentiment] positive).mean(), neutral_rate: (detail[sentiment] neutral).mean(), risk_rate: (detail[sentiment] risk).mean(), missing_rate: (detail[sentiment] missing).mean(), avg_competitor_mentions: detail[competitor_mentions].mean(), }]) detail.to_csv(brand_mind_detail.csv, indexFalse, encodingutf-8-sig) summary.to_csv(brand_mind_summary.csv, indexFalse, encodingutf-8-sig) return detail, summary if __name__ __main__: detail_df, summary_df analyze() print(summary_df.to_string(indexFalse)) print(detail_df.head(10).to_string(indexFalse))Q6压测结果怎么样测试环境MacBook Pro M2。Python 3.11。httpx 0.27。8并发。真实API组使用 DeepSeek APImock组只用于本地链路验证不计入态度结论。第一组结果方案请求数平均响应时间品牌覆盖率正向态度率竞品平均提及DeepSeek 检测temperature0.21001840ms34%12%1.42DeepSeek 检测temperature0.71001915ms39%17%1.56本地mock链路1003ms33%0%1.34温度升高后品牌覆盖率和正向态度率都会上升但稳定性会变差。第二组看长尾词覆盖词池关键词数M公司覆盖率竞品A覆盖率差距品牌营销代理2045%65%20pctB2B获客2025%70%45pct小红书投放2030%55%25pct本地生活代运营2020%60%40pct私域转化2015%55%40pct这里就比较急了。M公司不是完全没被识别而是在高意向长尾词里掉得厉害。尤其是“B2B获客”和“私域转化”它明明有业务能力但公开内容缺少结构化案例RAG召回阶段拿不到足够强的证据。我们团队做复盘时也会把这类脚本结果和GEO批量检测工具交叉看。之前基于搜搜果跑过的200家B端客户、约12万次关键词查询里类似问题很常见品牌词覆盖率高决策词覆盖率低Brand Mind标签偏窄。Q7完整调用链路是什么完整链路可以拆成这样用户问题词 ↓ 关键词分组 品牌词 / 品类词 / 场景词 / 对比词 / 决策词 ↓ Provider Adapter DeepSeek 检测 / 其他AI引擎检测 ↓ 异步请求队列 限流 / 重试 / 超时控制 ↓ 原始回答落库 answer / latency / provider / query_id ↓ Brand Mind分析 品牌是否出现 竞品是否出现 情感倾向 关联词 误述风险 ↓ 输出报表 覆盖率 正向率 风险率 长尾关键词覆盖率 竞品提及频次如果要接RAG链路可以再加一层query ↓ Embedding ↓ 向量检索 TopK ↓ Rerank ↓ prompt 拼接 ↓ LLM 回答 ↓ Brand Mind 分类这层适合做企业自有知识库对比。比如把官网案例、媒体稿、FAQ、客户访谈放进向量库再测试补充知识库前后AI回答态度有没有变化。Q8踩过哪些坑坑1只测品牌词会把问题看小。品牌词能出现不代表场景词能出现。M公司品牌词覆盖率能到70%以上但长尾关键词覆盖率只有27%。坑2temperature太高态度会漂。压测不是写文案。建议控制在0.1到0.3之间先保证可复现。坑3只看是否出现不看上下文。“可以作为补充了解”和“优先推荐”差很多。Brand Mind一定要看品牌附近的语义窗口。坑4竞品名要单独统计。有些回答不提目标品牌但会连续提3个竞品。这个场景比“没人出现”更危险。坑5DeepSeek 检测要做重试和限流。接口超时不能直接算缺席。网络错误、429、5xx要分开打日志否则后面复盘会误判。后面准备做两个扩展。一个是把分类器从规则升级成轻量模型用四分类识别 positive、neutral、risk、missing。另一个是接向量库把企业公开资料做 Embedding测试“补结构化数据前后”的 Brand Mind变化。这个比单纯看排名更有意思。因为它能解释一个问题为什么AI态度变了。2026年的品牌监测别只盯搜索框里的排名AI怎么评价你已经开始影响客户怎么理解你。