Files
Yi.Framework/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Mcp/OnlineSearchTool.cs
ccnetcore b5ff6c141c feat: 联网搜索支持按时间范围过滤
- OnlineSearch 方法新增 daysAgo 参数,支持按最近天数筛选搜索结果
- 百度搜索请求增加 search_filter 时间范围(gte/lte)
- 补充相关模型与 JSON 源生成配置
- 更新工具描述,明确近期与实时信息范围
2026-01-08 22:19:36 +08:00

252 lines
7.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.ComponentModel;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Yi.Framework.AiHub.Domain.Shared.Attributes;
namespace Yi.Framework.AiHub.Domain.Mcp;
[YiAgentTool]
public class OnlineSearchTool : ISingletonDependency
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<OnlineSearchTool> _logger;
private readonly string _baiduApiKey;
private const string BaiduSearchUrl = "https://qianfan.baidubce.com/v2/ai_search/web_search";
public OnlineSearchTool(
IHttpClientFactory httpClientFactory,
ILogger<OnlineSearchTool> logger,
IConfiguration configuration)
{
_httpClientFactory = httpClientFactory;
_logger = logger;
_baiduApiKey = configuration["BaiduSearch:ApiKey"] ?? "";
}
[YiAgentTool("联网搜索"), DisplayName("OnlineSearch"), Description("进行在线搜索获取最新的网络信息近期信息是7天实时信息是1天")]
public async Task<string> OnlineSearch([Description("搜索关键字")]string keyword, [Description("距离现在多久天")]int? daysAgo = null)
{
if (string.IsNullOrWhiteSpace(keyword))
{
return "搜索关键词不能为空";
}
try
{
var client = _httpClientFactory.CreateClient();
// 构建请求体
var requestBody = new BaiduSearchRequest
{
Messages = new List<BaiduSearchMessage>
{
new() { Role = "user", Content = keyword }
}
};
// 设置时间范围过滤
if (daysAgo.HasValue)
{
requestBody.SearchFilter = new BaiduSearchFilter
{
Range = new BaiduSearchRange
{
PageTime = new BaiduSearchPageTime
{
Gte = $"now-{daysAgo.Value}d/d",
Lte = "now"
}
}
};
}
var jsonContent = JsonSerializer.Serialize(requestBody, BaiduJsonContext.Default.BaiduSearchRequest);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
// 设置请求头
var request = new HttpRequestMessage(HttpMethod.Post, BaiduSearchUrl)
{
Content = content
};
request.Headers.Add("Authorization", $"Bearer {_baiduApiKey}");
// 发送请求
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
_logger.LogError("百度搜索接口调用失败: {StatusCode}, {Error}", response.StatusCode, errorContent);
return $"搜索失败: {response.StatusCode}";
}
var responseJson = await response.Content.ReadAsStringAsync();
var searchResult = JsonSerializer.Deserialize(responseJson, BaiduJsonContext.Default.BaiduSearchResponse);
if (searchResult?.References == null || searchResult.References.Count == 0)
{
return "未找到相关搜索结果";
}
// 格式化搜索结果返回给AI
return FormatSearchResults(searchResult.References);
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "百度搜索网络请求异常");
return "搜索服务暂时不可用,请稍后重试";
}
catch (TaskCanceledException ex)
{
_logger.LogError(ex, "百度搜索请求超时");
return "搜索请求超时,请稍后重试";
}
catch (JsonException ex)
{
_logger.LogError(ex, "百度搜索结果解析失败");
return "搜索结果解析失败";
}
catch (Exception ex)
{
_logger.LogError(ex, "百度搜索发生未知异常");
return "搜索发生异常,请稍后重试";
}
}
/// <summary>
/// 格式化搜索结果
/// </summary>
private string FormatSearchResults(List<BaiduSearchReference> references)
{
var sb = new StringBuilder();
sb.AppendLine("搜索结果:");
sb.AppendLine();
var count = 0;
foreach (var item in references.Take(10)) // 最多返回10条
{
count++;
sb.AppendLine($"【{count}】{item.Title}");
sb.AppendLine($"来源:{item.Website} | 时间:{item.Date}");
sb.AppendLine($"摘要:{item.Snippet}");
sb.AppendLine($"链接:{item.Url}");
sb.AppendLine();
}
return sb.ToString();
}
}
#region DTO
/// <summary>
/// 百度搜索请求
/// </summary>
public class BaiduSearchRequest
{
[JsonPropertyName("messages")]
public List<BaiduSearchMessage> Messages { get; set; } = new();
[JsonPropertyName("search_filter")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public BaiduSearchFilter? SearchFilter { get; set; }
}
/// <summary>
/// 百度搜索过滤器
/// </summary>
public class BaiduSearchFilter
{
[JsonPropertyName("range")]
public BaiduSearchRange? Range { get; set; }
}
/// <summary>
/// 百度搜索范围
/// </summary>
public class BaiduSearchRange
{
[JsonPropertyName("page_time")]
public BaiduSearchPageTime? PageTime { get; set; }
}
/// <summary>
/// 百度搜索时间范围
/// </summary>
public class BaiduSearchPageTime
{
[JsonPropertyName("gte")]
public string? Gte { get; set; }
[JsonPropertyName("lte")]
public string? Lte { get; set; }
}
/// <summary>
/// 百度搜索消息
/// </summary>
public class BaiduSearchMessage
{
[JsonPropertyName("role")]
public string Role { get; set; } = "user";
[JsonPropertyName("content")]
public string Content { get; set; } = "";
}
/// <summary>
/// 百度搜索响应
/// </summary>
public class BaiduSearchResponse
{
[JsonPropertyName("request_id")]
public string? RequestId { get; set; }
[JsonPropertyName("references")]
public List<BaiduSearchReference>? References { get; set; }
}
/// <summary>
/// 百度搜索结果项
/// </summary>
public class BaiduSearchReference
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("url")]
public string? Url { get; set; }
[JsonPropertyName("title")]
public string? Title { get; set; }
[JsonPropertyName("date")]
public string? Date { get; set; }
[JsonPropertyName("snippet")]
public string? Snippet { get; set; }
[JsonPropertyName("website")]
public string? Website { get; set; }
}
#endregion
#region JSON
[JsonSerializable(typeof(BaiduSearchRequest))]
[JsonSerializable(typeof(BaiduSearchResponse))]
[JsonSerializable(typeof(BaiduSearchFilter))]
[JsonSerializable(typeof(BaiduSearchRange))]
[JsonSerializable(typeof(BaiduSearchPageTime))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
internal partial class BaiduJsonContext : JsonSerializerContext
{
}
#endregion