欢迎光临
我们一直在努力

一个 Agent 如何接入百炼、Dify 和 RAGFlow:用适配器隔离不同 RAG 配置

本文基于一个真实的 Agent 项目,介绍如何通过统一的 IKnowledge 接口和 KnowledgeFactory 工厂,将百炼、Dify、RAGFlow 三种知识库适配到同一套 Agent 架构中。文章会结合项目中的数据库结构、配置对象、适配器实现和 Agent 装配代码,说明不同供应商的配置如何转换成统一的 Knowledge 对象,并分析这种设计的优点和当前实现边界。


一、为什么 Agent 需要统一接入多个 RAG 服务

一个 Agent 的核心流程通常是:

用户输入问题
|
v
Agent 判断是否需要知识检索
|
v
调用知识库进行检索
|
v
把检索结果交给大模型
|
v
生成最终回答

如果项目只接入一个知识库,可以直接写成:

BailianKnowledge knowledge =
BailianKnowledge.builder()
.config(config)
.build();

但当平台同时支持百炼、Dify 和 RAGFlow 时,问题就会变成:

当前 Agent 使用哪个知识库?
百炼需要哪些参数?
Dify 的检索模式怎么配置?
RAGFlow 的数据集如何传递?
不同供应商的重排序配置如何统一?

如果没有适配层,Agent 代码可能出现大量判断:

if (kbType == BAILIAN) {
// 构造百炼配置
} else if (kbType == DIFY) {
// 构造 Dify 配置
} else if (kbType == RAGFLOW) {
// 构造 RAGFlow 配置
}

