至从折腾完RSS后,有了到手的内容,便想能不能试着用AI来帮我阅读这些内容呢?就从试用版Gemini 接口入口,整理一下过程,经测试可用待完善。大致有两套方案:
- 使用流式接口,这样方便动态计算token 大小,调用AI接口,通过AI 分析 并生成结果。
- 使用导入结构化文本,上传给AI,通过AI 分析 并生成结果。
今天整理了一下第一种方案:
流式接口
-
后端Controler
[HttpGet("stream")] public async Task StreamRssArticles([FromQuery] RssAnalysisRequest request) { try { // 设置响应头 Response.Headers.Add("Content-Type", "text/event-stream"); Response.Headers.Add("Cache-Control", "no-cache"); Response.Headers.Add("Connection", "keep-alive"); // 获取流式数据 var result = _feedService.GetFeedsByDateLazyAsync(request); // 使用 IAsyncEnumerable 的流式处理 await foreach (var feedItem in result) { // 使用 SSE 格式返回数据 var json = JsonSerializer.Serialize(feedItem); await Response.WriteAsync($"data: {json}\n\n"); // 刷新响应流 await Response.Body.FlushAsync(); // 添加小延迟 await Task.Delay(10); } } catch (Exception ex) { _logger.LogError(ex, "Error streaming RSS articles"); throw; } }
-
后端数据服务
public async IAsyncEnumerable<FeedItem> GetFeedsByDateLazyAsync(RssAnalysisRequest request) { var filterBuilder = Builders<FeedItem>.Filter; var filter = filterBuilder.Empty; // 根据请求日期条件来构建过滤器 if (!string.IsNullOrEmpty(request.Date)) { var date = DateTime.Parse(request.Date); filter = filterBuilder.And( filterBuilder.Gte(x => x.PubDate, date.Date), filterBuilder.Lt(x => x.PubDate, date.Date.AddDays(1)) ); } using (var dbContext = _databaseConfiguration.GetDbContext()) { var mongoDb = dbContext.GetMongoDb(); var month = DateTime.Now.AddMonths(-1).ToString("yyyyMM"); var collectionName = $"FeedItem_{month}"; var collection = mongoDb.GetCollection<FeedItem>(collectionName); // 使用 FindAsync 获取游标 using (var cursor = await collection.Find(filter) .SortByDescending(x => x.PubDate) .Limit(request.Limit ?? 20) .ToCursorAsync()) { // 异步遍历游标 while (await cursor.MoveNextAsync()) { foreach (var article in cursor.Current) { yield return article; // 减少延迟时间,提高响应速度 await Task.Delay(10); } } } } }
前端调用
-
这里简单在winform上加增加一个会话按钮来模拟,后面自动化请求
private async void btnSendRequest_Click(object sender, EventArgs e) { try { string userPrompt = txtUserInput.Text; conversationHistory.AppendLine($"User: {userPrompt}"); // 创建请求对象 var request = new RssAnalysisRequest { Date = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd"), Limit = 2 }; // 显示加载状态 richTextBoxResponse.AppendText("正在获取文章...\n"); richTextBoxResponse.Refresh(); // 获取流式文章 var articles = GetRssArticlesAsync(request); var finalArticles = new StringBuilder(); // 处理流式返回的文章 await foreach (var article in articles) { finalArticles.AppendLine($"标题: {article.Title}, 链接: {article.Link}"); richTextBoxResponse.AppendText($"收到文章: {article.Title}\n"); richTextBoxResponse.Refresh(); } // 生成分析报告 var context = new AnalysisContext { UserInput = userPrompt, Date = DateTime.Now.ToString("yyyy-MM-dd") }; // 显示正在生成报告 richTextBoxResponse.AppendText("\n正在生成分析报告...\n"); richTextBoxResponse.Refresh(); // 生成最终分析 var responseContent = await GenerateFinalAnalysisFromStreamAsync(context, articles); if (responseContent != null) { richTextBoxResponse.AppendText($"\nAI: {responseContent}\n"); conversationHistory.AppendLine($"AI: {responseContent}"); } else { MessageBox.Show("无法从 API 获取响应。"); } } catch (Exception ex) { MessageBox.Show($"发生错误: {ex.Message}"); } } private async IAsyncEnumerable<FeedItem> GetRssArticlesAsync(RssAnalysisRequest request) { var handler = new HttpClientHandler { ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true }; using (var client = new HttpClient(handler)) { client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("text/event-stream")); var queryString = ToQueryString(request); var url = $"https://localhost:7129/api/Analysis/Rss/stream?{queryString}"; //调用后台接口 using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead)) { response.EnsureSuccessStatusCode(); using (var stream = await response.Content.ReadAsStreamAsync()) using (var reader = new StreamReader(stream)) { while (!reader.EndOfStream) { var line = await reader.ReadLineAsync(); if (!string.IsNullOrWhiteSpace(line) && line.StartsWith("data: ")) { var json = line.Substring(6); // 移除 "data: " 前缀 var feedItem = JsonConvert.DeserializeObject<FeedItem>(json); yield return feedItem; } } } } } } private async Task<string> GenerateFinalAnalysisFromStreamAsync(AnalysisContext context, IAsyncEnumerable<FeedItem> articles) { var prompt = $@"基于以下分析上下文,生成最终的分析报告: {JsonConvert.SerializeObject(context)} 请生成一个结构化的报告,包含: 1. 今日重要新闻(按优先级排序,包含链接) 2. 主要趋势分析 3. 个性化推荐 4. 关键洞察 请保持简洁明了,突出重点。"; var finalArticles = new StringBuilder(); await foreach (var article in articles) { finalArticles.AppendLine($"标题: {article.Title}, 链接: {article.Link}"); } prompt += $"\n\n以下是获取的 RSS 文章:\n{finalArticles.ToString()}"; return await CallApiAsync(prompt); } private async Task<string> CallApiAsync(string prompt) { var handler = new HttpClientHandler { Proxy = new System.Net.WebProxy("socks5://127.0.0.1:18988"), UseProxy = true, ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true }; using (var client = new HttpClient(handler)) { client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); // 构造请求负载 var requestPayload = new { contents = new[] { new { parts = new[] { new { text = prompt } } } }, generationConfig = new { temperature = 0.9, topK = 40, topP = 0.95, maxOutputTokens = 1024, } }; var jsonPayload = JsonConvert.SerializeObject(requestPayload); var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); // 发送请求 var response = await client.PostAsync($"{apiUrl}?key={apiKey}", content); if (response.IsSuccessStatusCode) { var responseString = await response.Content.ReadAsStringAsync(); var responseObject = JsonConvert.DeserializeObject<dynamic>(responseString); return responseObject.candidates[0].content.parts[0].text; } else { var responseContent = await response.Content.ReadAsStringAsync(); MessageBox.Show($"API请求失败: {response.StatusCode} - {responseContent}"); return null; } } } private string ToQueryString(RssAnalysisRequest request) { var properties = request.GetType().GetProperties(); var keyValuePairs = properties .Select(p => $"{Uri.EscapeDataString(p.Name)}={Uri.EscapeDataString(p.GetValue(request)?.ToString() ?? "")}"); return string.Join("&", keyValuePairs); }
最后上菜
大致模拟了,从请求动态数据,到组织数据再拼提示词给Gemini的简单实现,可能是数据量的原因,Gemini回答的也很简单。后面再对比测试上传数据的反馈结果。
从数据到行为,都是AI启发再到AI实现,用魔法对决魔法,不知是增加了人的思考空间,还是限制人的行动空间.