Practical Multi Agent RAG using CrewAI, Weaviate, Groq and ExaTool.

In 2023, Retrieval-Augmented Generation (RAG) applications became a cornerstone for AI-native solutions, offering powerful context retrieval capabilities. Now, in 2024, the rise of agentic applications is revolutionizing automation through AI agents, evolving traditional Robotic Process Automation (RPA) into a more adaptive RPA 2.0 (workflow automation using AI agents). By combining agentic workflows with RAG, we can enhance AI-native applications to not only retrieve relevant context but also automate repetitive tasks that organizations rely on daily. This shift is impacting industries across the board, where tasks like research, document analysis, and report generation are routine and demand a streamlined approach.

What is agentic RAG?

Agentic RAG merges AI agents with RAG systems, enabling agents to retrieve context and relevant details autonomously to accomplish their objectives. Within agentic applications, these agents can dynamically access sources such as internal documents (PDFs, DOCX, Markdown) to efficiently complete tasks. In this practical guide, we'll explore a hands-on approach to creating a team of agents, each with specialized roles, managed by a central "manager agent." Together, we'll build custom tools for our agents while leveraging CrewAI tools to enhance their collaborative capabilities. You can find the full code on Github.

Practical App Overview

This guide will show you how to build RAG powered CrewAI agents that enables code_interpretation, rag, memory, and building a custom tool. This will be a practical guide that will show you how to build a report using internal documents, competitor research, and graph visualizations.

Output

A report with graph visualizations like this:

Goal:

Given a query, find relevant docs to generate a report that includes businesses financial data as well as a graph visualizations as a completed report.

Steps

  1. Fetching relevant docs based on a query.
  2. Competitor Research
  3. Generating a writen doc
  4. Generating graphs for data analysis

Tech Stack

  • CrewAI: The leading multi-agent platform
  • Weaviate: The AI-native database for a new generation of software
  • Groq: Fast AI Inference
  • ExaSearchTool: The search engine for AI
  • WebsiteSearchTool: Built in web rag tool from crewai_tools

Getting Started

Scaffold a template with the crewai cli

pip install crewai crewai_tool exa-py weaviate-client
crewai create crew agentic-rag

With CrewAI, setting up agents and tasks are super easy using yaml.

document_rag_agent:
  role: >
    Document RAG Agent
  goal: >
    Answer questions about the documents in the Weaviate database.
    The question is {query}
  backstory: >
    You are a document retrieval agent that can answer questions about the documents in the Weaviate database.
    Documents are internal documents regarding the company
    You have tools that allow you to search the information in the Weaviate database.

web_agent:
  role: >
    Web Agent
  goal: >
    Answer questions using the web like the EXASearchTool
    The question is {query}
  backstory: >
    You're a web search agent that can answer questions using the web.
    your ability to turn complex data into clear and concise reports, making
    it easy for others to understand and act on the information you provide.
    You have tools that allow you to search the information on the web.

code_execution_agent:
  role: >
    Code Execution Agent for data visualization
  goal: >
    You are a senior python developer that can execute code to generate the output
    Most of your tasks will be to generate python code to visualze data passed to you.  
    Execute the code and return the output.
    The output file should be valid python code only.
  backstory: >
    You are a senior python developer that can execute code to generate the output
    You have tools that allow you to execute python code.
    Execute the code and return the output.
  allow_code_execution: true

Defining AI agents should be simple. CrewAI makes it easy. Your agents just need a couple required attributes:

  1. role
  2. goal
  3. backstory

Notice:

code_execution_agent:
  role: >
    Code Execution Agent for data visualization
  goal: >
    You are a senior python developer that can execute code to generate the output
    Most of your tasks will be to generate python code to visualze data passed to you.  
    Execute the code and return the output.
    The output file should be valid python code only.
  backstory: >
    You are a senior python developer that can execute code to generate the output
    You have tools that allow you to execute python code.
    Execute the code and return the output.
  allow_code_execution: true

We have an agent, code_execution_agent, that will generate python code in a docker container. All you need is the allow_code_execution flag as true.

tasks.yaml

fetch_tax_docs_task:
  description: >
    Find the relevant tax documents according to the question: {query}
    You can use the WeaviateTool to find the relevant documents.
    You need to provide the query and generate an appropriate question for the WeaviateTool.
  expected_output: >
    The relevant tax documents according to the question and the query.

answer_question_task:
  description: >
    Find our competitors and their financial data.
    Use the WebsiteSearchTool and EXASearchTool to find the relevant information.
  expected_output: >
    The answer to the question.
    The answer should be in markdown format

