NewsHub——人工智能驱动的新闻聚合平台
NewsHub——人工智能驱动的新闻聚合平台
这是参加 Redis AI 挑战赛 :实时 AI 创新者的 参赛作品。
我建造的
NewsHub 是一个智能新闻聚合平台,它彻底改变了用户发现和消费新闻内容的方式。NewsHub 以 Redis 8 作为核心实时数据层,结合了人工智能的强大功能和 Redis 的先进特性,提供个性化、情境相关的新闻体验。
这是我第一次使用 Redis,学习曲线虽然陡峭,但收获颇丰。我发现 Redis 远不止是一个缓存——它是一个功能强大的多模型平台,能够处理复杂的 AI 工作负载。构建 NewsHub 让我明白了 Redis 如何集主数据库、向量搜索引擎和实时分析平台于一体。
主要特点
人工智能驱动的内容智能
利用谷歌 Gemini AI 进行智能摘要,生成简洁、有意义的文章摘要
具备实时情绪检测(正面、负面、中性)的高级情感分析
动态关键词提取和人工智能驱动的主题识别
面向上下文感知内容分析的语义理解
高级搜索与发现
基于 Redis 的相似性匹配的向量语义搜索,使用 768 维嵌入
按主题、情感、来源、关键词和日期范围进行多方面筛选
使用余弦相似度算法的相似文章引擎
模糊搜索,支持拼写错误容错和智能建议
智能个性化
智能信息流管理,可自动从个性化信息流中移除已查看的文章
行为分析追踪阅读模式和参与度指标
基于用户交互模式的实时推荐
实时分析与洞察
通过缓存命中率、响应时间和系统健康状况进行性能监控
全面的用户旅程分析
性能与可扩展性
采用智能请求、查询和结果缓存策略的多层缓存
通过优化的 Redis 操作,响应时间低于 50 毫秒
利用实时新闻摄取和人工智能分析流程进行实时处理
建筑设计
NewsHub 采用现代微服务架构,以 Redis 8 作为核心智能层。该系统旨在实现高性能、可扩展性和实时 AI 处理。
高级系统架构
Redis 8 多模型平台架构
数据流与处理管道
演示
在线应用
Web 应用 : NewsHub 在线演示
API后端 : NewsHub API
GitHub 仓库 :
NewsHub——人工智能驱动的新闻聚合平台
NewsHub是一个现代化的全栈新闻聚合和摘要平台,采用React、TypeScript、Node.js和Redis构建。它利用Google Gemini AI和Redis向量搜索,为用户提供AI驱动的新闻摘要、个性化信息流和高级搜索功能。
🚀 功能
核心功能
AI驱动的新闻摘要 :使用谷歌Gemini AI进行智能内容分析
向量搜索与相似性 :基于 Redis 的语义搜索与嵌入
个性化新闻推送 :基于用户偏好的内容推荐
实时新闻抓取 :从多个来源自动收集新闻
高级搜索 :按主题、情感、来源和关键词筛选文章
响应式设计 :针对台式机、平板电脑和移动设备进行了优化
用户体验
无限滚动 :无缝浏览,内容自动加载
文章分析 :查看互动指标和热门文章
情感分析 :文章情感(正面、负面、中性)的可视化指标
主题筛选 :按类别和兴趣浏览新闻
实时更新 ……
YouTube演示
VIDEO
音频及辅助功能说明: 视频中如有任何背景噪音或我的口音,敬请谅解。为了获得最佳观看体验,我建议您开启字幕。字幕提供英文版本,确保您不会错过任何精彩内容!
屏幕截图
首页 - 智能新闻推送
主仪表盘包含人工智能精选新闻,并附有情感指标、互动指标和个性化推荐。
高级搜索界面
强大的搜索功能,可按主题和情感进行实时筛选
个性化文章视图
提供文章详情视图,包括人工智能生成的摘要、情感分析和相似文章推荐。
个性化“为你推荐”信息流
由人工智能精心策划的个性化新闻推送,可根据用户偏好和阅读行为自动调整。
相关文章发现
基于向量的语义相似度引擎,使用 Redis 向量搜索显示上下文相关的文章。
主要功能展示
采用向量相似性搜索的智能搜索,响应时间低于 100 毫秒
根据用户偏好进行动态内容策划,实现实时个性化
智能文章移除功能,可自动从个性化信息流中移除已阅读的文章
基于情感的过滤,带有视觉情感指标
利用人工智能进行趋势检测并结合互动指标
我如何使用 Redis 8
Redis 8 是 NewsHub 智能功能的主要实时数据层。我之前主要使用传统数据库,学习 Redis 让我大开眼界。我发现 Redis 能够在处理复杂的 AI 工作负载的同时,保持卓越的性能。
向量搜索与语义智能
这是我使用 Redis 过程中最具挑战性的部分。如何实现向量搜索和语义相似度对我来说完全是全新的领域,但结果却令人惊艳。
// redisService.js - Vector Search Implementation
class RedisService {
async createVectorIndex () {
try {
// Create vector search index for semantic similarity
await this . client . ft . create ( ' news:vector_index ' , {
' $.title ' : ' TEXT ' ,
' $.content ' : ' TEXT ' ,
' $.embedding ' : ' VECTOR ' ,
' $.sentiment ' : ' TAG ' ,
' $.topic ' : ' TAG ' ,
' $.source ' : ' TAG ' ,
' $.publishedAt ' : ' NUMERIC '
}, {
ON : ' JSON ' ,
PREFIX : ' news:articles: ' ,
FILTER : ' @publishedAt:[0 +inf] ' ,
SCHEMA : {
' $.title ' : { type : ' TEXT ' , WEIGHT : 2.0 },
' $.content ' : { type : ' TEXT ' , WEIGHT : 1.0 },
' $.embedding ' : {
type : ' VECTOR ' ,
DIM : 768 ,
DISTANCE_METRIC : ' COSINE ' ,
TYPE : ' FLOAT32 '
},
' $.sentiment ' : { type : ' TAG ' },
' $.topic ' : { type : ' TAG ' },
' $.source ' : { type : ' TAG ' },
' $.publishedAt ' : { type : ' NUMERIC ' }
}
});
console . log ( ' ✅ Vector search index created successfully ' );
} catch ( error ) {
console . log ( ' Vector index already exists or error: ' , error . message );
}
}
async findSimilarArticles ( articleId , limit = 10 ) {
try {
// Get article embedding
const article = await this . client . json . get ( `news:articles: ${ articleId } ` );
if ( ! article || ! article . embedding ) {
throw new Error ( ' Article or embedding not found ' );
}
// Vector similarity search
const query = `*=>[KNN $k @embedding $embedding AS score]` ;
const params = {
k : limit ,
embedding : article . embedding
};
const results = await this . client . ft . search ( ' news:vector_index ' , query , {
PARAMS : params ,
RETURN : [ ' $.title ' , ' $.content ' , ' $.sentiment ' , ' $.topic ' , ' score ' ],
SORTBY : ' score ' ,
DIALECT : 2
});
return results . documents . map ( doc => ({
id : doc . id . replace ( ' news:articles: ' , '' ),
title : doc . value [ ' $.title ' ],
content : doc . value [ ' $.content ' ],
sentiment : doc . value [ ' $.sentiment ' ],
topic : doc . value [ ' $.topic ' ],
similarity : doc . value . score
}));
} catch ( error ) {
console . error ( ' Error finding similar articles: ' , error );
throw error ;
}
}
}
Enter fullscreen mode
Exit fullscreen mode
支持全文搜索的 JSON 存储
学习使用 Redis 作为主数据库是一次范式转变。它能够存储复杂的 JSON 文档,同时还能保持全文搜索功能,这简直太棒了。
// redisService.js - JSON Storage Implementation
class RedisService {
async storeArticle ( article ) {
try {
const articleId = `news:articles: ${ article . id } ` ;
// Store article as JSON with full-text search capabilities
await this . client . json . set ( articleId , ' $ ' , {
id : article . id ,
title : article . title ,
content : article . content ,
summary : article . summary ,
sentiment : article . sentiment ,
topic : article . topic ,
source : article . source ,
url : article . url ,
publishedAt : article . publishedAt ,
embedding : article . embedding ,
keywords : article . keywords ,
metadata : {
wordCount : article . wordCount ,
readingTime : article . readingTime ,
language : article . language
}
});
// Create search index for full-text search
await this . createSearchIndex ();
console . log ( `✅ Article stored: ${ articleId } ` );
return article . id ;
} catch ( error ) {
console . error ( ' Error storing article: ' , error );
throw error ;
}
}
async searchArticles ( query , filters = {}) {
try {
let searchQuery = `(@title: ${ query } | @content: ${ query } | @summary: ${ query } )` ;
// Add filters
if ( filters . sentiment ) {
searchQuery += ` @sentiment:{ ${ filters . sentiment } }` ;
}
if ( filters . topic ) {
searchQuery += ` @topic:{ ${ filters . topic } }` ;
}
if ( filters . source ) {
searchQuery += ` @source:{ ${ filters . source } }` ;
}
if ( filters . dateRange ) {
searchQuery += ` @publishedAt:[ ${ filters . dateRange . start } ${ filters . dateRange . end } ]` ;
}
const results = await this . client . ft . search ( ' news:search_index ' , searchQuery , {
RETURN : [ ' $.title ' , ' $.content ' , ' $.sentiment ' , ' $.topic ' , ' $.source ' ],
SORTBY : ' @publishedAt ' ,
LIMIT : { from : 0 , size : 50 }
});
return results . documents . map ( doc => ({
id : doc . id . replace ( ' news:articles: ' , '' ),
title : doc . value [ ' $.title ' ],
content : doc . value [ ' $.content ' ],
sentiment : doc . value [ ' $.sentiment ' ],
topic : doc . value [ ' $.topic ' ],
source : doc . value [ ' $.source ' ]
}));
} catch ( error ) {
console . error ( ' Error searching articles: ' , error );
throw error ;
}
}
}
Enter fullscreen mode
Exit fullscreen mode
多层缓存策略
理解 Redis 缓存模式对提升性能至关重要。我实现了一种复杂的多层缓存机制,显著提高了响应速度。
// cacheClearService.js - Multi-layer Caching Implementation
class CacheClearService {
constructor ( redisClient ) {
this . client = redisClient ;
this . cacheLayers = {
REQUEST : ' cache:requests: ' ,
QUERY : ' cache:queries: ' ,
RESULT : ' cache:results: ' ,
SIMILARITY : ' cache:similarity: ' ,
USER : ' cache:user: '
};
}
async setCacheWithTTL ( key , data , ttl = 3600 ) {
try {
const cacheKey = `cache:results: ${ key } ` ;
await this . client . json . set ( cacheKey , ' $ ' , {
data : data ,
timestamp : Date . now (),
ttl : ttl
});
await this . client . expire ( cacheKey , ttl );
console . log ( `✅ Cache set: ${ cacheKey } ` );
} catch ( error ) {
console . error ( ' Error setting cache: ' , error );
}
}
async getCache ( key ) {
try {
const cacheKey = `cache:results: ${ key } ` ;
const cached = await this . client . json . get ( cacheKey );
if ( cached && cached . data ) {
console . log ( `✅ Cache hit: ${ cacheKey } ` );
return cached . data ;
}
console . log ( `❌ Cache miss: ${ cacheKey } ` );
return null ;
} catch ( error ) {
console . error ( ' Error getting cache: ' , error );
return null ;
}
}
}
Enter fullscreen mode
Exit fullscreen mode
实时个性化引擎
正是在个性化系统中,我学习到了 Redis 的高级数据结构。实现从个性化订阅源中自动删除文章的功能,是一次非常宝贵的学习经历。
// redisService.js - User Preferences Implementation
class RedisService {
async storeUserPreferences ( userId , preferences ) {
try {
const userKey = `user:preferences: ${ userId } ` ;
await this . client . hset ( userKey , {
topics : JSON . stringify ( preferences . topics ),
sources : JSON . stringify ( preferences . sources ),
sentiment : preferences . sentiment ,
language : preferences . language ,
updatedAt : Date . now ()
});
// Set expiration for user preferences (30 days)
await this . client . expire ( userKey , 30 * 24 * 60 * 60 );
console . log ( `✅ User preferences stored: ${ userKey } ` );
return true ;
} catch ( error ) {
console . error ( ' Error storing user preferences: ' , error );
throw error ;
}
}
async getPersonalizedNews ( userId , limit = 20 ) {
try {
const preferences = await this . getUserPreferences ( userId );
if ( ! preferences ) {
return await this . getTrendingArticles ( limit );
}
// Build personalized search query
let searchQuery = ' * ' ;
const filters = [];
if ( preferences . topics . length > 0 ) {
filters . push ( `@topic:{ ${ preferences . topics . join ( ' | ' )} }` );
}
if ( preferences . sources . length > 0 ) {
filters . push ( `@source:{ ${ preferences . sources . join ( ' | ' )} }` );
}
if ( preferences . sentiment ) {
filters . push ( `@sentiment:{ ${ preferences . sentiment } }` );
}
if ( filters . length > 0 ) {
searchQuery = filters . join ( ' ' );
}
const results = await this . client . ft . search ( ' news:search_index ' , searchQuery , {
RETURN : [ ' $.title ' , ' $.content ' , ' $.sentiment ' , ' $.topic ' , ' $.source ' ],
SORTBY : ' @publishedAt ' ,
LIMIT : { from : 0 , size : limit }
});
return results . documents . map ( doc => ({
id : doc . id . replace ( ' news:articles: ' , '' ),
title : doc . value [ ' $.title ' ],
content : doc . value [ ' $.content ' ],
sentiment : doc . value [ ' $.sentiment ' ],
topic : doc . value [ ' $.topic ' ],
source : doc . value [ ' $.source ' ]
}));
} catch ( error ) {
console . error ( ' Error getting personalized news: ' , error );
throw error ;
}
}
// Auto-remove viewed articles from personalized feed
async trackArticleView ( userId , articleId ) {
try {
// Add to viewed articles set
await this . client . sadd ( `user:viewed: ${ userId } ` , articleId );
// Remove from personalized feed
await this . client . srem ( `user:personalized: ${ userId } ` , articleId );
// Track engagement metrics
await this . trackEngagement ( articleId , ' view ' );
console . log ( `✅ Article view tracked and removed from personalized feed: ${ articleId } ` );
} catch ( error ) {
console . error ( ' Error tracking article view: ' , error );
}
}
}
Enter fullscreen mode
Exit fullscreen mode
热门文章与分析
学习使用 Redis 有序集合来实现热门算法真是太棒了。能够直接在 Redis 中实现时间衰减算法,简直是颠覆性的突破。
// redisService.js - Trending Articles Implementation
class RedisService {
async updateTrendingArticles () {
try {
// Get all articles with engagement metrics
const articles = await this . client . ft . search ( ' news:search_index ' , ' * ' , {
RETURN : [ ' $.title ' , ' $.source ' , ' $.publishedAt ' ],
SORTBY : ' @publishedAt ' ,
LIMIT : { from : 0 , size : 1000 }
});
const trendingScores = {};
for ( const doc of articles . documents ) {
const articleId = doc . id . replace ( ' news:articles: ' , '' );
const publishedAt = parseInt ( doc . value [ ' $.publishedAt ' ]);
const now = Date . now ();
// Calculate trending score based on recency and engagement
const timeDecay = Math . exp ( - ( now - publishedAt ) / ( 24 * 60 * 60 * 1000 )); // 24 hours
const engagementScore = await this . getEngagementScore ( articleId );
trendingScores [ articleId ] = timeDecay * engagementScore ;
}
// Store trending articles in sorted set
const trendingKey = ' trending:articles ' ;
await this . client . del ( trendingKey ); // Clear existing
for ( const [ articleId , score ] of Object . entries ( trendingScores )) {
await this . client . zadd ( trendingKey , score , articleId );
}
console . log ( `✅ Updated trending articles: ${ Object . keys ( trendingScores ). length } articles` );
} catch ( error ) {
console . error ( ' Error updating trending articles: ' , error );
throw error ;
}
}
async trackEngagement ( articleId , action = ' view ' ) {
try {
const engagementKey = `metrics:engagement: ${ articleId } ` ;
const actionKey = ` ${ action } :count` ;
await this . client . hincrby ( engagementKey , actionKey , 1 );
await this . client . hset ( engagementKey , ' last_updated ' , Date . now ());
// Set expiration for engagement metrics (7 days)
await this . client . expire ( engagementKey , 7 * 24 * 60 * 60 );
console . log ( `✅ Tracked engagement: ${ action } for article ${ articleId } ` );
} catch ( error ) {
console . error ( ' Error tracking engagement: ' , error );
}
}
}
Enter fullscreen mode
Exit fullscreen mode
绩效结果
在学习 Redis 的过程中,我取得了令人瞩目的性能指标:
缓存性能
常用数据的缓存命中率达到 85-90%
缓存响应的响应时间低于 50 毫秒
通过 LRU 淘汰策略优化内存使用
向量搜索性能
在 100 毫秒内对 1000 多篇文章进行相似性搜索
使用余弦距离的768维向量存储
使用 RedisJSON 进行高效压缩
可扩展性指标
支持 1000+ 并发用户
高效的 JSON 存储,具备全文搜索功能
个性化推送响应时间低于一秒
学习经验
这个项目让我第一次接触到 Redis,它彻底改变了我对数据存储和缓存的看法。我了解到 Redis 不仅仅是一个缓存——它是一个完整的数据平台,能够:
作为包含 JSON 文档的主数据库
为人工智能应用提供矢量搜索功能
利用有序集合和哈希处理实时分析
管理跨多层的复杂缓存策略
支持实时流媒体和发布/订阅消息传递
学习曲线非常陡峭,尤其是理解向量搜索和 Redis 的查询语法,但我所取得的性能和能力让我觉得所有的挑战都是值得的。构建 NewsHub 让我明白了 Redis 如何加速 AI 应用,并处理通常需要多个专用系统才能完成的复杂实时工作负载。
NewsHub 展示了 Redis 8 如何为下一代 AI 应用提供强大支持,使其成为一个完整的实时智能平台。向量搜索、JSON 存储、多层缓存和实时分析的结合,打造出无缝、智能的用户体验,能够实时适应用户行为和偏好。
由Varshith V Hegde 倾情打造
技术栈 :React、TypeScript、Node.js、Express、Redis 8、Google Gemini AI、Tailwind CSS
文章来源:https://dev.to/varshithvhegde/newshub-ai-powered-news-aggregation-platform-5c11