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:
@@ -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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ public static class AiChatEndpoints
|
||||
HttpContext http,
|
||||
DeepSeekClient llmClient,
|
||||
PromptManager promptManager,
|
||||
FastGptKnowledgeClient knowledgeClient,
|
||||
IAiToolExecutionService toolExecution,
|
||||
IAiWriteConfirmationStore confirmations,
|
||||
IAiConversationService conversations,
|
||||
@@ -110,9 +111,24 @@ public static class AiChatEndpoints
|
||||
var systemPrompt = promptManager.GetSystemPrompt(parsedType);
|
||||
var patientContext = await patientContexts.BuildAsync(userId.Value, ct);
|
||||
|
||||
// ── RAG 知识库检索 ──
|
||||
// 仅对需要医学知识的 Agent 启用检索;记数据/管用药/排运动这些纯工具调用类跳过。
|
||||
// 失败/空结果不阻塞流程,仅作为 prompt 增强。
|
||||
var knowledgeSnippet = "";
|
||||
if (NeedsKnowledgeBase(parsedType) && knowledgeClient.IsEnabled)
|
||||
{
|
||||
knowledgeSnippet = await knowledgeClient.SearchAsync(message, ct);
|
||||
}
|
||||
|
||||
var enhancedSystem = systemPrompt + "\n\n当前患者信息:\n" + patientContext;
|
||||
if (!string.IsNullOrWhiteSpace(knowledgeSnippet))
|
||||
{
|
||||
enhancedSystem += "\n\n知识库参考资料(如与用户问题相关请优先采用):\n" + knowledgeSnippet;
|
||||
}
|
||||
|
||||
var messages = new List<ChatMessage>
|
||||
{
|
||||
new() { Role = "system", Content = systemPrompt + "\n\n当前患者信息:\n" + patientContext },
|
||||
new() { Role = "system", Content = enhancedSystem },
|
||||
};
|
||||
|
||||
// 加载历史对话(最近 10 条)
|
||||
@@ -303,6 +319,20 @@ public static class AiChatEndpoints
|
||||
|
||||
// ── Agent / Tool 调度 ──
|
||||
|
||||
/// <summary>
|
||||
/// 哪些 Agent 需要走 FastGPT 知识库 RAG。
|
||||
/// 纯工具调用类(Health/Medication/Exercise)跳过 RAG,避免拖慢响应。
|
||||
/// </summary>
|
||||
private static bool NeedsKnowledgeBase(AgentType agentType) => agentType switch
|
||||
{
|
||||
AgentType.Default => true,
|
||||
AgentType.Consultation => true,
|
||||
AgentType.Diet => true,
|
||||
AgentType.Report => true,
|
||||
AgentType.Unified => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private static List<ToolDefinition> GetToolsForAgent(AgentType agentType) => agentType switch
|
||||
{
|
||||
AgentType.Health => HealthDataAgentHandler.Tools,
|
||||
|
||||
@@ -162,6 +162,12 @@ builder.Services.AddHttpClient<VisionClient>(client =>
|
||||
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", builder.Configuration["VLM_API_KEY"] ?? "");
|
||||
client.Timeout = TimeSpan.FromSeconds(120);
|
||||
});
|
||||
builder.Services.AddHttpClient<FastGptKnowledgeClient>(client =>
|
||||
{
|
||||
client.BaseAddress = new Uri((builder.Configuration["FASTGPT_BASE_URL"] ?? "https://cloud.fastgpt.cn/api").TrimEnd('/') + "/");
|
||||
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", builder.Configuration["FASTGPT_API_KEY"] ?? "");
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
});
|
||||
|
||||
// ---- 后台服务 ----
|
||||
builder.Services.AddHostedService<MedicationReminderService>();
|
||||
|
||||
Reference in New Issue
Block a user