business_trends_task:
  description: >
    Generate a report on the latest trends we found in our business. Take our tax data and compare them from year to year: [2020, 2021, 2022, 2023].
    You might want to use WeaviateTool to find the relevant tax documents to generate the trends.
  expected_output: >
    The latest business trends data from all business years.
    The report should describe the trends in the data.
  output_file: "outputs/business_trends.md"

graph_visualization_task:
  description: >
    Generate a graph based on the data generating trends from the business_trends_task.
    Use matplotlib to generate the graphs.
    You can use the code execution agent to execute the code and generate the output.
    The output file should be a python file and not markdown.
  expected_output: >
    The graph as a png file.
    The output file should be a python file and not markdown.
  output_file: "outputs/visualize.ipynb"

Define your workflow. What tasks do you want executed. This can be clear.

main.py

def run():
    """
    Run the crew.
    """
    inputs = {"query": "What was the year with the highest total expenses?"}
    result = AgenticRagCrew().crew().kickoff(inputs=inputs)

    if isinstance(result, str) and result.startswith("```python"):
        code = result[9:].strip()
        if code.endswith("```"):
            code = code[:-3].strip()

        with open("outputs/visualize.ipynb", "w") as f:
            f.write(code)

Creating a Custom Tool

Creating the WeaviateTool

import json
import weaviate

from crewai_tools import BaseTool
from pydantic import BaseModel, Field
from typing import Type, Optional, Any
from weaviate.classes.config import Configure

class WeaviateToolSchema(BaseModel):
    """Input for WeaviateTool."""

    query: str = Field(
        ...,
        description="The query to search retrieve relevant information from the Weaviate database. Pass only the query, not the question.",
    )

class WeaviateTool(BaseTool):
    """Tool to search the Weaviate database"""

    name: str = "WeaviateTool"
    description: str = "A tool to search the Weaviate database for relevant information on internal documents"
    args_schema: Type[BaseModel] = WeaviateToolSchema
    query: Optional[str] = None

    def _run(self, query: str) -> str:
        """Search the Weaviate database

        Args:
            query (str): The query to search retrieve relevant information from the Weaviate database. Pass only the query as a string, not the question.

        Returns:
            str: The result of the search query
        """
        client = weaviate.connect_to_local()
        internal_docs = client.collections.get("tax_docs")

        if not internal_docs:
            internal_docs = client.collections.create(
                name="tax_docs",
                vectorizer_config=Configure.Vectorizer.text2vec_ollama(  # Configure the Ollama embedding integration
                    api_endpoint="http://host.docker.internal:11434",  # Allow Weaviate from within a Docker container to contact your Ollama instance
                    model="nomic-embed-text",  # The model to use
                ),
                generative_config=Configure.Generative.ollama(
                    model="llama3.2:1b",
                    api_endpoint="http://host.docker.internal:11434",
                ),
            )

        response = internal_docs.query.near_text(
            query=query,
            limit=3,
        )
        json_response = ""
        for obj in response.objects:
            json_response += json.dumps(obj.properties, indent=2)
    
        client.close()
        return json_response

Setup for WeaviateDB Added some business finanical data inside directory called /internal_docs

docker-compose.yml

services:
  weaviate:
    command:
    - --host
    - 0.0.0.0
    - --port
    - '8080'
    - --scheme
    - http
    image: cr.weaviate.io/semitechnologies/weaviate:1.27.2
    ports:
    - 8080:8080
    - 50051:50051
    volumes:
    - weaviate_data:/var/lib/weaviate
    restart: on-failure:0
    environment:
      QUERY_DEFAULTS_LIMIT: 25
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
      PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
      DEFAULT_VECTORIZER_MODULE: 'none'
      ENABLE_API_BASED_MODULES: 'true'
      ENABLE_MODULES: 'text2vec-ollama,generative-ollama'
      CLUSTER_HOSTNAME: 'node1'
volumes:
  weaviate_data:

Preloading docs to WeaviateDB

import os
import weaviate
import json
from weaviate.classes.config import Configure

client = weaviate.connect_to_local()

print(client.is_ready())

internal_docs = client.collections.get("tax_docs")
if not internal_docs:
    internal_docs = client.collections.create(
        name="tax_docs",
        vectorizer_config=Configure.Vectorizer.text2vec_ollama(  # Configure the Ollama embedding integration
            api_endpoint="http://host.docker.internal:11434",  # Allow Weaviate from within a Docker container to contact your Ollama instance
            model="nomic-embed-text",  # The model to use
        ),
        generative_config=Configure.Generative.ollama(
            model="llama3.2:1b",
            api_endpoint="http://host.docker.internal:11434",
        ),
    )

