File size: 7,221 Bytes
0b9dc2e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# -*- coding: utf-8 -*-
"""Wire :class:`RAGMiddleware` into an :class:`Agent` — library mode.



The middleware in :mod:`agentscope.middleware._rag` is the agent-side

half of RAG: given one or more :class:`~agentscope.rag.KnowledgeBase`

handles (each pairing an embedding model with a vector-store

collection), the middleware drives search on each new user turn and

feeds the matched chunks into the model context.



This example reuses the indexing pipeline shown in ``index_and_search.py``

(parse → chunk → embed → insert) and then attaches the same knowledge base

to two agents — one per mode:



- ``"static"``: on the first reasoning step of a reply, embed the

  user's question, search, and inject the top hits as a one-shot

  :class:`HintBlock` into the agent's context. The model sees the

  matched snippets but never "decides" to search.

- ``"agentic"`` (the default): expose a ``search_knowledge`` tool. The

  model decides when (and what) to search, the same way it decides any

  other tool call.



Run with::



    DASHSCOPE_API_KEY=sk-... python examples/rag/integrate_with_agent.py

"""
import asyncio
import os

from agentscope.agent import Agent
from agentscope.credential import DashScopeCredential
from agentscope.embedding import DashScopeEmbeddingModel
from agentscope.message import UserMsg
from agentscope.middleware import RAGMiddleware
from agentscope.model import DashScopeChatModel
from agentscope.rag import (
    ApproxTokenChunker,
    KnowledgeBase,
    QdrantStore,
    TextParser,
)
from agentscope.tool import Toolkit


COLLECTION = "demo-kb"

KNOWLEDGE: dict[str, bytes] = {
    "company-policy.md": (
        b"# Acme Remote Work Policy\n\n"
        b"Employees may work remotely up to three days per week. "
        b"Wednesdays are mandatory in-office days for the whole "
        b"engineering org so cross-team syncs land on a predictable "
        b"day.\n\n"
        b"Equipment stipend: each new hire receives a USD 1,500 "
        b"one-off stipend for a home-office setup. Receipts must be "
        b"submitted within 90 days of the start date.\n"
    ),
    "release-notes.md": (
        b"# AgentScope 3.0 release notes\n\n"
        b"- New ``agentscope.rag`` module: pluggable parser, chunker, "
        b"embedding, and vector-store backends.\n"
        b"- ``RAGMiddleware`` ships in two modes -- ``static`` for "
        b"automatic injection, ``agentic`` for tool-driven search.\n"
        b"- Knowledge base service supports embedded and dedicated "
        b"worker deployments through a single message-bus channel.\n"
    ),
}


async def index_corpus(knowledge: KnowledgeBase) -> None:
    """Index the demo corpus into the knowledge base.



    Identical pipeline to ``examples/rag/index_and_search.py`` — extracted

    as a helper here so the agent-side wiring stays the focus.  Each source

    file becomes one logical document; ``KnowledgeBase.insert_document``

    embeds and inserts every chunk in a single batch.

    """
    parser = TextParser()
    chunker = ApproxTokenChunker(chunk_size=256, overlap=32)
    for filename, file_bytes in KNOWLEDGE.items():
        sections = await parser.parse(file=file_bytes, filename=filename)
        chunks = await chunker.chunk(sections)
        await knowledge.insert_document(
            chunks,
            document_metadata={"filename": filename},
        )


def build_agent(

    name: str,

    *,

    chat_model: DashScopeChatModel,

    rag_mw: RAGMiddleware,

) -> Agent:
    """Construct an :class:`Agent` with the RAG middleware attached.



    The middleware is just one entry in the ``middlewares=`` list; it

    composes with every other middleware (tool offload, mem0, ...) the

    agent uses.

    """
    return Agent(
        name=name,
        system_prompt=(
            "You are a concise assistant. Use matched context when "
            "available; if you don't know, say so."
        ),
        model=chat_model,
        toolkit=Toolkit(),
        middlewares=[rag_mw],
    )


async def ask(agent: Agent, question: str) -> None:
    """Run one reply and print the final assistant message."""
    print(f"\n[{agent.name}] user: {question}")
    reply = await agent.reply(UserMsg(name="user", content=question))
    print(f"[{agent.name}] assistant: {reply.get_text_content()}")


async def main() -> None:
    """The main entry point of the example."""
    api_key = os.environ.get("DASHSCOPE_API_KEY")
    if not api_key:
        raise RuntimeError(
            "Set DASHSCOPE_API_KEY before running this example.",
        )

    credential = DashScopeCredential(api_key=api_key)
    chat_model = DashScopeChatModel(
        credential=credential,
        model="qwen-plus",
        stream=False,
    )
    embedding_model = DashScopeEmbeddingModel(
        credential=credential,
        model="text-embedding-v4",
        dimensions=1024,
    )

    store = QdrantStore(location=":memory:")
    async with store:
        # One :class:`KnowledgeBase` handle binds embedding + vector store +
        # collection together.  ``insert_document`` / ``search`` /
        # ``list_documents`` all go through it, and the backing
        # collection is created lazily on first use.
        knowledge = KnowledgeBase(
            name="acme-handbook",
            description="Acme HR policies and AgentScope 3.0 release notes.",
            embedding_model=embedding_model,
            vector_store=store,
            collection=COLLECTION,
        )

        await index_corpus(knowledge)

        # ---- Mode 1: static ----
        # Search is automatic on the first reasoning step. The injected
        # ``HintBlock`` is one-shot (removed after the model call) so it
        # doesn't poison the next turn.
        static_mw = RAGMiddleware(
            knowledge_bases=[knowledge],
            parameters=RAGMiddleware.Parameters(
                mode="static",
                top_k=3,
                emit_hint_event=False,
            ),
        )
        static_agent = build_agent(
            "rag-static-agent",
            chat_model=chat_model,
            rag_mw=static_mw,
        )
        await ask(
            static_agent,
            "How many remote days per week does Acme allow?",
        )

        # ---- Mode 2: agentic ----
        # The middleware exposes a ``search_knowledge`` tool instead of
        # auto-injecting. The model decides when to call it; it may
        # also pass ``knowledge_bases=[...]`` to scope the search when
        # multiple knowledge bases are bound.
        agentic_mw = RAGMiddleware(
            knowledge_bases=[knowledge],
            parameters=RAGMiddleware.Parameters(mode="agentic", top_k=3),
        )
        agentic_agent = build_agent(
            "rag-agentic-agent",
            chat_model=chat_model,
            rag_mw=agentic_mw,
        )
        await ask(
            agentic_agent,
            "Summarise what's new in the AgentScope 3.0 release notes.",
        )


if __name__ == "__main__":
    asyncio.run(main())