AgentScope

AgentScope Java特性:RAG

(虽然在ASJ的2.0的relesse note中提到:RAG (Knowledge / KnowledgeRetrievalTools / RAGMode) and long term memory modules depreca…

TL;DR

(虽然在ASJ的2.0的relesse note中提到:RAG (Knowledge / KnowledgeRetrievalTools / RAGMode) and long term memory modules depreca…

(虽然在ASJ的2.0的relesse note中提到:RAG (Knowledge / KnowledgeRetrievalTools / RAGMode) and long-term memory modules deprecated — being rewritten on the v2 architecture; don't depend on them in new code`,但是截止目前,2.0还没正式发布,也没给出替代方案,我们先讲1.0的方案和用法,后续新版本更新了我能再单独讲方案和用法)

三个核心抽象

ASJ中针对RAG做了抽象,其中比较核心的就是Knowledge / Document / RetrieveConfig这三个。整个 RAG 子系统就由这三个类(接口)驱动,其它都是装饰。 Knowledge 接口只有两个方法:

public interface Knowledge {

    Mono<Void> addDocuments(List<Document> documents);

    Mono<List<Document>> retrieve(String query, RetrieveConfig config);
}

整个 RAG 子系统就建立在这个接口上,所有上游(Hook、Tool、Bailian/Dify 等扩展)都只依赖它。这意味着: - 写自己的接入:你只要实现 Knowledge 就能塞到 ReActAgent 里跑。 - 想换底座:把 SimpleKnowledge 换成 BailianKnowledge、DifyKnowledge 即可,无需动 Agent 代码。 也就是说,ASJ其实内置了很多RAG系统的支持,包括百炼、Dify、RAGFlow等。 Document 用来表示在RAG系统的每一个chunk,他的定义如下:

public class Document {

    private final String id;
    private final DocumentMetadata metadata;
    private double[] embedding;
    private Double score;
    private String vectorName;
}

DocumentMetadata 内部装的是一个 ContentBlock——agentscope 统一的多模态块类型(TextBlock / ImageBlock 等)。这个设计让"文本块"和"图片块"共用一条 RAG 管线。 RetrieveConfig 用来配置检索参数:

RetrieveConfig.builder()
    .limit(5)                       // top-k,默认 5
    .scoreThreshold(0.5)            // 相似度阈值,默认 0.5
    .vectorName("doc_v1")           // 向量空间名(可选,用于隔离不同 corpus)
    .conversationHistory(history)   // 多轮上下文(百炼会用它做 query rewrite)
    .build();

两种 RAG 模式

这是 agentscope 给业务方提供的"插法选择",对应 RAGMode 枚举:

public enum RAGMode {
    /**
     * Generic mode: Knowledge is automatically retrieved and injected
     * before each reasoning step via Hook.
     *
     * <p>In this mode, the system automatically retrieves relevant knowledge
     * based on user queries and injects it into the prompt context.
     */
    GENERIC,

    /**
     * Agentic mode: Agent decides when to retrieve knowledge via Tool.
     *
     * <p>In this mode, the agent has a tool to retrieve knowledge and
     * actively decides when to use it based on the conversation context.
     */
    AGENTIC,

    /**
     * Disabled mode: No RAG functionality.
     *
     * <p>Knowledge retrieval is not enabled for this agent.
     */
    NONE
}
模式 触发方 注入位置 适合场景
GENERIC 框架(每次推理前自动) inputMessages FAQ / 知识助手,每条用户提问都需要查
AGENTIC LLM 自己决策 作为 多技能 Agent,RAG 是诸多工具之一
NONE 关掉 RAG

Generic:

在 Generic 模式下,知识会自动检索并注入到用户的消息中,他的工作原理,和我们传统的RAG系统的流程是一样的: 1. 用户发送查询 2. 知识库自动检索相关文档 3. 检索到的文档被添加到用户消息之前 4. Agent 处理增强后的消息并响应 只不过这个检索和追加的动作内部实现了,通过GenericRAGHook作为核心实现:

private Mono<PreCallEvent> handlePreCall(PreCallEvent event) {
    String query = extractQueryFromMessages(event.getInputMessages());
    if (query == null || query.isBlank()) return Mono.just(event);

    return knowledge.retrieve(query, defaultConfig)
        .flatMap(docs -> {
            if (docs.isEmpty()) return Mono.just(event);
            List<Msg> enhanced = new ArrayList<>(event.getInputMessages());
            enhanced.add(buildKnowledgeUserMsg(docs));   // 追加到末尾
            event.setInputMessages(enhanced);
            return Mono.just(event);
        })
        .onErrorResume(err -> {
            log.warn("Generic RAG retrieval failed: {}", err.getMessage());
            return Mono.just(event);                     // ← 失败兜底,不打断主流程
        });
}

Agentic

Agentic 其实是Agentic RAG的实现,也就是说让Agent来决策什么时候该调用RAG做检索,相当于把RAG当做一个工具。工作原理是: 1. 用户发送查询 2. Agent 推理并决定是否检索知识 3. 如果需要,Agent 调用 retrieve_knowledge(query="...") 4. 检索到的文档作为工具结果返回 5. Agent 使用检索到的信息再次推理 因为要作为一个工具,可想而知需要声明一个tool,他的实现是靠KnowledgeRetrievalTools实现的。

@Tool(
        name = "retrieve_knowledge",
        description =
                "Retrieve relevant documents from knowledge base. Use this tool when you need"
                    + " to find specific information or when user asks questions about stored"
                    + " knowledge.")
public String retrieveKnowledge(
        @ToolParam(
                        name = "query",
                        description =
                                "The search query to find relevant documents in the knowledge"
                                        + " base")
                String query,
        @ToolParam(
                        name = "limit",
                        description = "Maximum number of documents to retrieve (default: 5)",
                        required = false)
                Integer limit,
        Agent agent) {

    // Set default value
    if (limit == null) {
        limit = 5;
    }

    // Extract conversation history from agent if available
    List<Msg> conversationHistory = null;
    if (agent instanceof ReActAgent reActAgent) {
        conversationHistory = reActAgent.getMemory().getMessages();
    }

    // Build retrieval config with conversation history
    RetrieveConfig config =
            this.defaultConfig
                    .mutate()
                    .limit(limit)
                    .conversationHistory(conversationHistory)
                    .build();

    return knowledge
            .retrieve(query, config)
            .map(this::formatDocumentsForTool)
            .onErrorReturn("Failed to retrieve knowledge for query: " + query)
            .block(); // Convert to synchronous call to match Tool interface
}

Agent agent 参数不是 @ToolParam,而是框架自动注入当前 Agent。这样工具内部能拿到完整 agent 状态,把会话历史一并送进 RetrieveConfig。 上面的Hook和工具,是在ReActAgent构造的时候,自动配置进去的:io.agentscope.core.ReActAgent.Builder#configureRAG

private void configureRAG(Toolkit agentToolkit) {
    // Aggregate knowledge bases if multiple are provided
    Knowledge aggregatedKnowledge;
    if (knowledgeBases.size() == 1) {
        aggregatedKnowledge = knowledgeBases.iterator().next();
    } else {
        aggregatedKnowledge = buildAggregatedKnowledge();
    }

    // Configure based on mode
    switch (ragMode) {
        case GENERIC -> {
            // Create and add GenericRAGHook
            GenericRAGHook ragHook =
                    new GenericRAGHook(aggregatedKnowledge, retrieveConfig);
            hooks.add(ragHook);
        }
        case AGENTIC -> {
            // Register knowledge retrieval tools
            KnowledgeRetrievalTools tools =
                    new KnowledgeRetrievalTools(aggregatedKnowledge, retrieveConfig);
            agentToolkit.registerTool(tools);
        }
        case NONE -> {
            // Do nothing
        }
    }
}

文档处理支持

Reader

ASJ中内置了很多Reader用来读取不同的类型的文档 | Reader | 作用 | | --- | --- | | TextReader | 直接吃 String 或文件 | | PDFReader | PDFBox 解析,按页拼文本 | | WordReader | POI 解析 .docx | | TikaReader | Apache Tika 万能解析(PPT/HTML/Markdown 等) | | ImageReader | 图片 → ImageBlock,配合多模态 embedding | | ExternalApiReader | 调外部 OCR / parsing API |

这里的实现还是比较简单了,比如PDF还是用PDFBox,Word还是用POI的。效果一般。

Chunker

ASJ中内置了TextChunker用来做文档分段。支持以下几种分段策略: | 策略 | 实现 | | --- | --- | | CHARACTER | 按字符数硬切,可能断词 | | PARAGRAPH | 按 | | TOKEN | 用 | | SEMANTIC | 当前未实现,回退到 PARAGRAPH |

EmbeddingModel

内置了EmbeddingModel接口以及默认实现,来做embedding。

public interface EmbeddingModel {


    Mono<double[]> embed(ContentBlock block);


    String getModelName();


    int getDimensions();
}

embed()方法支持传入ContentBlock,他也有多种实现,比如文本是 TextBlock,图片走多模态实现传 ImageBlock,两者复用同一接口。 有多重默认实现:DashScopeTextEmbedding / DashScopeMultiModalEmbedding / OpenAITextEmbedding / OllamaTextEmbedding。(需要配置apikey等)

VDBStoreBase

向量数据库也提供了抽象:

public interface VDBStoreBase {

    Mono<Void> add(List<Document> documents);

    Mono<List<Document>> search(SearchDocumentDto searchDocumentDto);

    Mono<Boolean> delete(String id);
}

有以下5个默认实现: | Store | 适用 | | --- | --- | | InMemoryStore | 开发/小数据集; | | PgVectorStore | PostgreSQL + pgvector 扩展 | | QdrantStore | Qdrant | | MilvusStore | Milvus | | ElasticsearchStore | ES(dense_vector) |

Demo

GENERIC + SimpleKnowledge

这种比较适合在类似我们dodo-agent中用户上传文档做问答的场景。 先要增加配置依赖。后面我们要用的TextEmbedding默认是不在ASJ的核心包里面的。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dashscope-sdk-java</artifactId>
    <version>2.22.9</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
   <groupId>com.aliyun</groupId>
   <artifactId>bailian20231229</artifactId>
   <version>2.13.1</version>
</dependency>
import io.agentscope.core.ReActAgent;
import io.agentscope.core.embedding.EmbeddingModel;
import io.agentscope.core.embedding.dashscope.DashScopeTextEmbedding;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.rag.RAGMode;
import io.agentscope.core.rag.knowledge.SimpleKnowledge;
import io.agentscope.core.rag.model.Document;
import io.agentscope.core.rag.reader.PDFReader;
import io.agentscope.core.rag.reader.ReaderInput;
import io.agentscope.core.rag.store.InMemoryStore;

import java.io.File;
import java.io.IOException;
import java.util.List;

public class LocalRagDemo {
    public static void main(String[] args) throws IOException {
        String apiKey = "sk-xxxxxxx";

        // 1. 起 Embedding + 内存向量库
        EmbeddingModel embed = DashScopeTextEmbedding.builder()
                .apiKey(apiKey).modelName("text-embedding-v3").dimensions(1024).build();
        InMemoryStore store = InMemoryStore.builder().dimensions(1024).build();
        SimpleKnowledge knowledge = SimpleKnowledge.builder()
                .embeddingModel(embed).embeddingStore(store).build();

        // 2. 灌数据(PDF)
        PDFReader reader = new PDFReader();
        File file = new File("/Users/hollis/LLM课程视频/RAG材料/Java八股文介绍.pdf");
        List<Document> docs = reader.read(ReaderInput.fromPath("/Users/hollis/LLM课程视频/RAG材料/Java八股文介绍.pdf")).block();
        knowledge.addDocuments(docs).block();

        // 3. 起 Agent,自动注入
        ReActAgent agent = ReActAgent.builder()
                .name("FAQBot")
                .sysPrompt("基于检索到的知识回答用户问题;若没找到请明确告知。")
                .model(DashScopeChatModel.builder()
                        .apiKey(apiKey)
                        .modelName("qwen-max")
                        .build())
                .knowledge(knowledge)
                .ragMode(RAGMode.GENERIC)     // 关键:自动 Hook
                .build();

        Msg msg = Msg.builder()
                .role(MsgRole.USER)
                .content(TextBlock.builder().text("这份JAVA八股文有哪些内容?").build())
                .build();
        System.out.println(agent.call(msg).block().getTextContent());
    }
}

使用第三方知识库

适合于那种你公司的知识库已经在百炼/Dify/RAGFlow 上维护,agent 只负责调用。

import io.agentscope.core.ReActAgent;
import io.agentscope.core.embedding.EmbeddingModel;
import io.agentscope.core.embedding.dashscope.DashScopeTextEmbedding;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.rag.RAGMode;
import io.agentscope.core.rag.integration.bailian.BailianConfig;
import io.agentscope.core.rag.integration.bailian.BailianKnowledge;
import io.agentscope.core.rag.knowledge.SimpleKnowledge;
import io.agentscope.core.rag.model.Document;
import io.agentscope.core.rag.reader.PDFReader;
import io.agentscope.core.rag.reader.ReaderInput;
import io.agentscope.core.rag.store.InMemoryStore;

import java.io.File;
import java.io.IOException;
import java.util.List;

public class BailianRagDemo {
    public static void main(String[] args) throws IOException {
        String apiKey = "sk-d221480c7a4c4b5aa45f67fa800e5da6";


        BailianConfig config =
                BailianConfig.builder()
                        .accessKeyId("xxx").accessKeySecret("xxx").workspaceId("llm-xxx")
                        .build();

        BailianKnowledge knowledge = BailianKnowledge.builder()
                .config(config)
                .indexId("xxx")
                .build();

        ReActAgent agent = ReActAgent.builder()
                .name("FAQBot")
                .sysPrompt("基于检索到的知识回答用户问题;若没找到请明确告知。")
                .model(DashScopeChatModel.builder()
                        .apiKey(apiKey)
                        .modelName("qwen-max")
                        .build())
                .knowledge(knowledge)
                .ragMode(RAGMode.AGENTIC)     // 关键:自动 Hook
                .build();

        Msg msg = Msg.builder()
                .role(MsgRole.USER)
                .content(TextBlock.builder().text("这份JAVA八股文有哪些内容?").build())
                .build();
        System.out.println(agent.call(msg).block().getTextContent());
    }
}

以上,是使用阿里云百炼SDK调用知识库的检索能力,为我们的Agent提供检索服务。所需参数(参考文档https://bailian.console.aliyun.com/cn-beijing?tab=doc#/doc/?type=app&url=2852772 ): - accessKeyId - accessKeySecret 过个人中心的AccessKey创建对应的accessKeyId和accessKeySecret - workspaceId - indexId

多知识库检索

如果一次回答需要从多个知识库检索,比如从FAQ、产品文档、技术手册同时检索:

ReActAgent agent = ReActAgent.builder()
    .knowledge(productDocsKB)
    .knowledge(faqKB)
    .knowledge(internalWikiKB)            // 框架自动 buildAggregatedKnowledge
    .ragMode(RAGMode.GENERIC)
    .build();

这种情况,会触发ReActAgent.Builder中的buildAggregatedKnowledge做多路检索和合并、重排:

private Knowledge buildAggregatedKnowledge() {
    return new Knowledge() {
        @Override
        public Mono<Void> addDocuments(List<Document> documents) {
            return Flux.fromIterable(knowledgeBases)
                    .flatMap(kb -> kb.addDocuments(documents))
                    .then();
        }

        @Override
        public Mono<List<Document>> retrieve(String query, RetrieveConfig config) {
            return Flux.fromIterable(knowledgeBases)
                    .flatMap(kb -> kb.retrieve(query, config))
                    .collectList()
                    .map(this::mergeAndSortResults);
        }

        private List<Document> mergeAndSortResults(List<List<Document>> allResults) {
            return allResults.stream()
                    .flatMap(List::stream)
                    .collect(
                            Collectors.toMap(
                                    Document::getId,
                                    doc -> doc,
                                    (doc1, doc2) ->
                                            doc1.getScore() != null
                                                            && doc2.getScore() != null
                                                            && doc1.getScore()
                                                                    > doc2.getScore()
                                                    ? doc1
                                                    : doc2))
                    .values()
                    .stream()
                    .sorted(
                            Comparator.comparing(
                                    Document::getScore,
                                    Comparator.nullsLast(Comparator.reverseOrder())))
                    .limit(retrieveConfig.getLimit())
                    .toList();
        }
    };
}
版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。