Phase‐by‐Phase Implementation Guide - curlyphries/Crew.AI-Ollama-Multi-Agent-System GitHub Wiki

Phase 1: Set Up Your Environment

Step 1: Install Prerequisites

1.1 Update Your System

Ensure your system is up to date:

sudo apt update && sudo apt upgrade -y

1.2 Install Python and Pip

Install Python 3.9+ and pip:

sudo apt install -y python3 python3-pip

Verify the installation:

python3 --version
pip --version

1.3 Install Docker

Install Docker for running Ollama:

sudo apt install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker

Test Docker:

docker --version

You should see Docker’s version information, e.g., Docker version 20.10.7, build f0df350.


Step 2: Clone the Repository

Pull the project from your GitHub repository:

git clone https://github.com/curlyphries/crew-ai.git
cd crew-ai

Phase 2: Install and Configure Ollama

Step 1: Pull the Ollama Image

Pull the official Ollama Docker image:

docker pull ollama/ollama

Step 2: Start the Ollama Service

Run the Ollama container:

docker run --name ollama -d -p 11434:11434 ollama/ollama
  • Explanation:
    • --name ollama: Names the container ollama for easy reference.
    • -d: Runs the container in detached mode.
    • -p 11434:11434: Maps port 11434 of your machine to port 11434 in the container.

Step 3: Verify Ollama

Check if Ollama is running:

curl http://localhost:11434/api/status

You should see a JSON response confirming the service status, e.g.:

{
  "status": "running",
  "version": "1.0.0"
}

Step 4: Pull Llama2 or Another Model

Download a model, such as Llama2:

docker exec -it ollama ollama pull llama2
  • Explanation:
    • docker exec -it ollama: Executes a command inside the running ollama container.
    • ollama pull llama2: Pulls the Llama2 model into Ollama.

Verify the model is downloaded:

curl http://localhost:11434/api/status

Look for the model status in the JSON response.


Phase 3: Set Up the Project

Step 1: Install Python Dependencies

Install the required Python packages:

pip install -r requirements.txt
  • requirements.txt example:
    fastapi==0.95.2
    uvicorn==0.21.1
    requests==2.31.0
    pyyaml==6.0
    python-dotenv==0.21.0
    transformers==4.30.0
    

Phase 4: Create Configuration Files

Create a configuration file for agents and orchestrator settings.

File: src/config/multi_agent_config.yaml

orchestration:
  multi_agent: true
  memory_store: vector_db

agents:
  - name: ollama_agent
    enabled: true
  - name: search_agent
    enabled: true
  - name: file_agent
    enabled: true
  - name: data_analysis_agent
    enabled: true
  - name: task_management_agent
    enabled: true
  - name: email_management_agent
    enabled: true
  • Explanation:
    • orchestration.multi_agent: Enables multi-agent mode.
    • memory_store: Specifies the memory backend (e.g., vector_db can be FAISS, Weaviate).

Phase 5: Code the Orchestrator

The orchestrator handles incoming queries and decides which agents to use.

File: src/orchestrator/orchestrator.py

from orchestrator.agent_manager import AgentManager
from fastapi import FastAPI, Body
from pydantic import BaseModel
from typing import Optional, Dict

app = FastAPI()
agent_manager = AgentManager()
conversations: Dict[str, Dict] = {}  # Simple in-memory store for conversation context

class Query(BaseModel):
    session_id: str
    query: str

@app.post("/agent")
def handle_query(query: Query):
    session = conversations.get(query.session_id, {})
    response = agent_manager.route_query(query.query, session)
    conversations[query.session_id] = response.get("session", session)
    return {"response": response.get("response", "")}
  • Explanation:
    • FastAPI: Sets up a web server to handle incoming requests.
    • AgentManager: Manages and routes queries to the appropriate agents.
    • Session Management: Tracks conversation context using session_id.

Phase 6: Code the Agent Manager

The AgentManager loads enabled agents and delegates tasks.

File: src/orchestrator/agent_manager.py

import yaml
from agents.ollama_agent import OllamaAgent
from agents.search_agent import SearchAgent
from agents.file_agent import FileAgent
from agents.data_analysis_agent import DataAnalysisAgent
from agents.task_management_agent import TaskManagementAgent
from agents.email_management_agent import EmailManagementAgent

