公司动态

用Agent打造个性化推荐流:从内容采集到大模型排序的完整实现

📅 2026/8/31 20:34:14
用Agent打造个性化推荐流:从内容采集到大模型排序的完整实现
B站、小红书、YouTube、Twitter 的首页推荐本质上是四个黑盒。平台决定我们看到什么用户只能靠点赞、收藏、拉黑反复调教算法却始终拿不到推荐结果背后的完整依据。开源社区里出现了一类新的 Agent 应用通过 Agent 主动抓取你指定的内容源再交给大模型统一排序和过滤最终把平台首页的推荐区域替换成一份属于你自己的信息流。GitHub 上已经出现 1500 Stars 的开源项目做这件事这类项目也经常出现在 Agent 公开赛的作品方向里。这篇文章会拆解这类“个性化推荐 Agent”的完整实现链路从整体架构、依赖环境、核心模块、最小可运行流程到参数调优、常见问题排查和扩展方向。读完你不仅能快速跑通一个属于自己的推荐流还能理解它背后的采集、用户画像、大模型排序和浏览器注入这几个关键环节是怎么串起来的。1. 先搞懂“用 Agent 替换推荐流”的完整链路很多人第一次看到这个项目标题时第一反应是“怎么可能替换平台推荐”。其实替换的不是平台算法而是平台首页的推荐区域。要做到这一点需要先理解它背后的链路。1.1 平台推荐为什么需要被替换平台推荐算法的目标和用户的目标并不完全一致。平台追求的是停留时长、互动率和广告曝光而用户想要的是“不遗漏我真正关心的内容”。这两个目标长时间博弈就会产生两类典型问题信息茧房你点过一次某类内容算法就会不断强化同类推荐跨领域内容很难进来。推荐理由不透明系统告诉你“猜你喜欢”但从不告诉你为什么喜欢你也没办法批量纠正。Agent 方案的核心思路是把“内容选择权”从平台算法手里拿回来。Agent 只做三件事一是按你的偏好去固定渠道抓内容二是用大模型理解这些内容和你画像的关系三是把筛选结果重新填回平台首页。推荐逻辑从“平台猜你”变成“你自己定义”。1.2 一条完整的 Agent 推荐链路这类项目的整体架构通常分成三层第一层是采集层。Agent 需要先拿到原始内容来源包括 RSS 订阅源、平台开放 API、用户授权的浏览器扩展抓取等。这一层解决的是“内容从哪来”的问题。以视频平台为例最常见的接入方式是订阅创作者的 RSS 输出这样不需要登录态也能获取到最新的发布列表。第二层是理解层。原始内容不能直接推给用户Agent 会先做去重、格式清洗再把标题、摘要、标签整合成结构化数据交给大模型进行相关性判断和排序。这一层解决的是“哪些内容值得看”的问题。第三层是展示层。处理完的结果需要有一个出口常见做法是浏览器扩展用 content script 把平台首页的推荐 DOM 区域替换成 Agent 生成的推荐列表。也可以输出成独立 Web 页面或者推送到 IM 机器人。链路本身并不复杂难点在于每一层的稳健性。采集层要应对反爬和页面结构变化理解层要控制大模型输出的稳定性展示层要适配不同平台的前端结构。1.3 为什么是 Agent而不是普通规则引擎你可能会想抓取内容后直接用关键词过滤不就行了吗为什么要引入大模型 Agent普通规则只能处理“标题包含某个词”这种显式匹配。比如你设置关键词“大模型”一条标题是“大模型在 CRM 系统中的应用”会被抓进来但一条标题是“智能客服背后的推理引擎”很可能被漏掉因为它是同义表达。Agent 做的事情是语义理解。它会把你的偏好描述、历史推荐记录和当前候选内容一起放进 Prompt让大模型判断“这条内容是否值得推荐给你”并返回推荐理由。同样是上面的例子Agent 能根据语义判断出“智能客服背后的推理引擎”和“大模型”高度相关。另外Agent 可以处理“协同过滤”做不到的冷启动问题。没有历史行为数据时规则引擎无从下手而 Agent 只需要你写一段 200 字的兴趣描述就能开始工作。2. 环境准备跑通这个开源项目需要什么在动手之前先把环境对齐。很多人在配置阶段就中断不是因为代码难度而是因为 Python 版本、Node 版本、模型 API 配置没有提前确认。2.1 最小环境清单这个开源项目的前端展示层通常基于浏览器扩展核心处理逻辑基于 Python 或 Node.js。以下面的清单为准软件版本建议用途Python3.10 及以上Agent 核心逻辑、内容采集、LLM 调用Node.js18 及以上浏览器扩展构建、前端展示层Git2.x克隆代码仓库Chrome 或 Edge最新稳定版加载浏览器扩展替换平台首页Docker可选统一依赖环境适合不想污染本机环境的人大模型 API Key视项目要求支持 OpenAI 兼容接口即可也可以配本地模型如果你的机器上同时有 Python 2 和 Python 3需要确认默认python指向哪个版本。推荐使用python3命令显式执行。原始项目如果没有给出明确版本要求落地前要先去 README 确认依赖版本尤其是 Python 包和 Node 包的版本兼容性。学习环境里可以按上述清单来生产环境建议用 Docker 做依赖隔离。2.2 获取开源项目在 GitHub 上找到目标仓库后有两个获取方式。第一个是直接克隆git clone https://github.com/你的目标仓库地址.git cd 目标仓库目录第二个是下载 release 包适合只需要运行、不准备改源码的场景。下载后解压目录结构和克隆的一致。如果 release 下载较慢可以从能访问的代码托管镜像同步或者切换 release 的下载来源但不要尝试绕过任何网络限制。项目的文档通常会在 README 开头写清安装命令先读 README 再操作能省掉很多弯路。2.3 第一个配置文件先理解配置项的含义进入项目目录后通常会看到一个示例配置文件。先把示例复制成正式配置cp config.example.json config.json cp profile.example.yaml profile.yaml一个典型的配置结构如下{ model: { provider: openai-compatible, base_url: https://api.example.com/v1, api_key: sk-xxxx, model_name: gpt-4o-mini, temperature: 0.3 }, platforms: { bilibili: { enabled: true, target_url: https://www.bilibili.com/, sources: [rss:https://example.com/bilibili/feed] }, xiaohongshu: { enabled: true, target_url: https://www.xiaohongshu.com/, sources: [rss:https://example.com/xhs/feed] } }, output: { path: ./output/feed.json, format: json }, schedule: { enabled: false, interval_minutes: 30 } }这里有几个关键点需要解释base_url是模型接口地址只要兼容 OpenAI 的/chat/completions接口就可以不同服务商填不同地址。temperature控制推荐结果的随机性值越大结果越多变值越小越稳定。推荐任务建议设置在 0.2 到 0.4 之间不要太高。platforms里每个平台都需要一个target_url这是浏览器扩展注入时定位首页 DOM 用的。sources是内容源列表常见前缀是rss:也支持api:和extension:类型。配置完成后先不急着启动。你应该先确认 API Key 是否有效最简单的方式是用 curl 调用一次模型接口避免后面排查问题时分不清是网络问题还是代码问题。3. 核心模块拆解Agent 如何完成“读懂你”这件事这个项目最核心的部分不是采集而是 Agent 如何“读懂你”。这一节拆开来看四个模块各自承担什么职责。3.1 内容采集器把散落的信息源汇成原始语料采集器的目标是统一内容格式。无论内容来自 RSS、开放 API 还是浏览器扩展最后都要转换成下面这样的结构{ id: bilibili-123456, source: bilibili, title: 用 Agent 改造信息流从入门到实践, url: https://www.bilibili.com/video/BVxxxx, content: 视频简介、字幕或摘要文本, tags: [Agent, 信息流, 开源], published_at: 2025-06-01T10:00:00Z, author: 某创作者 }统一格式非常重要。因为后续大模型排序时只会读取这些字段不会去原始页面重新抓取。如果采集器输出格式不一致Agent 的 Prompt 就无法稳定工作。不同内容源有自己的限制RSS 订阅源内容更新有延迟但结构稳定适合追踪固定创作者。开放 API返回数据完整但多数平台需要申请权限且有调用频率限制。浏览器扩展采集能拿到登录后可见的信息但受页面结构影响大平台改版后需要同步更新选择器。学习阶段建议先接 RSS因为它最稳定不需要维护登录态。3.2 用户画像不需要训练模型用文件描述“你是谁”Agent 推荐系统不需要你在本地训练模型。它把用户画像做成一个可维护的配置文件通常是 YAML 或 JSONprofile: description: 我是一名后端开发者关注大模型应用、Agent 开发、分布式系统。 对穿搭、明星八卦、娱乐综艺完全不感兴趣。 favorite_sources: - 某技术博主 - 某开源项目作者 topics: 后端开发: weight: 8 大模型应用: weight: 10 分布式系统: weight: 7 娱乐八卦: weight: 0 exclude_keywords: - 带货 - 抽奖 - 明星同款 recency_days: 7这份画像文件会随着 Agent 的 Prompt 一起发送给大模型作为判断依据。它的重要性不亚于代码本身description给出一段整体描述让模型理解你的背景。topics给主题分配权重权重越高相关内容的排序越靠前。exclude_keywords是硬过滤条件命中直接丢弃。favorite_sources用于保证你关注的创作者最新内容一定出现在推荐结果里。画像文件的价值在于你可以随时调整不需要重新训练任何模型。今天想多看点 AI 内容把权重从 8 调成 10 即可。3.3 决策引擎让大模型做语义排序和过滤Agent 的决策引擎是一段循环逻辑。它读取候选内容列表把它们和用户画像一起交给大模型要求模型返回带排序结果的 JSON。下面这段代码是决策引擎的最小流程示意import json from openai import OpenAI client OpenAI( base_urlconfig[model][base_url], api_keyconfig[model][api_key] ) def build_prompt(profile, candidates): candidate_text \n.join( f{i1}. [{item[source]}] {item[title]} | {item.get(content, )[:200]} for i, item in enumerate(candidates) ) return f 你是一个个性化推荐引擎。请根据用户的画像对候选内容进行评分和排序。 用户画像 {profile} 候选内容 {candidate_text} 要求 1. 只能使用候选内容不能编造不存在的内容。 2. 排除与 exclude_keywords 匹配的内容。 3. 对每个候选内容打分 0-100按分数从高到低排列。 4. 输出 JSON格式为{{recommendations: [{{index: 1, score: 80, reason: 推荐理由}}]}} def rank(profile, candidates): response client.chat.completions.create( modelconfig[model][model_name], temperatureconfig[model][temperature], messages[ {role: system, content: 你是推荐引擎只输出 JSON。}, {role: user, content: build_prompt(profile, candidates)} ], response_format{type: json_object} ) return json.loads(response.choices[0].message.content) recommendations rank(profile, candidates)这段代码有三个关键设计第一个是response_format。强制模型输出 JSON减少解析失败的概率。如果你的模型接口不支持该参数就需要在 Prompt 里反复强调“只输出 JSON不要解释”并在代码里做容错解析。第二个是候选内容截断。给模型的每条内容只保留前 200 字摘要避免超出上下文窗口。长视频简介和长文章会被截断所以采集阶段就应该做好文本清洗。第三个是推荐理由。模型必须输出reason字段这样做有两个作用一是让你能验证推荐逻辑是否正确二是展示层可以直接展示“为什么推荐这条”。3.4 展示层把推荐结果放回平台首页Agent 计算出推荐结果后展示层负责把它渲染到平台首页。浏览器扩展是最常见的实现方式。扩展在后台读取output/feed.json通过 content script 定位平台首页的推荐区域替换成 Agent 生成的推荐列表。核心逻辑是这样的// content.js async function replaceRecommendationFeed() { const feed await fetch(chrome.runtime.getURL(output/feed.json)).then(res res.json()); const container document.querySelector(#recommend-container); if (!container) return; const list document.createElement(div); list.className agent-recommend-list; for (const item of feed.recommendations) { const card document.createElement(a); card.href item.url; card.target _blank; card.innerHTML div classagent-recommend-title${item.title}/div div classagent-recommend-reason${item.reason}/div div classagent-recommend-meta${item.source} · ${item.score} 分/div ; list.appendChild(card); } container.innerHTML ; container.appendChild(list); } replaceRecommendationFeed();这里最麻烦的是#recommend-container选择器。不同平台、不同时间段的页面结构不同选择器很容易失效。建议在扩展的配置中心维护一份“平台选择器映射表”平台改版后只需要更新选择器不需要重新构建扩展。4. 用 10 分钟跑通最小流程从空配置到看见自己的推荐流环境准备完成后整个验证流程可以压缩到 10 分钟左右。这里给出一条可复现的操作路径。4.1 初始化配置并检查接口可用性先回到项目目录确认真实配置已经就位ls -la config.json profile.yaml然后验证模型接口curl -X POST ${base_url}/chat/completions \ -H Authorization: Bearer ${api_key} \ -H Content-Type: application/json \ -d {model:gpt-4o-mini,messages:[{role:user,content:ping}],max_tokens:10}如果接口返回 JSON 且包含choices字段说明模型配置可用。这一步能排除后面 80% 的“推荐结果为空”问题。4.2 填写一份最简单的用户画像不需要一开始就把画像写得很复杂。先只写最关键的部分确认链路通后再逐步细化profile: description: 我是一名后端开发者关注大模型和 Agent 开发。 topics: 大模型: weight: 10 exclude_keywords: - 抽奖重点在于先让流程跑通画像的准确度可以之后迭代。4.3 启动 Agent 并观察日志不同项目的启动命令略有差异但整体逻辑一致。先安装依赖再执行入口脚本pip install -r requirements.txt python main.py --once--once表示执行一轮完整流程后退出适合首次验证。预期日志会经历三个阶段[INFO] 采集完成共 45 条候选内容来自 3 个内容源 [INFO] 调用模型排列 45 条候选内容... [INFO] 推荐生成完成保留 12 条输出到 ./output/feed.json如果日志卡在“调用模型”阶段优先检查 API Key 和网络连通性。如果日志提示候选内容为 0则问题在采集层。4.4 验证推荐结果打开输出文件cat output/feed.json一个正常的结果应该长这样{ generated_at: 2025-06-01T12:00:00Z, recommendations: [ { title: 用 Agent 重构个人知识库, source: bilibili, url: https://www.bilibili.com/video/BVxxxx, score: 92, reason: 内容涉及 Agent 开发实践与用户关注的大模型应用方向高度相关。 } ] }如果能看到带score和reason的推荐结果说明核心链路已经打通。接下来再加载浏览器扩展进入平台首页确认推荐区域是否被替换。注意第一次验证时优先看feed.json不要急着打开浏览器。后端输出正确再排查前端注入问题边界会清晰很多。5. 推荐质量由哪些参数决定跑通之后你会进入调优阶段。推荐效果不好通常不是代码问题而是参数设置问题。5.1 大模型参数速查参数建议值影响错误设置的表现temperature0.2 - 0.4结果随机性过高时推荐结果不稳定同样的内容每次排序不同top_p0.8 - 0.9采样范围过小时内容过于保守过大时可能引入无关内容max_tokens800 - 2000输出长度过短时推荐理由被截断JSON 解析失败request_timeout30 - 60 秒接口等待时间过短时模型没返回就超时生成失败recency_days7内容时效范围过大会推荐很多过时内容其中温度参数最容易忽略。推荐任务属于“需要稳定判断”的场景temperature 不建议超过 0.5。如果你想每次运行结果都一样可以设置为 0但实际使用中 0.2 左右更平衡。5.2 内容源优先级的权重设计内容源不是完全平等的。你关注的创作者发布的内容应该比泛关键词抓取的内容权重更高。在采集阶段可以给每个来源打上权重{ sources: [ {type: rss, url: https://example.com/feed, weight: 10}, {type: rss, url: https://example.com/hot-topic, weight: 5} ] }权重字段会拼进 Prompt让模型参考。实际效果中高权重来源的内容即便分数略低也值得保留。这个逻辑很像“社交关系优先”的推荐策略与其全站推荐不如先把信任的人的内容看完。5.3 去重与更新频率推荐流最忌讳重复。你昨天看过的内容今天不应该再出现在首页。项目通常会在输出前做两层去重URL 精确去重同一个视频链接直接丢弃。标题相似度去重使用difflib或rapidfuzz计算标题相似度高于 0.85 视为重复。from rapidfuzz import fuzz def is_duplicate(title, seen_titles, threshold85): for seen in seen_titles: if fuzz.ratio(title, seen) threshold: return True return False定时运行场景下还要把上一次输出的推荐标题作为seen_titles输入避免长期运行后出现“每天推荐同样的内容”的问题。5.4 更新频率的生产配置学习阶段可以手动执行生产环境建议用系统定时任务*/30 * * * * cd /path/to/project python main.py --once logs/agent.log 21每 30 分钟执行一次既能保证信息流新鲜度又不会频繁触发平台的反爬或 API 限流。如果内容源数量很大建议把频率降到每小时一次。6. 常见问题排查从现象到根因这类项目涉及采集、模型调用、文件读写、浏览器注入多个环节出问题时定位链路比搜错误信息更高效。下面按现象列出排查路径。6.1 内容采集不到数据现象日志显示候选内容为 0推荐列表为空。按顺序检查RSS 地址是否正确用浏览器直接打开订阅地址看返回内容。是否添加了User-Agent请求头部分平台对裸请求直接拒绝。是否触发了频率限制连续请求时观察 HTTP 返回状态码。平台是否改版导致解析选择器失效。常见原因集中在 RSS 地址错误和平台反爬。学习环境可以先抓取一个稳定的小型 RSS 源快速验证链路。6.2 大模型返回 JSON 解析失败现象日志报JSONDecodeError或推荐结果缺失recommendations字段。可能原因有三个模型输出被max_tokens截断。把max_tokens调大到 1500 以上。模型接口不支持response_format但代码里仍然传了该参数。检查模型服务商文档。Prompt 中的示例格式与解析代码不一致。统一使用严格 JSON 格式并要求模型只输出 JSON。建议增加一个兜底解析函数从模型输出中提取第一个{到最后一个}之间的内容再解析import json def safe_json_loads(text): start text.find({) end text.rfind(}) if start -1 or end -1: raise ValueError(未找到 JSON 内容) return json.loads(text[start:end 1])6.3 推荐结果每次都不一样现象内容源没变但两次运行得到的排序差异很大。主要原因是temperature设置过高。推荐场景要求可复现性把 temperature 降到 0.2 左右。另外如果 Prompt 里没有给足排序规则模型会按自己的理解随机调整需要在 Prompt 里明确“严格按用户主题权重评分”。6.4 浏览器扩展不生效现象feed.json正常但平台首页没有变化。排查顺序扩展是否启用了“开发人员模式”并重新加载了最新代码。feed.json是否在扩展可访问的目录下。浏览器扩展无法直接读取项目目录的本地文件需要把输出文件复制到扩展目录或通过后台脚本注入。页面选择器是否匹配。在浏览器控制台执行document.querySelector(#recommend-container)返回null说明选择器失效。是否有 CSP 限制。部分平台禁止网页加载外部资源需要把推荐列表数据直接嵌入 content script。6.5 模型调用费用过高现象每轮处理几百条内容Token 消耗量巨大。解决办法是分层过滤。先用规则的exclude_keywords和来源权重减少候选集再用一个轻量模型做粗排最后用主模型做精排。推荐场景不需要每一条内容都用最强模型处理。7. 从玩一玩到生产可用扩展方向与安全边界最小流程跑通后这个项目可以往多个方向扩展。扩展时要时刻注意隐私、成本和稳定性。7.1 扩展更多内容源不要局限于视频平台。博客 RSS、播客、论文 ArXiv、Hacker News 都可以成为 Agent 的内容源。接入新源时只需要做两步一是写一个适配器把源内容转换成统一的 JSON 结构二是把源地址加到config.json的sources列表。7.2 把推荐结果发送到不同终端除了浏览器扩展推荐结果还可以输出到自建 Web 页面、企业微信群机器人、飞书机器人或邮件摘要。以 Web 页面为例只需要启动一个静态文件服务指向output目录python -m http.server 8080 --directory output然后访问http://localhost:8080/feed.json就能看到结构化推荐结果。组合定时任务后每天早上自动生成一份“今日信息流”。7.3 隐私与数据安全边界这个项目最大的优点也是最大的责任用户画像在你本地抓取数据也在你本地。生产化时要遵守这些边界API Key 不要写死在仓库里使用环境变量或本地密钥文件。采集数据不要长期留存建议每次运行前清理旧缓存只保留最近三天。用户画像文件不要同步到公开仓库加入.gitignore。涉及平台 API 时先确认对方的使用条款避免违规调用。注意本项目适合个人学习、研究和个人信息流管理。如果需要扩大使用范围要重新评估内容源的授权边界和平台使用规则。7.4 生产环境的额外保障个人使用和生产使用差别很大。生产环境至少还要补上几块第一是日志。每次运行要记录采集数量、模型调用耗时、Token 消耗、推荐结果数量方便成本核算和故障排查。第二是监控。可以设置一个简单的健康检查比如每轮运行结束后在output/health.json写入时间戳超过指定时长未更新则告警。第三是回滚。修改画像或 Prompt 后先备份上一版运行对比两版推荐结果效果下降时能快速回退。第四是模型降级。主模型不可用时可以配置一个备选模型或者直接暂停 Agent 运行避免输出空推荐流。如果想把项目分享给更多人还应该补充一个“快速开始脚本”把环境检查、依赖安装、配置校验合并成一个命令降低使用门槛。最后给新手一个练习建议不要一上来就接入四个平台。先用一个 RSS 源和一个视频平台跑通全链路理解采集、画像、排序、展示这四段流程后再逐步增加内容源。等你能独立解释“为什么推荐这条视频”时说明你已经完全掌握这类 Agent 项目的核心逻辑了。