Spaces:
Paused
Paused
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())
|