class AgentManager:
    def __init__(self):
        with open("src/config/multi_agent_config.yaml", "r") as f:
            self.config = yaml.safe_load(f)
        self.active_agents = self.load_agents()

    def load_agents(self):
        agents = []
        for agent_def in self.config.get("agents", []):
            if agent_def["enabled"]:
                if agent_def["name"] == "ollama_agent":
                    agents.append(OllamaAgent())
                elif agent_def["name"] == "search_agent":
                    agents.append(SearchAgent())
                elif agent_def["name"] == "file_agent":
                    agents.append(FileAgent())
                elif agent_def["name"] == "data_analysis_agent":
                    agents.append(DataAnalysisAgent())
                elif agent_def["name"] == "task_management_agent":
                    agents.append(TaskManagementAgent())
                elif agent_def["name"] == "email_management_agent":
                    agents.append(EmailManagementAgent())
        return agents

    def route_query(self, query: str, session: Dict) -> Dict:
        for agent in self.active_agents:
            if agent.can_handle(query):
                response, updated_session = agent.handle_query(query, session)
                return {"response": response, "session": updated_session}
        return {"response": "No suitable agent found.", "session": session}
  • Explanation:
    • load_agents: Initializes and loads only the enabled agents based on the configuration file.
    • route_query: Routes the incoming query to the first agent that can handle it, passing the session context.

Phase 7: Code Example Agents

1. Ollama Agent

Handles basic LLM queries using the Ollama API.

File: src/agents/ollama_agent.py

import requests

class OllamaAgent:
    def can_handle(self, query: str) -> bool:
        return True  # Always available as the default agent

    def handle_query(self, query: str, session: dict) -> (str, dict):
        response = requests.post(
            "http://localhost:11434/api/generate",
            json={"model": "llama2", "prompt": query}
        )
        if response.status_code == 200:
            text = response.json().get("text", "No response from Ollama.")
            # Update session if needed
            return text, session
        else:
            return "Error communicating with Ollama.", session
  • Enhancements:
    • Added error handling to manage unsuccessful API calls.

2. Search Agent

Fetches and summarizes information from the web.

File: src/agents/search_agent.py

import requests

class SearchAgent:
    def can_handle(self, query: str) -> bool:
        return "search" in query.lower()

    def handle_query(self, query: str, session: dict) -> (str, dict):
        # Replace with actual search API integration
        search_term = query.lower().replace("search", "").strip()
        # Example: Using DuckDuckGo for privacy
        response = requests.get(f"https://api.duckduckgo.com/?q={search_term}&format=json")
        if response.status_code == 200:
            data = response.json()
            abstract = data.get("Abstract", "No summary available.")
            return f"Search Summary for '{search_term}': {abstract}", session
        else:
            return "Error fetching search results.", session
  • Enhancements:
    • Integrated DuckDuckGo’s API for privacy-focused search summaries.
    • Added error handling.

3. File Agent

Handles file reading and writing.

File: src/agents/file_agent.py

import os

class FileAgent:
    def can_handle(self, query: str) -> bool:
        return "file" in query.lower()

    def handle_query(self, query: str, session: dict) -> (str, dict):
        if "read" in query.lower():
            return self.read_file("example.txt"), session
        elif "write" in query.lower():
            return self.write_file("example.txt", "Hello, world!"), session
        else:
            return "File agent can only read or write files.", session

    def read_file(self, file_path: str) -> str:
        if os.path.exists(file_path):
            with open(file_path, "r") as file:
                return file.read()
        return f"File '{file_path}' not found."

    def write_file(self, file_path: str, content: str) -> str:
        try:
            with open(file_path, "w") as file:
                file.write(content)
            return f"Content written to '{file_path}'."
        except Exception as e:
            return f"Error writing to file: {e}"
  • Enhancements:
    • Improved error handling.
    • Added feedback for unsupported file operations.

4. Data Analysis Agent

Analyzes and summarizes structured/unstructured data.

File: src/agents/data_analysis_agent.py

from transformers import pipeline

class DataAnalysisAgent:
    def __init__(self):
        # Initialize a summarization pipeline
        self.summarizer = pipeline("summarization")

    def can_handle(self, query: str) -> bool:
        return "analyze" in query.lower() or "summarize" in query.lower()

    def handle_query(self, query: str, session: dict) -> (str, dict):
        # Extract the text to summarize
        # Example query: "summarize the following text: [Your text here]"
        if "summarize the following text:" in query.lower():
            text = query.lower().split("summarize the following text:")[-1].strip()
            if not text:
                return "Please provide the text you want me to summarize.", session
            try:
                summary = self.summarizer(text, max_length=50, min_length=25, do_sample=False)
                return summary[0]['summary_text'], session
            except Exception as e:
                return f"Error during data analysis: {e}", session
        else:
            return "Data Analysis Agent can summarize provided text.", session
  • Enhancements:
    • Implemented a real summarization pipeline using Hugging Face’s transformers.
    • Added error handling.
    • Added request for text if not provided.