docs_dir = os.path.join(
    os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "internal_docs"
)
markdown_files = [f for f in os.listdir(docs_dir) if f.endswith(".md")]

with internal_docs.batch.dynamic() as batch:
    for filename in markdown_files:
        with open(os.path.join(docs_dir, filename), "r") as f:
            content = f.read()
            batch.add_object(
                {
                    "content": content,
                    "class_name": filename.split(".")[0],
                }
            )
            print(f"Added {filename.split('.')[0]} to Weaviate")

Examples Document

## Year: 2020

## Income Statement

Gross Sales: $150,000
Returns & Allowances: $3,000
Net Sales: $147,000

## Cost of Goods Sold

Beginning Inventory: $10,000
Purchases: $60,000
Ending Inventory: $15,000
COGS: $55,000

## Expenses

Rent: $30,000
Utilities: $6,000
Salaries & Wages: $50,000
Marketing & Advertising: $4,000
Miscellaneous (Supplies, Insurance): $5,000
Total Expenses: $95,000

## Net Income (Loss)

Gross Profit: $92,000
Net Income (before taxes): $(3,000) (Loss)

## Balance Sheet (End of Year)

### Assets

Cash: $5,000
Inventory: $15,000
Equipment: $25,000

### Liabilities

Accounts Payable: $8,000

### Owner's Equity

Owner's Equity: $37,000

I have a couple years worth for this data in but add the documents you want. We will generate the embeddings with ollama (great for sensitive data)

crew.py

import os
from crewai import Agent, Crew, Process, Task, LLM
from crewai.project import CrewBase, agent, crew, task
from agentic_rag.tools.weaviate_tool import WeaviateTool
from crewai_tools import EXASearchTool

from dotenv import load_dotenv
load_dotenv()

@CrewBase
class AgenticRagCrew:
    """AgenticRag crew"""

    llm = LLM(model="groq/llama-3.1-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))

    @agent
    def document_rag_agent(self) -> Agent:
        return Agent(
            config=self.agents_config["document_rag_agent"],
            tools=[WeaviateTool()],
            verbose=True,
            llm=self.llm,
        )
    @agent
    def web_agent(self) -> Agent:
        return Agent(
            config=self.agents_config["web_agent"],
            tools=[EXASearchTool()],
            verbose=True,
            llm=self.llm,
        )
    @agent
    def code_execution_agent(self) -> Agent:
        return Agent(
            config=self.agents_config["code_execution_agent"],
            verbose=True,
            llm=self.llm,
        )
    @task
    def fetch_tax_docs_task(self) -> Task:
        return Task(
            config=self.tasks_config["fetch_tax_docs_task"],
        )
    @task
    def answer_question_task(self) -> Task:
        return Task(
            config=self.tasks_config["answer_question_task"], output_file="report.md"
        )
    @task
    def business_trends_task(self) -> Task:
        return Task(config=self.tasks_config["business_trends_task"])
    @task
    def graph_visualization_task(self) -> Task:
        return Task(config=self.tasks_config["graph_visualization_task"])
    @crew
    def crew(self) -> Crew:
        """Creates the AgenticRag crew"""
        return Crew(
            agents=self.agents,  # Automatically created by the @agent decorator
            tasks=self.tasks,  # Automatically created by the @task decorator
            process=Process.hierarchical,
            verbose=True,
            manager_llm="openai/gpt-4o",
        )

Hierarchical Process 

Enables full agency when it comes to our AI agents making decisions. Our tasks involve:

  • Fetching relevant docs based on a query.
  • Competitor Research
  • Generating a writen doc 
  • Generating graphs for data analysis

Based on these tasks, our manager agent has access to delegating to our specialized agents for:

  • document_rag_agent
  • web_agent
  • code_execution_agent

Execute

Running our crew is easy. Just do crewai run

crewai run

Output

Next Steps

Deploy to production at https://app.crewai.com. You can start for free.

Conclusion

This practical guide showed you how do build an agentic automation leveraging rag capabilities to provide your agents with relevant information to succeed.  While you're exploring use cases, think about something you do everyday repetitively. I challenge you to build an automation for it.

Good luck. If you have any questions, you can find me on twitter/x.


If you found this article helpful, consider subscribing to my free Substack for more articles like this. 👇