import nest_asyncio from llama_index.llms.ollama import Ollama from llama_index.embeddings.huggingface import HuggingFaceEmbedding from llama_index.core.settings import Settings from llama_index.core.workflow import Event, Context, Workflow, StartEvent, StopEvent, step from llama_index.core.schema import NodeWithScore from llama_index.core import SimpleDirectoryReader, VectorStoreIndex from llama_index.core.response_synthesizers import CompactAndRefine # Apply nest_asyncio to allow nested event loops nest_asyncio.apply() class RetrieverEvent(Event): """Result of running retrieval""" nodes: list[NodeWithScore] class RAGWorkflow(Workflow): def __init__(self, model_name="llama3.2", embedding_model="BAAI/bge-small-en-v1.5"): super().__init__() # Initialize LLM and embedding model self.llm = Ollama(model=model_name) self.embed_model = HuggingFaceEmbedding(model_name=embedding_model) # Configure global settings Settings.llm = self.llm Settings.embed_model = self.embed_model self.index = None @step async def ingest(self, ctx: Context, ev: StartEvent) -> StopEvent | None: """Entry point to ingest documents from a directory.""" dirname = ev.get("dirname") if not dirname: return None documents = SimpleDirectoryReader(dirname).load_data() self.index = VectorStoreIndex.from_documents(documents=documents) return StopEvent(result=self.index) @step async def retrieve(self, ctx: Context, ev: StartEvent) -> RetrieverEvent | None: """Entry point for RAG retrieval.""" query = ev.get("query") index = ev.get("index") or self.index if not query: return None if index is None: print("Index is empty, load some documents before querying!") return None retriever = index.as_retriever(similarity_top_k=2) nodes = await retriever.aretrieve(query) await ctx.set("query", query) return RetrieverEvent(nodes=nodes) @step async def synthesize(self, ctx: Context, ev: RetrieverEvent) -> StopEvent: """Generate a response using retrieved nodes.""" summarizer = CompactAndRefine(streaming=True, verbose=True) query = await ctx.get("query", default=None) response = await summarizer.asynthesize(query, nodes=ev.nodes) return StopEvent(result=response) async def query(self, query_text: str): """Helper method to perform a complete RAG query.""" if self.index is None: raise ValueError("No documents have been ingested. Call ingest_documents first.") result = await self.run(query=query_text, index=self.index) return result async def ingest_documents(self, directory: str): """Helper method to ingest documents.""" result = await self.run(dirname=directory) self.index = result return result # Example usage async def main(): # Initialize the workflow workflow = RAGWorkflow() # Ingest documents await workflow.ingest_documents("data") # Perform a query result = await workflow.query("How was DeepSeekR1 trained?") # Print the response async for chunk in result.async_response_gen(): print(chunk, end="", flush=True) if __name__ == "__main__": import asyncio asyncio.run(main())