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 "";
}
}
}

View File

@@ -33,6 +33,7 @@ public static class AiChatEndpoints
HttpContext http, HttpContext http,
DeepSeekClient llmClient, DeepSeekClient llmClient,
PromptManager promptManager, PromptManager promptManager,
FastGptKnowledgeClient knowledgeClient,
IAiToolExecutionService toolExecution, IAiToolExecutionService toolExecution,
IAiWriteConfirmationStore confirmations, IAiWriteConfirmationStore confirmations,
IAiConversationService conversations, IAiConversationService conversations,
@@ -110,9 +111,24 @@ public static class AiChatEndpoints
var systemPrompt = promptManager.GetSystemPrompt(parsedType); var systemPrompt = promptManager.GetSystemPrompt(parsedType);
var patientContext = await patientContexts.BuildAsync(userId.Value, ct); 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> var messages = new List<ChatMessage>
{ {
new() { Role = "system", Content = systemPrompt + "\n\n当前患者信息\n" + patientContext }, new() { Role = "system", Content = enhancedSystem },
}; };
// 加载历史对话(最近 10 条) // 加载历史对话(最近 10 条)
@@ -303,6 +319,20 @@ public static class AiChatEndpoints
// ── Agent / Tool 调度 ── // ── 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 private static List<ToolDefinition> GetToolsForAgent(AgentType agentType) => agentType switch
{ {
AgentType.Health => HealthDataAgentHandler.Tools, AgentType.Health => HealthDataAgentHandler.Tools,

View File

@@ -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.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", builder.Configuration["VLM_API_KEY"] ?? "");
client.Timeout = TimeSpan.FromSeconds(120); 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>(); builder.Services.AddHostedService<MedicationReminderService>();

View File

@@ -8,7 +8,7 @@ import 'local_database.dart';
/// Android 真机 WiFi: flutter run --dart-define=API_BASE_URL=http://电脑IP:5000 /// Android 真机 WiFi: flutter run --dart-define=API_BASE_URL=http://电脑IP:5000
const String baseUrl = String.fromEnvironment( const String baseUrl = String.fromEnvironment(
'API_BASE_URL', 'API_BASE_URL',
defaultValue: 'http://192.168.1.29:5000', defaultValue: 'http://10.4.170.202:5000',
); );
class ApiException implements Exception { class ApiException implements Exception {