一个输入框搞定两种模式,GitHub 热榜 AI 解读器实战复盘

📅 发布时间:2026/8/27 16:39:41
一个输入框搞定两种模式,GitHub 热榜 AI 解读器实战复盘
从“陪聊”到“产品”一个输入框背后的双重工作流做 AI 应用最容易踩的坑是把“能让模型回答”误认为“做出了一个产品”。真正能拿来展示的工具至少要回答三个核心问题数据从哪里来流程怎么控制用户在哪里使用很多开发者在尝试构建 GitHub 热榜分析工具时往往止步于一个简单的问答窗口用户上传文档模型回答。但这对于需要高频关注技术趋势的独立开发者或产品负责人来说远远不够。我们需要的不只是一个能聊天的机器人而是一套面向技术调研的自动化报告生成链路。这次实战复盘的项目OpenRadar就是为了解决这个痛点而生。它没有复杂的模式切换按钮页面上只有一个简洁的输入框。你可以直接问“今天 GitHub 有什么热门 Python 项目”也可以丢进去一个仓库地址说“帮我分析一下这个项目”。看似简单的交互背后是一套精密编排的双路工作流前端通过 Next.js 服务端代理隔离密钥后端利用 Dify Chatflow 自动识别意图将请求分流至“热榜趋势分析”或“指定仓库深度解读”两条路径。架构设计把复杂度留给工作流把简单留给用户OpenRadar 的核心设计理念是“无感知的模式切换”。在传统应用中用户可能需要先选择“查询热榜”还是“分析仓库”再填写语言、时间范围等参数。这种表单式的交互虽然逻辑清晰但极大地增加了用户的认知负担。在这个项目中我们将所有复杂度都隐藏在了工作流内部。前端只负责接收自然语言输入而“理解用户意图”这一关键任务完全交给了 Dify 的 LLM 节点。整体架构分为三层接入层EdgeOne Pages Next.js负责前端渲染和服务端代理。关键点在于Dify 的 API Key 绝不暴露在浏览器端所有请求均通过 Next.js 的 API Route 转发确保密钥安全。编排层Dify Chatflow这是大脑所在。它接收标准化后的查询通过意图识别节点判断走向调用 HTTP 请求节点获取真实 GitHub 数据经过代码节点清洗后最终由 LLM 生成结构化报告。数据层GitHub Public API Trending Page提供真实的元数据、README 内容以及非结构化的热榜 HTML 页面。这种分层设计带来了一个显著优势调试变得异常清晰。如果报告生成出错我们可以迅速定位是数据抓取失败、清洗逻辑有误还是模型推理偏差而不必在一大段生成文字里盲目猜测。意图识别与输入规范化让模型做路由让代码做兜底工作流的起点是一个看似简单的“开始节点”它只接收一个变量sys.query用户输入。紧接着第一个关键节点登场——LLM 意图识别。这个节点的任务不是回答问题而是充当“路由器”。它需要判断用户是想看trending热榜还是要分析repository指定仓库。如果是热榜还需提取出语言如 Python、Go和时间范围daily、weekly如果是仓库分析则必须精准提取出 GitHub URL。然而依赖大模型进行参数提取存在不确定性。用户输入的格式千奇百怪有人贴完整 URLhttps://github.com/owner/repo有人只写owner/repo甚至有人在 URL 后加中文标点。如果直接将这些原始数据传给后续的 HTTP 请求节点极易导致接口调用失败。因此在意图识别之后我们插入了一个**代码节点Code Node**作为“输入规范化”的兜底策略。这段代码不负责语义理解只负责严格的格式校验与清洗。以下是一个典型的 URL 提取与补全逻辑片段import re import html def extract_repo_url(text: str) - str: # 定义多种可能的 URL 匹配模式 patterns [ rhttps?://github\.com/[A-Za-z0-9_.-]/[A-Za-z0-9_.-](?:\.git)?, rgitgithub\.com:[A-Za-z0-9_.-]/[A-Za-z0-9_.-](?:\.git)?, rgithub\.com/[A-Za-z0-9_.-]/[A-Za-z0-9_.-](?:\.git)? ] for pattern in patterns: match re.search(pattern, text or , re.I) if not match: continue value match.group(0).rstrip(.,;,.) # 去除末尾标点 if value.startswith(github.com/): value https:// value # 补全协议头 return value return # 执行提取并 fallback repo_url extract_repo_url(raw_input) or fallback_url这段逻辑确保了无论用户如何输入传递给下游 GitHub API 的都是标准的https://github.com/owner/repo格式。这种LLM 理解语义 Code 兜底校验”的组合拳是构建高稳定性 AI 工作流的关键。热榜分支实战从杂乱 HTML 到结构化情报当意图识别判定为“热榜查询”时工作流进入第一条分支。这里有一个常见的误区直接使用 GitHub Search API 模拟热榜。实际上Search API 返回的是按搜索条件匹配的仓库列表与 GitHub 官方 Trending 页面所展示的“短期热度趋势”并不完全等同。为了获取最真实的热榜数据我们选择直接请求 GitHub Trending 的 HTML 页面。但这带来了新问题HTML 包含大量导航栏、脚本、广告和无用文本直接塞给 LLM 不仅浪费 Token还会干扰推理。解决方案是在 HTTP 请求节点后再接一个代码节点进行数据清洗。这个节点的核心任务是从原始 HTML 中剥离出项目卡片并提取关键字段。我们使用正则表达式定位article标签从中抽取仓库名、描述、编程语言、今日 Star 增量等信息。以下是一个简化的清洗思路import re from bs4 import BeautifulSoup # 或使用内置 html 解析 def clean_trending_html(html_content: str): articles re.findall(rarticle\b[\s\S]*?/article, html_content, re.I) projects [] for article in articles[:5]: # 仅取前 5 个避免报告过长 # 提取 owner 和 repo name repo_match re.search(rhref/([^/\s])/([^/\s]), article, re.I) if not repo_match: continue owner html.unescape(repo_match.group(1)).strip() repo html.unescape(repo_match.group(2)).strip() full_name f{owner}/{repo} # 提取描述信息 desc_match re.search(rclass[^]*col-9[^]*[^]*([\s\S]*?)/p, article) description desc_match.group(1).strip() if desc_match else No description # 提取语言 lang_match re.search(ritempropprogrammingLanguage[^]*([\s\S]*?)/span, article) language lang_match.group(1).strip() if lang_match else Unknown projects.append({ name: full_name, url: fhttps://github.com/{full_name}, description: description, language: language }) return str(projects) # 转为字符串供后续 LLM 使用经过这一步清洗传递给 LLM 的不再是几万字的 HTML 源码而是一份干净的 JSON 列表。模型基于这些结构化数据能够更准确地生成趋势摘要、推荐表格以及风险提示而不是泛泛而谈Python 很热门”。仓库分析分支压缩上下文让报告有据可依第二条分支针对指定仓库的分析。如果直接把整个仓库的代码扔给模型不仅超出上下文限制而且效率极低。我们的策略是只读取元数据、README 和文件树结构。工作流通过 HTTP 请求节点并行获取三类数据仓库元信息包括 Stars 数、Forks 数、License 类型、主要语言分布等。README 内容了解项目定位、功能说明及启动方式。根目录文件树判断项目结构、关键配置文件如package.json,go.mod,Dockerfile的存在情况。同样这里也需要一个代码节点将这三部分数据压缩成一段紧凑的repo_digest_markdown。例如过滤掉 README 中的图片链接、 badges只保留文本大纲将文件树简化为层级缩进文本。这样做的目的是让模型在生成报告时“有据可依”。当模型指出“该项目采用 React 19 技术栈”时它是基于package.json中的依赖版本得出的结论而非凭空猜测。这种基于证据的分析大大提升了报告的可信度使其真正成为开发者选型前的有效参考。前端交付流式渲染与安全代理后端工作流跑通了最后一步是让用户看到结果。OpenRadar 的前端基于 Next.js 构建部署在 EdgeOne Pages 上。密钥隔离是首要原则。我们在.env.local中配置 Dify 的 API Key并在 Next.js 的 API Route 中创建服务端代理。前端页面只发送用户查询不携带任何敏感凭证。// pages/api/chat.ts import { ChatClient } from dify-client; export default async function handler(req, res) { const chatClient new ChatClient( process.env.DIFY_API_KEY, process.env.DIFY_API_URL ); const response await chatClient.createChatMessage( {}, req.body.query, openradar-app-id, true // 开启流式输出 ); res.setHeader(Content-Type, text/event-stream); res.send(response.data); }流式渲染优化则是提升体验的关键。由于报告生成涉及多次 API 调用和数据清洗耗时可能在几秒到十几秒之间。为了让用户感知到进度前端采用了流式接收方案。同时针对模型输出的 Markdown 内容我们没有手写正则去解析标题和列表而是引入了marked解析器配合DOMPurify进行 sanitization。这不仅解决了代码块、表格、引用块的渲染问题还防止了潜在的 XSS 攻击。当报告生成完毕用户不仅可以滚动阅读还能一键复制原始 Markdown 内容方便粘贴到自己的笔记或团队文档中。结语让工具回归工具的本质跑通整个流程后OpenRadar 不再是一个简单的“问答机器人”而是一个具备数据处理、逻辑判断和报告生成能力的微型 SaaS。对于独立开发者而言这套架构的价值在于其可复用性。无论是分析 GitHub 热榜还是监控其他技术社区的趋势核心的“意图识别 - 数据清洗 - 报告生成”链路都可以无缝迁移。蓝耘元生代 MaaS 提供的 OpenAI 兼容接口让我们可以灵活切换 DeepSeek-V3.2 或其他模型而无需改动业务逻辑Dify 的可视化编排则降低了维护成本让非后端背景的开发者也能轻松调整工作流细节。下次当你看到一个突然冲上热榜的仓库不必再手动点开 README 逐行阅读。把这个地址丢进工具等它整理好技术栈、目录结构和潜在风险你再决定是否需要投入时间去深入研究。这才是 AI 工具应有的样子。