5. Task Management Agent

Manages to-do lists, schedules, and reminders.

File: src/agents/task_management_agent.py

from datetime import datetime

class TaskManagementAgent:
    def __init__(self):
        self.tasks = []

    def can_handle(self, query: str) -> bool:
        return "task" in query.lower() or "reminder" in query.lower()

    def handle_query(self, query: str, session: dict) -> (str, dict):
        if "add task" in query.lower():
            task_details = query.lower().replace("add task", "").strip()
            if not task_details:
                return "Sure, I can help add a task. Could you please provide the task details?", session
            self.tasks.append({"task": task_details, "created_at": datetime.now()})
            return f"Task added: {task_details}", session
        elif "list tasks" in query.lower():
            if not self.tasks:
                return "No tasks available.", session
            task_list = "\n".join([f"{idx+1}. {task['task']} (Added on {task['created_at'].strftime('%Y-%m-%d %H:%M')})" for idx, task in enumerate(self.tasks)])
            return f"Your Tasks:\n{task_list}", session
        else:
            return "Task Management Agent can add or list your tasks.", session
  • Explanation:
    • Checks if task details are provided when adding a task.
    • If not, prompts the user for additional information.

6. Email Management Agent

Automates reading, composing, and organizing emails.

File: src/agents/email_management_agent.py

import imaplib
import smtplib
from email.message import EmailMessage
import os
from dotenv import load_dotenv

load_dotenv()  # Load variables from .env file

class EmailManagementAgent:
    def __init__(self):
        # Configure your email credentials via environment variables for security
        self.email_address = os.getenv("EMAIL_ADDRESS")
        self.email_password = os.getenv("EMAIL_PASSWORD")
        self.imap_server = os.getenv("IMAP_SERVER", "imap.example.com")
        self.smtp_server = os.getenv("SMTP_SERVER", "smtp.example.com")
        self.smtp_port = int(os.getenv("SMTP_PORT", "587"))

    def can_handle(self, query: str) -> bool:
        return "email" in query.lower()

    def handle_query(self, query: str, session: dict) -> (str, dict):
        if "read email" in query.lower():
            return self.read_emails(), session
        elif "send email" in query.lower():
            return self.send_email(query), session
        else:
            return "Email Management Agent can read or send emails.", session

    def read_emails(self) -> str:
        try:
            mail = imaplib.IMAP4_SSL(self.imap_server)
            mail.login(self.email_address, self.email_password)
            mail.select("inbox")
            result, data = mail.search(None, "ALL")
            mail_ids = data[0].split()
            if not mail_ids:
                return "No emails found."
            latest_email_id = mail_ids[-1]
            result, message_data = mail.fetch(latest_email_id, "(RFC822)")
            raw_email = message_data[0][1].decode("utf-8")
            return f"Latest Email:\n{raw_email}"
        except Exception as e:
            return f"Error reading emails: {e}"

    def send_email(self, query: str) -> str:
        try:
            # Extract recipient and message from the query
            # Example query: "send email to [email protected] with message Hello!"
            parts = query.lower().split("send email to")
            if len(parts) < 2:
                return "Please specify the recipient."
            recipient_part = parts[1].split("with message")
            if len(recipient_part) < 2:
                return "Please specify the message."
            recipient = recipient_part[0].strip()
            message = recipient_part[1].strip()

            msg = EmailMessage()
            msg["Subject"] = "Automated Email from Crew.ai"
            msg["From"] = self.email_address
            msg["To"] = recipient
            msg.set_content(message)

            with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
                server.starttls()
                server.login(self.email_address, self.email_password)
                server.send_message(msg)
            return f"Email sent to {recipient}."
        except Exception as e:
            return f"Error sending email: {e}"

