feat: 接入FastGPT知识库RAG检索,AI回答前先查医学资料

- 新增 FastGptKnowledgeClient:调用 searchTest 接口检索知识库
- ai_chat_endpoints: 调 DeepSeek 之前先检索,把相关片段拼到 system prompt
- 5个智能体(default/consultation/diet/report/unified)启用 RAG
- 3个纯工具调用智能体(health/medication/exercise)保持原样
- .env: 新增 FastGPT 配置(API Key、datasetId、相似度等)
- api_client: 更新默认 baseUrl 适配新网络环境
This commit is contained in:
MingNian
2026-06-22 20:50:32 +08:00
parent 73d99d56f6
commit 7ff429e071
4 changed files with 109 additions and 2 deletions

View File

@@ -0,0 +1,71 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Health.Infrastructure.AI;
/// <summary>
/// FastGPT 知识库检索客户端(仅做 RAG 检索,不参与生成)。
/// 我们后端的 DeepSeek 依旧负责对话生成与工具调用;
/// 这里只把检索到的医学知识片段拼回 system prompt提升回答准确度。
/// </summary>
public sealed class FastGptKnowledgeClient(HttpClient http, IConfiguration config, ILogger<FastGptKnowledgeClient> logger)
{
private readonly HttpClient _http = http;
private readonly string _datasetId = config["FASTGPT_DATASET_ID"] ?? "";
private readonly float _similarity = float.TryParse(config["FASTGPT_SIMILARITY"], out var s) ? s : 0.4f;
private readonly int _limit = int.TryParse(config["FASTGPT_LIMIT"], out var l) ? l : 3000;
private readonly ILogger<FastGptKnowledgeClient> _logger = logger;
private readonly JsonSerializerOptions _jsonOptions = new() { PropertyNameCaseInsensitive = true };
public bool IsEnabled => !string.IsNullOrWhiteSpace(_datasetId);
/// <summary>
/// 按用户问题检索知识库,返回最相关的文档片段。
/// 失败或未配置时返回空字符串调用方应当容错——RAG 只是增强,不是必需。
/// </summary>
public async Task<string> SearchAsync(string question, CancellationToken ct = default)
{
if (!IsEnabled || string.IsNullOrWhiteSpace(question)) return "";
try
{
var payload = new
{
datasetId = _datasetId,
text = question,
limit = _limit,
similarity = _similarity,
searchMode = "embedding",
};
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
using var response = await _http.PostAsync("core/dataset/searchTest", content, ct);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("FastGPT 检索失败: {Status}", response.StatusCode);
return "";
}
var body = await response.Content.ReadAsStringAsync(ct);
using var doc = JsonDocument.Parse(body);
if (!doc.RootElement.TryGetProperty("data", out var data)) return "";
if (!data.TryGetProperty("list", out var list) || list.ValueKind != JsonValueKind.Array) return "";
var snippets = new List<string>();
foreach (var item in list.EnumerateArray())
{
var q = item.TryGetProperty("q", out var qEl) ? qEl.GetString() ?? "" : "";
var a = item.TryGetProperty("a", out var aEl) ? aEl.GetString() ?? "" : "";
if (string.IsNullOrWhiteSpace(q) && string.IsNullOrWhiteSpace(a)) continue;
snippets.Add(string.IsNullOrWhiteSpace(a) ? q : $"{q}{a}");
}
return snippets.Count == 0 ? "" : string.Join("\n", snippets);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "FastGPT 检索异常,已忽略");
return "";
}
}
}