Source code for fenn.agents.rag.rag_node
from typing import Any
from fenn.agents import Node
from .loader import load_documents
from .retriever import Retriever
[docs]
class RAGNode(Node):
"""
Flow node that retrieves relevant context from indexed sources.
Loads and indexes all sources once at construction time, then per run
queries the index using ``shared[query_key]`` and writes the results
into ``shared[chunks_key]`` and ``shared[context_key]``.
Parameters
----------
sources : str or list of str, optional
File paths, folder paths, or URLs to load and index on init.
Additional sources can be indexed later with :meth:`add_source`.
query_key : str
Key in ``shared`` that holds the user query. Default: ``"query"``.
context_key : str
Key written into ``shared`` with the concatenated chunk text.
Default: ``"rag_context"``.
chunks_key : str
Key written into ``shared`` with the raw list of chunks.
Default: ``"rag_chunks"``.
top_k : int
Maximum number of chunks to retrieve. Default: 5.
next_action : str
Action string returned by ``post()``, used by ``Flow.get_next_node()``.
Default: ``"default"``.
faiss : bool
Use FAISS semantic search instead of BM25. Default: False.
embedding_provider : str
Embedding provider (only used when faiss=True). Default: ``"local"``.
embedding_model : str
Embedding model (only used when faiss=True). Default: ``"all-MiniLM-L6-v2"``.
embedding_api_key : str, optional
API key for the embedding provider.
chunk_mode : str
Document chunking strategy. One of ``"smart"``, ``"paragraphs"``,
``"sentences"``, ``"fixed"``. Default: ``"smart"``.
persist_path : str or Path, optional
Directory to save/load the FAISS index. Only used when faiss=True.
"""
[docs]
def __init__(
self,
sources: str | list[str] | None = None,
query_key: str = "query",
context_key: str = "rag_context",
chunks_key: str = "rag_chunks",
top_k: int = 5,
next_action: str = "default",
faiss: bool = False,
embedding_provider: str = "local",
embedding_model: str = "all-MiniLM-L6-v2",
embedding_api_key: str | None = None,
chunk_mode: str = "smart",
persist_path: str | None = None,
) -> None:
super().__init__()
self._query_key = query_key
self._context_key = context_key
self._chunks_key = chunks_key
self._top_k = top_k
self._next_action = next_action
self._retriever = Retriever(
use_faiss=faiss,
embedding_provider=embedding_provider,
embedding_model=embedding_model,
embedding_api_key=embedding_api_key,
chunk_mode=chunk_mode,
persist_path=persist_path,
)
if sources:
if isinstance(sources, str):
sources = [sources]
for s in sources:
self._retriever.index(load_documents(s))
[docs]
def add_source(self, source: str) -> "RAGNode":
"""Index an additional source. Returns self for chaining."""
self._retriever.index(load_documents(source))
return self
[docs]
def prep(self, shared: dict[str, Any]) -> str:
return shared.get(self._query_key, "")
[docs]
def exec(self, query: str) -> list[str]:
return self._retriever.query(query, top_k=self._top_k)
[docs]
def post(self, shared: dict[str, Any], query: str, chunks: list[str]) -> str:
shared[self._chunks_key] = chunks
shared[self._context_key] = "\n\n".join(chunks)
return self._next_action