c3bf08ac8d
K8s Workspace Integration Tests / k8s-workspace-tests (push) Has been cancelled
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.11) (push) Has been cancelled
Web UI / check (push) Has been cancelled
199 lines
6.9 KiB
Python
199 lines
6.9 KiB
Python
# -*- 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())
|