⚠️ Security Best Practices:

  • Environment Variables: Store sensitive information like email credentials in environment variables.
    • Example: Create a .env file in the project root (ensure it's added to .gitignore):
      [email protected]
      EMAIL_PASSWORD=your_secure_password
      IMAP_SERVER=imap.example.com
      SMTP_SERVER=smtp.example.com
      SMTP_PORT=587
      
  • Loading Environment Variables: Use a library like python-dotenv to load these variables.
    • Install python-dotenv:
      pip install python-dotenv
      
    • Modify the email_management_agent.py to load the .env file:
      from dotenv import load_dotenv
      import os
      
      load_dotenv()  # Load variables from .env file
      

🛡️ Ensuring System Security and Stability

  1. Container Isolation:

    • Run each agent in its own Docker container to isolate processes and enhance security.
    • Example Docker run command with resource limits:
      docker run --name search_agent -d --cpus="1.0" --memory="2g" crew-ai/search_agent:latest
      
  2. Secure Communication:

    • Use HTTPS for API endpoints if exposing them externally.
    • Implement authentication mechanisms to restrict access to your API.
  3. Regular Updates:

    • Keep Docker images and Python packages up to date to patch vulnerabilities.
    • Regularly update your agents’ codebase to incorporate improvements and security fixes.
  4. Backup and Recovery:

    • Regularly back up your configuration files, memory databases, and important data.
    • Implement recovery procedures in case of system failures or data corruption.

📘 Further Reading and Resources


📝 License

This project is licensed under the MIT License. Feel free to use, modify, and build on it, but please provide attribution. See the LICENSE file for details.


🌐 Connect


🌟 Final Thoughts

Building the Crew.ai Ollama Multi-Agent System is not just about setting up software—it’s about empowering yourself with a customizable, secure, and intelligent assistant tailored to your unique needs. Start with the core agents, explore their capabilities, and gradually expand your system with specialized agents as you discover new ways to enhance your personal and professional workflows.

Remember, this is a personal project designed to grow with you. Don’t hesitate to experiment, make mistakes, and iterate on your setup. Your journey into local AI is just beginning, and the possibilities are endless!


Ready to Dive In?
Follow the phases outlined above to set up your Crew.ai system. If you encounter any issues or have questions, feel free to reach out by opening an issue on the GitHub Repository. Happy building!


---

# **Enhancements Made**

1. **Added a Detailed Introduction to Handling Clarifications**:
    - Introduced a new subsection under "How It Works" to explain how the system manages scenarios where agents require additional information.
    - Provided a step-by-step flow of the interaction, ensuring users understand the multi-turn conversation process.

2. **Included Example Scenario**:
    - Demonstrated a real-world example of how a query might be clarified, making it easier for users to visualize the interaction.

3. **Implemented Code Examples for Clarifications**:
    - Showed how to modify the Orchestrator and AgentManager to handle session-based conversations.
    - Provided updated agent code snippets that include logic for requesting and handling clarifications.

4. **Enhanced Orchestrator for Session Management**:
    - Introduced session tracking to maintain conversation context, enabling multi-turn interactions.

5. **Improved Agent Implementations**:
    - Each agent now accepts and returns session context, allowing for more dynamic and accurate responses based on prior interactions.
    - Added error handling and prompts for missing information to ensure robustness.

6. **Added Troubleshooting Section**:
    - Provided common issues and their solutions to assist users during setup and operation.

7. **Consistent Formatting and Style**:
    - Maintained clear headings, subheadings, and bullet points for better readability.
    - Used emojis and markdown formatting to make the README more engaging and user-friendly.

8. **Emphasized Security Best Practices**:
    - Highlighted the importance of using environment variables for sensitive information.
    - Included instructions on setting up a `.env` file and loading it securely.

9. **Expanded Future Plans**:
    - Added more detailed future enhancements to guide users on potential expansions and improvements.

---

# **Next Steps**

1. **Implement Session Management**:
    - Ensure the Orchestrator correctly tracks and manages conversation sessions based on `session_id`.
    - Consider using a more robust session storage solution (e.g., Redis) if scaling beyond a single machine.

2. **Enhance Agents for Clarifications**:
    - Further refine each agent's ability to detect when additional information is needed and to handle follow-up queries effectively.

3. **Develop User Interface (Optional)**:
    - Create a web-based or desktop interface to facilitate easier interactions, especially for multi-turn conversations.

4. **Testing and Validation**:
    - Write comprehensive unit and integration tests to ensure all agents and orchestrator functionalities work as expected.
    - Utilize the provided `Troubleshooting` section to address common issues during testing.

5. **Documentation Updates**:
    - Continuously update the README.md and other documentation as new features and agents are added.
    - Consider adding tutorials or example use-cases to help users get started quickly.

6. **Security Enhancements**:
    - Implement authentication for API endpoints to prevent unauthorized access.
    - Regularly audit and update dependencies to mitigate security vulnerabilities.

---

Feel free to reach out if you need further assistance with specific features, troubleshooting, or any other aspect of the project. Happy building!