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,
|
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,
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user