至从折腾完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实现,用魔法对决魔法,不知是增加了人的思考空间,还是限制人的行动空间.

image-1746125483834