这种代码有几个问题:

  • Agent 业务逻辑和供应商 SDK 强耦合;
  • 新增知识库供应商时需要修改 Agent 主流程;
  • 每个供应商的 JSON 字段散落在多个业务类中;
  • 不同供应商的参数校验标准不统一;
  • 很难通过后台配置动态切换知识库。
  • 本项目采用的解决方案是:

    Agent 主流程
    |
    v
    KnowledgeFactory
    |
    v
    IKnowledge 统一适配接口
    |
    +—— BailianIKnowledge
    |
    +—— DifyIKnowledge
    |
    +—— RagFlowIKnowledge


    二、项目中的核心设计

    项目中知识库适配的核心接口是:

    agentOri/core/src/main/java/com/hxh/apboa/core/knowledge/IKnowledge.java

    代码如下:

    public interface IKnowledge
    extends SmartInitializingSingleton {

    Knowledge build(
    KnowledgeBaseConfig knowledgeBaseConfig
    );

    Object build(
    KnowledgeBaseConfig knowledgeBaseConfig,
    boolean isOld
    );

    KbType type();

    @Override
    default void afterSingletonsInstantiated() {
    KnowledgeFactory.register(this);
    }

    boolean lightHealthCheck(
    KnowledgeBaseConfig knowledgeBaseConfig
    );
    }

    这个接口定义了每个知识库适配器必须具备的能力:

    方法作用
    build(config) 根据数据库配置创建 Agentscope Knowledge
    build(config, isOld) 兼容旧构建方式的接口
    type() 返回当前适配器对应的知识库类型
    lightHealthCheck() 轻量健康检查接口
    afterSingletonsInstantiated() Spring 启动后自动注册适配器

    三种实现分别是:

    BailianIKnowledge
    DifyIKnowledge
    RagFlowIKnowledge

    对应类型:

    public enum KbType {
    BAILIAN("百炼"),
    DIFY("Dify"),
    RAGFLOW("Ragflow");
    }


    三、数据库如何保存统一知识库配置

    项目使用 knowledge_base_config 表保存知识库配置:

    CREATE TABLE `knowledge_base_config` (
    `id` bigint NOT NULL,
    `name` varchar(100) NOT NULL COMMENT '知识库名称',
    `kb_type` enum('BAILIAN','DIFY','RAGFLOW')
    NOT NULL COMMENT '知识库类型',
    `rag_mode` enum('GENERIC','AGENTIC')
    NOT NULL DEFAULT 'GENERIC'
    COMMENT '集成模式',
    `description` varchar(500) DEFAULT NULL,
    `connection_config` text NOT NULL COMMENT '连接配置',
    `endpoint_config` text DEFAULT NULL COMMENT '端点配置',
    `retrieval_config` text DEFAULT NULL COMMENT '检索配置',
    `reranking_config` text DEFAULT NULL COMMENT '重排序配置',
    `query_rewrite_config` text DEFAULT NULL COMMENT '查询重写配置',
    `metadata_filters` text DEFAULT NULL COMMENT '元数据过滤',
    `http_config` text DEFAULT NULL COMMENT 'HTTP配置',
    `enabled` tinyint(1) NOT NULL DEFAULT 1,
    `health_status` enum(
    'HEALTHY',
    'UNHEALTHY',
    'UNKNOWN'
    ) DEFAULT 'UNKNOWN',
    `last_sync_time` datetime DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_kb_name` (`name`),
    KEY `idx_kb_type` (`kb_type`),
    KEY `idx_kb_enabled` (`enabled`)
    ) COMMENT='知识库配置表';

    实体类:

    @Getter
    @Setter
    @TableName(
    value = "knowledge_base_config",
    autoResultMap = true
    )
    public class KnowledgeBaseConfig
    extends BaseEntity {

    private String name;

    private KbType kbType;

    private RAGMode ragMode;

    private String description;

    @TableField(
    typeHandler = JsonNodeTypeHandler.class
    )
    private JsonNode connectionConfig;

    @TableField(
    typeHandler = JsonNodeTypeHandler.class
    )
    private JsonNode endpointConfig;

    @TableField(
    typeHandler = JsonNodeTypeHandler.class
    )
    private JsonNode retrievalConfig;

    @TableField(
    typeHandler = JsonNodeTypeHandler.class
    )
    private JsonNode rerankingConfig;

    @TableField(
    typeHandler = JsonNodeTypeHandler.class
    )
    private JsonNode queryRewriteConfig;

    @TableField(
    typeHandler = JsonNodeTypeHandler.class
    )
    private JsonNode metadataFilters;

    @TableField(
    typeHandler = JsonNodeTypeHandler.class
    )
    private JsonNode httpConfig;

    private HealthStatus healthStatus;

    private LocalDateTime lastSyncTime;
    }

    这里没有把百炼、Dify、RAGFlow 的所有字段都拆成固定列,而是使用多个 JSON 配置字段:

    connection_config
    endpoint_config
    retrieval_config
    reranking_config
    query_rewrite_config
    metadata_filters
    http_config

    这样做的好处是:

    通用表结构
    供应商差异放入 JSON
    新增配置字段不需要频繁修改数据库表

    但代价是:

    字段类型检查需要由适配器完成
    配置错误只能在运行时或保存时发现
    不同供应商的 JSON 结构需要清晰约定


    四、KnowledgeFactory 如何选择适配器

    项目中的工厂类:

    @Component
    @RequiredArgsConstructor
    public class KnowledgeFactory {

    private static final Map<KbType, IKnowledge>
    KNOWLEDGE_MAP =
    new ConcurrentHashMap<>();

    private final KnowledgeBaseConfigService
    knowledgeBaseConfigService;

    public KnowledgeWrapper getKnowledge(
    AgentDefinition definition) {

    KnowledgeBaseConfig config =
    knowledgeBaseConfigService.getByAgentId(
    definition.getId()
    );

    if (config == null) {
    return null;
    }

    if (!config.getEnabled()) {
    return null;
    }

    IKnowledge knowledge =
    KNOWLEDGE_MAP.get(config.getKbType());

    if (knowledge == null) {
    return null;
    }

    return KnowledgeWrapper.builder()
    .ragMode(config.getRagMode())
    .knowledge(knowledge.build(config))
    .build();
    }

    public static void register(
    IKnowledge knowledge
    ) {
    KNOWLEDGE_MAP.put(
    knowledge.type(),
    knowledge
    );
    }
    }

    工厂的执行逻辑是:

    1. 根据 Agent ID 查询绑定的知识库配置
    2. 判断配置是否存在
    3. 判断知识库是否启用
    4. 根据 kbType 查找适配器
    5. 调用适配器 build()
    6. 返回 KnowledgeWrapper

    KnowledgeWrapper 用于同时保存知识库对象和 RAG 模式:

    @Getter
    @Setter
    @Builder
    public class KnowledgeWrapper {

    private RAGMode ragMode;

    private Knowledge knowledge;
    }

    适配器注册依赖 Spring 生命周期:

    @Override
    default void afterSingletonsInstantiated() {
    KnowledgeFactory.register(this);
    }

    例如:

    @Component
    public class BailianIKnowledge
    implements IKnowledge {

    @Override
    public KbType type() {
    return KbType.BAILIAN;
    }
    }

    Spring 初始化完成后,百炼适配器会自动注册到:

    KNOWLEDGE_MAP

    最终得到:

    BAILIAN -> BailianIKnowledge
    DIFY -> DifyIKnowledge
    RAGFLOW -> RagFlowIKnowledge

    这就是工厂和适配器结合的地方:

    业务代码只认识 IKnowledge
    具体供应商由 KbType 决定


    五、Agent 如何真正使用知识库

    适配器创建出的 Knowledge 最终会在 Agent 构建阶段使用。

    项目中的 ReActAgentHelper:

    public ReActAgent.Builder getReActAgentBuilder(
    AgentDefinition definition) {

    Model model =
    chatModelFactory.getModel(definition);

    ReActAgent.Builder builder =
    ReActAgent.builder()
    .name(definition.getAgentCode())
    .description(
    FuncUtils.isEmpty(
    definition.getDescription()
    )
    ? definition.getName()
    : definition.getDescription()
    )
    .maxIters(
    definition.getMaxIterations()
    )
    .model(model)
    .sysPrompt(
    agentSysPromptFactory
    .getAgentSysPrompt(
    definition
    )
    );

    KnowledgeWrapper knowledgeWrapper =
    knowledgeFactory.getKnowledge(definition);

    if (knowledgeWrapper != null) {
    builder.knowledge(
    knowledgeWrapper.getKnowledge()
    );

    builder.ragMode(
    knowledgeWrapper.getRagMode()
    );

    builder.retrieveConfig(
    RetrieveConfig.builder()
    .limit(5)
    .scoreThreshold(0.5)
    .build()
    );
    }

    return builder;
    }

    Agent 主流程并不关心当前使用的是:

    百炼
    Dify
    RAGFlow

    它只接收:

    Knowledge
    RAGMode
    RetrieveConfig

    这就是适配层带来的隔离效果。


    六、百炼适配器的实现

    百炼适配器:

    core/knowledge/impl/BailianIKnowledge.java

    核心方法:

    @Component
    public class BailianIKnowledge
    implements IKnowledge {

    @Override
    public Knowledge build(
    KnowledgeBaseConfig config) {

    try {
    validateConfig(config);

    BailianConfig bailianConfig =
    buildBailianConfig(config);

    return BailianKnowledge.builder()
    .config(bailianConfig)
    .build();

    } catch (Exception e) {
    log.error(
    "Failed to build Bailian knowledge configuration",
    e
    );

    throw new IllegalArgumentException(
    "Invalid Bailian knowledge configuration",
    e
    );
    }
    }

    @Override
    public KbType type() {
    return KbType.BAILIAN;
    }
    }

    6.1 百炼连接配置

    百炼必需字段:

    accessKeyId
    accessKeySecret
    workspaceId
    indexId

    校验代码:

    private void validateConfig(
    KnowledgeBaseConfig config) {

    if (config == null) {
    throw new IllegalArgumentException(
    "KnowledgeBaseConfig cannot be null"
    );
    }

    if (config.getConnectionConfig() == null
    || config.getConnectionConfig().isEmpty()) {
    throw new IllegalArgumentException(
    "Connection config is required for Bailian knowledge"
    );
    }

    JsonNode connectionConfig =
    config.getConnectionConfig();

    validateRequiredField(
    connectionConfig,
    "accessKeyId",
    "accessKeyId"
    );

    validateRequiredField(
    connectionConfig,
    "accessKeySecret",
    "accessKeySecret"
    );

    validateRequiredField(
    connectionConfig,
    "workspaceId",
    "workspaceId"
    );

    validateRequiredField(
    connectionConfig,
    "indexId",
    "indexId"
    );
    }

    构造百炼配置:

    private void applyConnectionConfig(
    BailianConfig.Builder builder,
    JsonNode connectionConfig) {

    builder.accessKeyId(
    JsonUtils.getStringValue(
    connectionConfig,
    "accessKeyId",
    true
    )
    );

    builder.accessKeySecret(
    JsonUtils.getStringValue(
    connectionConfig,
    "accessKeySecret",
    true
    )
    );

    builder.workspaceId(
    JsonUtils.getStringValue(
    connectionConfig,
    "workspaceId",
    true
    )
    );

    builder.indexId(
    JsonUtils.getStringValue(
    connectionConfig,
    "indexId",
    true
    )
    );
    }

    实际配置结构可以是:

    {
    "accessKeyId": "your-access-key-id",
    "accessKeySecret": "your-access-key-secret",
    "workspaceId": "your-workspace-id",
    "indexId": "your-index-id",
    "saveRetrieverHistory": false
    }

    文章中不要填写真实密钥。

    6.2 百炼端点配置

    默认端点:

    private static final String
    DEFAULT_ENDPOINT =
    "bailian.cn-beijing.aliyuncs.com";

    自定义端点:

    {
    "endpoint": "bailian.cn-beijing.aliyuncs.com"
    }

    代码:

    private void applyEndpointConfig(
    BailianConfig.Builder builder,
    JsonNode endpointConfig) {

    if (endpointConfig != null
    && !endpointConfig.isEmpty()) {

    String endpoint =
    JsonUtils.getStringValue(
    endpointConfig,
    "endpoint",
    false
    );

    builder.endpoint(
    Objects.requireNonNullElse(
    endpoint,
    DEFAULT_ENDPOINT
    )
    );

    } else {
    builder.endpoint(DEFAULT_ENDPOINT);
    }
    }

    6.3 百炼检索配置

    百炼支持稠密检索和稀疏检索 Top K:

    {
    "denseSimilarityTopK": 100,
    "sparseSimilarityTopK": 100
    }

    代码会限制范围:

    private int clamp(int value) {
    return Math.max(
    0,
    Math.min(100, value)
    );
    }

    构造配置:

    private void applyRetrievalConfig(
    BailianConfig.Builder builder,
    JsonNode retrievalConfig) {

    int denseTopK =
    JsonUtils.getIntValue(
    retrievalConfig,
    "denseSimilarityTopK",
    100
    );

    int sparseTopK =
    JsonUtils.getIntValue(
    retrievalConfig,
    "sparseSimilarityTopK",
    100
    );

    denseTopK = clamp(denseTopK);
    sparseTopK = clamp(sparseTopK);

    builder.denseSimilarityTopK(denseTopK)
    .sparseSimilarityTopK(sparseTopK);
    }

    6.4 百炼重排序

    百炼重排序配置:

    {
    "enableReranking": true,
    "rerankConfig": {
    "modelName": "gte-rerank-hybrid",
    "rerankMinScore": 0.3,
    "rerankTopN": 5
    }
    }

    代码:

    private RerankConfig buildRerankConfig(
    JsonNode node) {

    String modelName =
    JsonUtils.getStringValue(
    node,
    "modelName",
    "gte-rerank-hybrid"
    );

    float minScore =
    JsonUtils.getFloatValue(
    node,
    "rerankMinScore",
    0.3f
    );

    int topN =
    JsonUtils.getIntValue(
    node,
    "rerankTopN",
    5
    );

    minScore = clamp(minScore);
    topN = Math.max(topN, 1);

    return RerankConfig.builder()
    .modelName(modelName)
    .rerankMinScore(minScore)
    .rerankTopN(topN)
    .build();
    }

    6.5 百炼查询重写

    查询重写配置:

    {
    "enableRewrite": true,
    "rewriteConfig": {
    "modelName": "conv-rewrite-qwen-1.8b"
    }
    }

    代码会根据 enableRewrite 决定是否设置重写配置。


    七、Dify 适配器的实现

    Dify 适配器:

    core/knowledge/impl/DifyIKnowledge.java

    核心方法:

    @Component
    public class DifyIKnowledge
    implements IKnowledge {

    @Override
    public Knowledge build(
    KnowledgeBaseConfig config) {

    try {
    validateConfig(config);

    DifyRAGConfig difyConfig =
    buildDifyRAGConfig(config);

    return DifyKnowledge.builder()
    .config(difyConfig)
    .build();

    } catch (Exception e) {
    log.error(
    "Failed to build Dify knowledge configuration",
    e
    );

    throw new IllegalArgumentException(
    "Invalid Dify knowledge configuration",
    e
    );
    }
    }

    @Override
    public KbType type() {
    return KbType.DIFY;
    }
    }

    7.1 Dify 连接配置

    Dify 必填字段:

    apiKey
    datasetId

    配置示例:

    {
    "apiKey": "your-dify-api-key",
    "datasetId": "your-dataset-id"
    }

    校验:

    private void validateConfig(
    KnowledgeBaseConfig config) {

    if (config == null) {
    throw new IllegalArgumentException(
    "KnowledgeBaseConfig cannot be null"
    );
    }

    JsonNode connectionConfig =
    config.getConnectionConfig();

    if (connectionConfig == null
    || connectionConfig.isEmpty()) {
    throw new IllegalArgumentException(
    "Connection config is required for Dify knowledge"
    );
    }

    validateRequiredField(
    connectionConfig,
    "apiKey",
    "apiKey"
    );

    validateRequiredField(
    connectionConfig,
    "datasetId",
    "datasetId"
    );
    }

    项目还会对 datasetId 做 UUID 格式检查,如果格式不符合预期会记录警告。

    7.2 Dify API 地址

    默认地址:

    private static final String
    DEFAULT_API_BASE_URL =
    "https://api.dify.ai/v1";

    自定义配置:

    {
    "apiBaseUrl": "https://your-dify-host/v1"
    }

    代码会检查 URL 是否以:

    http://
    https://

    开头。

    7.3 Dify 检索模式

    Dify 的检索配置:

    {
    "retrievalMode": "HYBRID_SEARCH",
    "topK": 10,
    "scoreThreshold": 0.2,
    "weights": 0.6
    }

    项目默认值:

    retrievalMode = HYBRID_SEARCH
    topK = 10
    scoreThreshold = 0.0
    weights = 0.6

    代码:

    private void applyRetrievalConfig(
    DifyRAGConfig.Builder builder,
    JsonNode retrievalConfig) {

    RetrievalMode mode =
    getRetrievalMode(retrievalConfig);

    builder.retrievalMode(mode);

    int topK =
    JsonUtils.getIntValue(
    retrievalConfig,
    "topK",
    10
    );

    topK = clamp(topK);

    double scoreThreshold =
    JsonUtils.getDoubleValue(
    retrievalConfig,
    "scoreThreshold",
    0.0
    );

    scoreThreshold = clamp(scoreThreshold);

    builder.topK(topK)
    .scoreThreshold(scoreThreshold);

    if (mode == RetrievalMode.HYBRID_SEARCH) {
    double weights =
    JsonUtils.getDoubleValue(
    retrievalConfig,
    "weights",
    0.6
    );

    builder.weights(clamp(weights));
    }
    }

    检索模式转换:

    private RetrievalMode getRetrievalMode(
    JsonNode retrievalConfig) {

    if (retrievalConfig == null
    || !retrievalConfig.has("retrievalMode")) {
    return RetrievalMode.HYBRID_SEARCH;
    }

    String mode =
    retrievalConfig
    .get("retrievalMode")
    .asText();

    try {
    return RetrievalMode.valueOf(
    mode.toUpperCase()
    );
    } catch (IllegalArgumentException e) {
    log.warn(
    "Invalid retrieval mode: {}, using default",
    mode
    );

    return RetrievalMode.HYBRID_SEARCH;
    }
    }

    7.4 Dify 重排序

    Dify 重排序配置:

    {
    "enableRerank": true,
    "rerankConfig": {
    "providerName": "cohere",
    "modelName": "rerank-english-v2.0",
    "topN": 5
    }
    }

    代码读取:

    private RerankConfig buildRerankConfig(
    JsonNode node) {

    String providerName =
    JsonUtils.getStringValue(
    node,
    "providerName",
    "cohere"
    );

    String modelName =
    JsonUtils.getStringValue(
    node,
    "modelName",
    "rerank-english-v2.0"
    );

    return RerankConfig.builder()
    .providerName(providerName)
    .modelName(modelName)
    .build();
    }

    当前项目读取了 topN,但对应的构建调用被保留为 TODO:

    // builder.topN(topN); // TODO

    因此文章不能写成“Dify 的 topN 已经完整生效”,只能准确描述为:

    当前适配器已经解析 Dify 的 topN 参数,但实际 builder 设置仍是待完善项。

    7.5 Dify 元数据过滤

    Dify 支持元数据过滤:

    {
    "logicalOperator": "AND",
    "conditions": [
    {
    "name": "source",
    "comparisonOperator": "=",
    "value": "thesis"
    }
    ]
    }

    支持的比较运算符:

    =
    !=
    >
    >=
    <
    <=
    in
    not in
    contains
    not contains

    代码会逐条校验:

    private boolean isValidComparisonOperator(
    String operator) {

    String[] validOperators = {
    "=", "!=", ">", ">=",
    "<", "<=", "in",
    "not in", "contains",
    "not contains"
    };

    for (String valid : validOperators) {
    if (valid.equalsIgnoreCase(operator)) {
    return true;
    }
    }

    return false;
    }

    这类配置的价值是:

    同一个 Dify 数据集
    只检索某个来源
    只检索某个部门
    只检索某个文档版本

    7.6 Dify HTTP 配置

    Dify 支持:

    {
    "connectTimeout": "30s",
    "readTimeout": "60s",
    "maxRetries": 3,
    "customHeaders": {
    "X-Tenant": "demo"
    }
    }

    项目默认值:

    连接超时:30 秒
    读取超时:60 秒
    最大重试:3 次

    代码:

    private void applyHttpConfig(
    DifyRAGConfig.Builder builder,
    JsonNode httpConfig) {

    Duration connectTimeout =
    JsonUtils.getDurationValue(
    httpConfig,
    "connectTimeout",
    Duration.ofSeconds(30)
    );

    Duration readTimeout =
    JsonUtils.getDurationValue(
    httpConfig,
    "readTimeout",
    Duration.ofSeconds(60)
    );

    int maxRetries =
    JsonUtils.getIntValue(
    httpConfig,
    "maxRetries",
    3
    );

    builder.connectTimeout(connectTimeout)
    .readTimeout(readTimeout)
    .maxRetries(Math.max(0, maxRetries));
    }


    八、RAGFlow 适配器的实现

    RAGFlow 适配器:

    core/knowledge/impl/RagFlowIKnowledge.java

    核心构建逻辑:

    @Component
    public class RagFlowIKnowledge
    implements IKnowledge {

    @Override
    public Knowledge build(
    KnowledgeBaseConfig config) {

    try {
    validateConfig(config);

    RAGFlowConfig ragflowConfig =
    buildRAGFlowConfig(config);

    return RAGFlowKnowledge.builder()
    .config(ragflowConfig)
    .build();

    } catch (Exception e) {
    log.error(
    "Failed to build RAGFlow knowledge configuration",
    e
    );

    throw new IllegalArgumentException(
    "Invalid RAGFlow knowledge configuration",
    e
    );
    }
    }

    @Override
    public KbType type() {
    return KbType.RAGFLOW;
    }
    }

    8.1 RAGFlow 连接配置

    RAGFlow 配置示例:

    {
    "apiKey": "your-ragflow-api-key",
    "baseUrl": "https://your-ragflow-host",
    "datasetIds": [
    "dataset-001",
    "dataset-002"
    ],
    "documentIds": [
    "document-001"
    ]
    }

    必填字段:

    apiKey
    baseUrl
    datasetIds

    校验代码:

    private void validateConfig(
    KnowledgeBaseConfig config) {

    JsonNode connectionConfig =
    config.getConnectionConfig();

    validateRequiredField(
    connectionConfig,
    "apiKey",
    "apiKey"
    );

    validateRequiredField(
    connectionConfig,
    "baseUrl",
    "baseUrl"
    );

    if (!connectionConfig.has("datasetIds")
    || connectionConfig.get("datasetIds")
    .isNull()
    || connectionConfig.get("datasetIds")
    .isEmpty()) {

    throw new IllegalArgumentException(
    "At least one datasetId is required"
    );
    }
    }

    项目同时支持数组和逗号分隔字符串:

    {
    "datasetIds": [
    "dataset-001",
    "dataset-002"
    ]
    }

    或者:

    {
    "datasetIds": "dataset-001,dataset-002"
    }

    统一转换方法:

    private List<String> getStringListValue(
    JsonNode node,
    String fieldName) {

    JsonNode valueNode =
    node.get(fieldName);

    List<String> result =
    new ArrayList<>();

    if (valueNode.isArray()) {
    for (JsonNode item : valueNode) {
    if (item.isTextual()
    && StringUtils.hasText(
    item.asText())) {
    result.add(
    item.asText().trim()
    );
    }
    }
    } else if (valueNode.isTextual()) {
    String[] items =
    valueNode.asText().split(",");

    for (String item : items) {
    if (StringUtils.hasText(item)) {
    result.add(item.trim());
    }
    }
    }

    return result.isEmpty()
    ? null
    : result;
    }

    8.2 RAGFlow 检索配置

    RAGFlow 配置示例:

    {
    "topK": 1024,
    "similarityThreshold": 0.2,
    "vectorSimilarityWeight": 0.3,
    "page": 1,
    "pageSize": 30,
    "useKg": false,
    "tocEnhance": false,
    "rerankId": 1,
    "keyword": false,
    "highlight": false,
    "crossLanguages": [
    "zh",
    "en"
    ]
    }

    基础检索配置:

    private void applyRetrievalConfig(
    RAGFlowConfig.Builder builder,
    JsonNode retrievalConfig) {

    int topK =
    JsonUtils.getIntValue(
    retrievalConfig,
    "topK",
    1024
    );

    double similarityThreshold =
    JsonUtils.getDoubleValue(
    retrievalConfig,
    "similarityThreshold",
    0.2
    );

    double vectorSimilarityWeight =
    JsonUtils.getDoubleValue(
    retrievalConfig,
    "vectorSimilarityWeight",
    0.3
    );

    int page =
    JsonUtils.getIntValue(
    retrievalConfig,
    "page",
    1
    );

    int pageSize =
    JsonUtils.getIntValue(
    retrievalConfig,
    "pageSize",
    30
    );

    builder.topK(Math.max(1, topK))
    .similarityThreshold(
    clamp(similarityThreshold)
    )
    .vectorSimilarityWeight(
    clamp(vectorSimilarityWeight)
    )
    .page(Math.max(1, page))
    .pageSize(Math.max(1, pageSize));
    }

    8.3 RAGFlow 高级检索

    项目支持以下高级能力:

    useKg:是否启用知识图谱多跳查询
    tocEnhance:目录增强
    rerankId:重排序模型 ID
    keyword:关键词匹配
    highlight:高亮结果
    crossLanguages:跨语言检索

    代码示例:

    private void applyAdvancedRetrievalConfig(
    RAGFlowConfig.Builder builder,
    JsonNode retrievalConfig) {

    boolean useKg =
    JsonUtils.getBooleanValue(
    retrievalConfig,
    "useKg",
    false
    );

    boolean tocEnhance =
    JsonUtils.getBooleanValue(
    retrievalConfig,
    "tocEnhance",
    false
    );

    boolean keyword =
    JsonUtils.getBooleanValue(
    retrievalConfig,
    "keyword",
    false
    );

    boolean highlight =
    JsonUtils.getBooleanValue(
    retrievalConfig,
    "highlight",
    false
    );

    builder.useKg(useKg)
    .tocEnhance(tocEnhance)
    .keyword(keyword)
    .highlight(highlight);
    }

    跨语言参数会进行简单的语言编码校验:

    private boolean isValidLanguageCode(
    String languageCode) {

    return languageCode != null
    && languageCode
    .trim()
    .toLowerCase()
    .matches("^[a-z]{2,3}$");
    }

    8.4 RAGFlow HTTP 配置

    {
    "timeout": "30s",
    "maxRetries": 3,
    "customHeaders": {
    "X-Tenant": "demo"
    }
    }

    代码:

    private void applyHttpConfig(
    RAGFlowConfig.Builder builder,
    JsonNode httpConfig) {

    Duration timeout =
    JsonUtils.getDurationValue(
    httpConfig,
    "timeout",
    Duration.ofSeconds(30)
    );

    int maxRetries =
    JsonUtils.getIntValue(
    httpConfig,
    "maxRetries",
    3
    );

    builder.timeout(timeout)
    .maxRetries(Math.max(0, maxRetries));
    }


    九、三个适配器的差异

    虽然三种实现都返回 Knowledge,但配置语义完全不同。

    能力百炼DifyRAGFlow
    认证 AccessKey API Key API Key
    数据定位 workspace + index dataset dataset 列表
    检索参数 稠密/稀疏 Top K 检索模式、Top K、阈值 Top K、相似度、分页
    重排序 模型、最低分、Top N 提供商、模型、Top N Rerank ID
    查询重写 支持 当前适配器未配置 未配置
    元数据过滤 未配置 支持 未配置
    知识图谱 未配置 未配置 支持
    HTTP 超时 由底层配置 连接超时和读取超时 请求超时
    跨语言 未配置 未配置 支持

    这正是适配器存在的意义:

    外部配置不同
    内部对象不同
    最终统一成 Agent 可使用的 Knowledge


    十、后台表单如何根据类型生成配置

    项目的 KnowledgeForm.vue 会根据 kbType 动态显示配置项。

    百炼显示:

    连接配置
    端点配置
    检索配置
    重排序配置
    查询重写配置

    Dify 显示:

    连接配置
    端点配置
    检索配置
    重排序配置
    元数据过滤
    HTTP 配置

    RAGFlow 显示:

    连接配置
    检索配置
    HTTP 配置

    前端根据类型组装提交数据:

    if (formData.kbType === 'BAILIAN') {
    data.connectionConfig = {
    …bailianConnection
    }

    data.endpointConfig =
    bailianEndpoint.endpoint
    ? { endpoint: bailianEndpoint.endpoint }
    : null

    data.retrievalConfig =
    bailianRetrieval.denseSimilarityTopK
    || bailianRetrieval.sparseSimilarityTopK
    ? { …bailianRetrieval }
    : null
    }

    Dify:

    if (formData.kbType === 'DIFY') {
    data.connectionConfig = {
    …difyConnection
    }

    data.endpointConfig =
    difyEndpoint.apiBaseUrl
    ? {
    apiBaseUrl:
    difyEndpoint.apiBaseUrl
    }
    : null

    data.retrievalConfig = {
    …difyRetrieval
    }

    data.metadataFilters =
    metadataFilters.conditions.length > 0
    ? { …metadataFilters }
    : null
    }

    RAGFlow:

    if (formData.kbType === 'RAGFLOW') {
    data.connectionConfig = {
    …ragflowConnection
    }

    data.retrievalConfig = {
    …ragflowRetrieval
    }

    data.httpConfig = {
    timeout: ragflowHttp.timeout,
    maxRetries: ragflowHttp.maxRetries
    }
    }

    这样后台页面和后端适配器使用的是同一套配置结构。


    十一、配置校验为什么必须放在适配器中

    不能只依赖前端校验。

    因为知识库配置还可能来自:

    后台管理接口
    数据库迁移脚本
    历史数据
    接口重放
    内部服务调用

    所以每个适配器都必须自行校验。

    例如百炼:

    validateRequiredField(
    connectionConfig,
    "workspaceId",
    "workspaceId"
    );

    Dify:

    validateRequiredField(
    connectionConfig,
    "datasetId",
    "datasetId"
    );

    RAGFlow:

    if (!connectionConfig.has("datasetIds")) {
    throw new IllegalArgumentException(
    "At least one datasetId is required"
    );
    }

    适配器还负责限制数值范围。

    Dify:

    topK = Math.max(
    1,
    Math.min(100, topK)
    );

    百炼:

    denseTopK = Math.max(
    0,
    Math.min(100, denseTopK)
    );

    RAGFlow:

    similarityThreshold =
    Math.max(
    0.0,
    Math.min(1.0,
    similarityThreshold)
    );

    这比把校验全部写在 Controller 中更合理,因为配置构建逻辑和配置校验逻辑属于同一个供应商适配边界。


    十二、健康状态字段的真实含义

    数据库表中有:

    health_status
    last_sync_time

    接口中也定义了:

    boolean lightHealthCheck(
    KnowledgeBaseConfig config
    );

    但是当前三个适配器的实现都是:

    @Override
    public boolean lightHealthCheck(
    KnowledgeBaseConfig config) {
    return false;
    }

    因此当前项目已经预留了健康检查扩展点,但并没有实现真实的百炼、Dify、RAGFlow 连通性检测。

    文章不能写成:

    系统已经实现三种知识库健康检查

    准确的说法是:

    项目在配置模型和适配器接口中预留了健康检查能力,但当前三个适配器的 lightHealthCheck() 尚未接入真实探测逻辑。

    如果后续实现,健康检查应该注意:

    不能执行完整大模型检索
    不能消耗大量额度
    不能把 API Key 写入日志
    应该设置连接和读取超时
    应该区分认证失败、网络失败和数据集不存在


    十三、为什么不在 Agent 中直接判断供应商

    不推荐这样写:

    if (config.getKbType() == KbType.BAILIAN) {
    BailianConfig bailian = …
    builder.knowledge(…);
    }

    if (config.getKbType() == KbType.DIFY) {
    DifyRAGConfig dify = …
    builder.knowledge(…);
    }

    问题在于 Agent 构建类会逐渐承担:

    供应商判断
    参数校验
    JSON 解析
    默认值处理
    SDK 对象创建
    异常处理

    最终形成一个很难维护的上帝类。

    当前项目的正确做法是:

    KnowledgeWrapper wrapper =
    knowledgeFactory.getKnowledge(definition);

    if (wrapper != null) {
    builder.knowledge(
    wrapper.getKnowledge()
    );

    builder.ragMode(
    wrapper.getRagMode()
    );
    }

    Agent 只关心统一接口:

    拿到 Knowledge
    配置 RAGMode
    继续构建 Agent


    十四、如何新增第四种知识库

    假设以后要接入一个新的知识库供应商 XKnowledge,不需要修改 Agent 主流程。

    只需要新增适配器:

    @Component
    public class XKnowledgeAdapter
    implements IKnowledge {

    @Override
    public Knowledge build(
    KnowledgeBaseConfig config) {

    validateConfig(config);

    XConfig xConfig =
    buildXConfig(config);

    return XKnowledge.builder()
    .config(xConfig)
    .build();
    }

    @Override
    public KbType type() {
    return KbType.X_KNOWLEDGE;
    }

    @Override
    public boolean lightHealthCheck(
    KnowledgeBaseConfig config) {
    return false;
    }
    }

    然后扩展枚举:

    public enum KbType {
    BAILIAN,
    DIFY,
    RAGFLOW,
    X_KNOWLEDGE
    }

    再在前端增加对应配置表单。

    Agent 主流程仍然是:

    KnowledgeWrapper wrapper =
    knowledgeFactory.getKnowledge(definition);

    不需要增加:

    if (X_KNOWLEDGE) {
    …
    }

    这就是适配器模式和工厂模式带来的扩展性。


    十五、当前实现中的几个边界

    15.1 KnowledgeFactory 使用静态注册表

    private static final Map<KbType, IKnowledge>
    KNOWLEDGE_MAP =
    new ConcurrentHashMap<>();

    优点是读取简单,适配器在启动时注册一次即可。

    但要注意:

    适配器类型必须唯一
    重复注册会覆盖旧实现
    测试之间需要清理静态 Map

    项目提供了:

    public static void clear() {
    KNOWLEDGE_MAP.clear();
    }

    这对测试和重新初始化有帮助。

    15.2 配置读取是动态的,但对象构建不是缓存的

    每次 getKnowledge() 都会读取配置并调用:

    iKnowledge.build(config)

    这意味着配置修改后,下一次构建 Agent 可以获得新配置,但也意味着每次构建可能重新创建知识库对象。

    如果未来知识库对象创建成本很高,可以增加:

    配置版本号
    配置哈希
    Knowledge 对象缓存
    缓存失效机制

    不过缓存知识库对象时必须处理:

    配置修改
    凭证轮换
    供应商连接失效
    多实例配置同步

    15.3 当前固定了 Agent 的检索参数

    ReActAgentHelper 中固定配置:

    RetrieveConfig.builder()
    .limit(5)
    .scoreThreshold(0.5)
    .build()

    这意味着知识库适配器内部的检索参数和 Agent 层的最终检索限制可能同时生效。

    文章中应区分:

    供应商配置:适配器如何构造 RAG 客户端
    Agent 配置:Agent 最终允许返回多少检索结果

    这两层参数如果没有统一规范,可能出现:

    底层 topK 配置很大
    Agent 层 limit 仍然只有 5


    十六、三个真实配置示例

    以下示例中的密钥均为占位符。

    16.1 百炼配置

    {
    "name": "论文百炼知识库",
    "kbType": "BAILIAN",
    "ragMode": "AGENTIC",
    "connectionConfig": {
    "accessKeyId": "your-access-key-id",
    "accessKeySecret": "your-access-key-secret",
    "workspaceId": "your-workspace-id",
    "indexId": "your-index-id",
    "saveRetrieverHistory": false
    },
    "endpointConfig": {
    "endpoint": "bailian.cn-beijing.aliyuncs.com"
    },
    "retrievalConfig": {
    "denseSimilarityTopK": 100,
    "sparseSimilarityTopK": 100
    },
    "rerankingConfig": {
    "enableReranking": true,
    "rerankConfig": {
    "modelName": "gte-rerank-hybrid",
    "rerankMinScore": 0.3,
    "rerankTopN": 5
    }
    },
    "queryRewriteConfig": {
    "enableRewrite": true,
    "rewriteConfig": {
    "modelName": "conv-rewrite-qwen-1.8b"
    }
    }
    }

    16.2 Dify 配置

    {
    "name": "论文 Dify 知识库",
    "kbType": "DIFY",
    "ragMode": "GENERIC",
    "connectionConfig": {
    "apiKey": "your-dify-api-key",
    "datasetId": "your-dataset-id"
    },
    "endpointConfig": {
    "apiBaseUrl": "https://your-dify-host/v1"
    },
    "retrievalConfig": {
    "retrievalMode": "HYBRID_SEARCH",
    "topK": 10,
    "scoreThreshold": 0.2,
    "weights": 0.6
    },
    "rerankingConfig": {
    "enableRerank": true,
    "rerankConfig": {
    "providerName": "cohere",
    "modelName": "rerank-english-v2.0",
    "topN": 5
    }
    },
    "metadataFilters": {
    "logicalOperator": "AND",
    "conditions": [
    {
    "name": "source",
    "comparisonOperator": "=",
    "value": "thesis"
    }
    ]
    },
    "httpConfig": {
    "connectTimeout": "30s",
    "readTimeout": "60s",
    "maxRetries": 3
    }
    }

    16.3 RAGFlow 配置

    {
    "name": "论文 RAGFlow 知识库",
    "kbType": "RAGFLOW",
    "ragMode": "GENERIC",
    "connectionConfig": {
    "apiKey": "your-ragflow-api-key",
    "baseUrl": "https://your-ragflow-host",
    "datasetIds": [
    "dataset-001",
    "dataset-002"
    ],
    "documentIds": [
    "document-001"
    ]
    },
    "retrievalConfig": {
    "topK": 1024,
    "similarityThreshold": 0.2,
    "vectorSimilarityWeight": 0.3,
    "page": 1,
    "pageSize": 30,
    "useKg": false,
    "tocEnhance": false,
    "rerankId": 1,
    "keyword": false,
    "highlight": false,
    "crossLanguages": [
    "zh",
    "en"
    ]
    },
    "httpConfig": {
    "timeout": "30s",
    "maxRetries": 3
    }
    }


    十七、如何验证适配器是否工作

    1. 创建知识库配置

    通过后台知识库配置页面,选择:

    百炼
    Dify
    RAGFlow

    填写对应的连接配置和检索配置。

    2. 绑定 Agent

    将知识库配置绑定到某个 Agent。

    项目通过 Agent 和知识库关联表保存绑定关系:

    agent_knowledge_bases

    3. 构建 Agent

    Agent 构建时执行:

    KnowledgeWrapper wrapper =
    knowledgeFactory.getKnowledge(
    definition
    );

    4. 查看类型选择

    可以在日志中输出:

    log.info(
    "Build knowledge, type={}, agentId={}",
    config.getKbType(),
    definition.getId()
    );

    5. 验证参数错误

    例如 Dify 缺少 datasetId:

    {
    "apiKey": "demo"
    }

    预期抛出:

    datasetId is required in connection config

    RAGFlow 缺少数据集:

    {
    "apiKey": "demo",
    "baseUrl": "https://ragflow.example.com",
    "datasetIds": []
    }

    预期抛出:

    At least one datasetId is required

    6. 验证 Agent 检索

    向 Agent 提问一个只有知识库中才有答案的问题:

    请根据论文资料说明第三章的研究方法。

    如果配置正确,Agent 会通过统一的 Knowledge 对象完成检索。


    十八、总结

    项目通过以下结构接入百炼、Dify 和 RAGFlow:

    knowledge_base_config
    |
    v
    KnowledgeBaseConfig
    |
    v
    KnowledgeFactory
    |
    v
    IKnowledge
    |
    +—— BailianIKnowledge
    |
    +—— DifyIKnowledge
    |
    +—— RagFlowIKnowledge
    |
    v
    Agentscope Knowledge
    |
    v
    ReActAgent

    这套设计解决了三个核心问题:

    1. 隔离供应商差异

    百炼、Dify、RAGFlow 的配置字段和 SDK 调用方式完全不同,但 Agent 只接收统一的 Knowledge。

    2. 配置由数据库驱动

    知识库配置不写死在 Java 代码中,而是保存为:

    连接配置
    端点配置
    检索配置
    重排序配置
    查询重写配置
    元数据过滤
    HTTP 配置

    3. 新增供应商不需要修改 Agent 主流程

    新增知识库时,只需要增加:

    新的 KbType
    新的 IKnowledge 实现
    新的后台配置表单

    不需要在 Agent 构建代码中继续增加供应商判断。

    但当前实现也有明确边界:

    三个适配器的 lightHealthCheck() 目前都返回 false
    Dify 的 topN 参数当前仍有 TODO
    Knowledge 对象没有做版本化缓存
    Agent 层固定了 limit = 5 和 scoreThreshold = 0.5
    JSON 配置错误主要在构建时发现

    因此,这不是一个“已经实现所有企业级能力”的知识库平台,而是一套已经完成多供应商适配、配置隔离和 Agent 装配的基础架构。

    它的核心价值可以概括为:

    让 Agent 面向统一的知识库接口编程,让每个 RAG 供应商的复杂配置都被限制在自己的适配器内部。

    赞(0)
    未经允许不得转载:171主机测评 » 一个 Agent 如何接入百炼、Dify 和 RAGFlow:用适配器隔离不同 RAG 配置
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址