chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:37:47 +08:00
commit 7653f56fed
1422 changed files with 359026 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
.DS_Store
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
# Build a reasoning model like DeepSeek-R1
This project implements building a reasoning model like DeepSeek-R1 using Unsloth.
---
## Setup and installations
**Install Dependencies**:
Ensure you have Python 3.11 or later installed.
```bash
pip install unsloth vllm
```
---
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
---
## Contribution
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
+65
View File
@@ -0,0 +1,65 @@
# MultiModal RAG with ColiVara and DeepSeek-Janus-Pro
This project implements a MultiModal RAG with DeepSeek's latest model Janus-Pro and ColiVara.
We use the following tools
- DeepSeek-Janus-Pro as the multi-modal LLM.
- [ColiVara](https://colivara.com/) for SOTA document understanding and retrieval.
- [Firecrawl](https://www.firecrawl.dev/i/api) for web scraping.
- Streamlit as the web interface.
## Demo
A demo of the project is available below:
![demo](./video-demo.mp4)
---
## Setup and installations
**Setup Janus**:
```
git clone https://github.com/deepseek-ai/Janus.git
pip install -e ./Janus
```
**Get the API keys**:
- [ColiVara](https://colivara.com/) for SOTA document understanding and retrieval.
- [Firecrawl](https://www.firecrawl.dev/i/api) for web scraping.
Create a .env file and store them as follows:
```python
COLIVARA_API_KEY="<COLIVARA-API-KEY>"
FIRECRAWL_API_KEY="<FIRECRAWL-API-KEY>"
```
**Install Dependencies**:
Ensure you have Python 3.11 or later installed.
```bash
pip install streamlit-pdf-viewer colivara-py streamlit fastembed flash-attn transformers
```
---
## Run the project
Finally, run the project by running the following command:
```bash
streamlit run app.py
```
---
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
---
## Contribution
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
+222
View File
@@ -0,0 +1,222 @@
# Adapted from https://docs.streamlit.io/knowledge-base/tutorials/build-conversational-apps#build-a-simple-chatbot-gui-with-streaming
import os
import base64
import gc
import random
import tempfile
import time
import uuid
from IPython.display import Markdown, display
import streamlit as st
import torch
import time
import numpy as np
from tqdm import tqdm
from pdf2image import convert_from_path
from rag_code import Retriever, RAG
from firecrawl import FirecrawlApp
from PIL import Image
from fpdf import FPDF
import io
import requests
import math
from colivara_py import ColiVara
from dotenv import load_dotenv
from streamlit_pdf_viewer import pdf_viewer
load_dotenv()
if "id" not in st.session_state:
st.session_state.id = uuid.uuid4()
st.session_state.collection_name = "webpage_collection" + str(st.session_state.id)
st.session_state.file_cache = {}
st.session_state.url_input = "" # Initialize URL input
st.session_state.pdf_displayed = False # Track if PDF is displayed
session_id = st.session_state.id
def reset_chat():
st.session_state.messages = []
st.session_state.context = None
# Don't reset URL and PDF state when clearing chat
gc.collect()
def display_pdf(file):
# Opening file from file path
if isinstance(file, str):
# If file is a path
with open(file, "rb") as f:
base64_pdf = base64.b64encode(f.read()).decode('utf-8')
else:
# If file is already a file object/buffer
base64_pdf = base64.b64encode(file.read()).decode('utf-8')
# Embedding PDF in HTML
pdf_display = f"""<embed
class="pdfobject"
type="application/pdf"
title="Embedded PDF"
src="data:application/pdf;base64,{base64_pdf}"
style="overflow: auto; width: 100%; height: 800px;">"""
# Displaying File
st.markdown(pdf_display, unsafe_allow_html=True)
def create_pdf_from_screenshot(screenshot_url):
response = requests.get(screenshot_url)
response.raise_for_status()
# Save screenshot
with open('image.png', 'wb') as f:
f.write(response.content)
image = Image.open('image.png')
width, height = image.size
slice_height = math.ceil(height / 10)
# Create PDF with custom page size matching the image aspect ratio
pdf = FPDF(unit='pt', format=[width, slice_height])
pdf.set_auto_page_break(auto=False) # Disable auto page break
for i in range(10):
top = i * slice_height
bottom = min((i + 1) * slice_height, height)
slice_img = image.crop((0, top, width, bottom))
temp_filename = f'temp_slice_{i}.png'
slice_img.save(temp_filename)
pdf.add_page()
# Add image with explicit dimensions matching the PDF page
pdf.image(temp_filename, x=0, y=0, w=width, h=bottom-top)
os.remove(temp_filename)
pdf_path = "screenshot_slices.pdf"
pdf.output(pdf_path)
return pdf_path
with st.sidebar:
st.header(f"Add your content!")
# Use session state to persist URL input
url_input = st.text_input("Enter webpage URL", value=st.session_state.url_input, key="url_field")
st.session_state.url_input = url_input # Store URL in session state
start_rag = st.button("Start RAG")
if start_rag and url_input:
try:
# Step 2: Get screenshot using FireCrawl
status_container = st.empty()
with status_container.status("Processing webpage...", expanded=True) as status:
status.write("🔍 Scraping webpage with Firecrawl...")
app = FirecrawlApp(api_key=os.getenv("FIRECRAWL_API_KEY"))
scrape_result = app.scrape_url(url_input,
params={'formats': ['screenshot@fullPage'],
'waitFor': 10000})
status.update(label="Creating PDF", state="running")
status.write("📄 Creating PDF from screenshot...")
# Step 3: Create PDF from screenshot
st.session_state.pdf_path = create_pdf_from_screenshot(scrape_result['screenshot'])
st.session_state.pdf_displayed = True # Mark PDF as ready to display
# Rest of the code for RAG setup
file_key = f"{session_id}-webpage.pdf"
if file_key not in st.session_state.get('file_cache', {}):
status.update(label="Indexing content", state="running")
status.write("🔎 Indexing content with ColiVara...")
# Initialize ColiVara client and process document
rag_client = ColiVara(api_key=os.getenv("COLIVARA_API_KEY"))
new_collection = rag_client.create_collection(
name=st.session_state.collection_name,
metadata={"description": "Webpage screenshots"}
)
document = rag_client.upsert_document(
collection_name=st.session_state.collection_name,
name="webpage_document",
document_path=st.session_state.pdf_path
)
# Initialize retriever and RAG
retriever = Retriever(rag_client=rag_client, collection_name=st.session_state.collection_name)
st.session_state.query_engine = RAG(retriever=retriever)
st.session_state.file_cache[file_key] = st.session_state.query_engine
else:
st.session_state.query_engine = st.session_state.file_cache[file_key]
status.update(label="Processing complete!", state="complete")
st.success("Ready to Chat!")
except Exception as e:
st.error(f"An error occurred: {e}")
st.stop()
# Always show PDF if it exists
if st.session_state.get('pdf_displayed', False) and hasattr(st.session_state, 'pdf_path'):
pdf_viewer(st.session_state.pdf_path)
col1, col2 = st.columns([6, 1])
# st.header("""# Multimodal RAG powered by <img src="data:image/png;base64,{}" width="170" style="vertical-align: -3px;"> Janus""".format(base64.b64encode(open("assets/deep-seek.png", "rb").read()).decode()))
with col1:
# # st.header("""
# # # Agentic RAG powered by <img src="data:image/png;base64,{}" width="170" style="vertical-align: -3px;">
# # """.format(base64.b64encode(open("assets/deep-seek.png", "rb").read()).decode()))
st.markdown("""
## Multimodal RAG powered by ColiVara SOTA Retrieval and <img src="data:image/png;base64,{}" width="170" style="vertical-align: -3px;"> Janus""".format(base64.b64encode(open("assets/deep-seek.png", "rb").read()).decode()), unsafe_allow_html=True)
with col2:
st.button("Clear ↺", on_click=reset_chat)
# Initialize chat history
if "messages" not in st.session_state:
reset_chat()
# Display chat messages from history on app rerun
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Accept user input
if prompt := st.chat_input("What's up?"):
# Add user message to chat history
st.session_state.messages.append({"role": "user", "content": prompt})
# Display user message in chat message container
with st.chat_message("user"):
st.markdown(prompt)
# Display assistant response in chat message container
with st.chat_message("assistant"):
message_placeholder = st.empty()
full_response = ""
streaming_response = st.session_state.query_engine.query(prompt)
for chunk in streaming_response:
full_response += chunk
message_placeholder.markdown(full_response + "")
time.sleep(0.01)
message_placeholder.markdown(full_response)
# Add assistant response to chat history
st.session_state.messages.append({"role": "assistant", "content": full_response})
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

+110
View File
@@ -0,0 +1,110 @@
import torch
from colivara_py import ColiVara
from Janus.janus.models import MultiModalityCausalLM, VLChatProcessor
from Janus.janus.utils.io import load_pil_images
from transformers import AutoModelForCausalLM
import base64
from io import BytesIO
from tqdm import tqdm
from PIL import Image
import io
def batch_iterate(lst, batch_size):
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), batch_size):
yield lst[i : i + batch_size]
class Retriever:
def __init__(self, rag_client, collection_name):
self.rag_client = rag_client
self.collection_name = collection_name
def search(self, query):
results = self.rag_client.search(
query=query,
collection_name=self.collection_name,
top_k=3
)
# Save the most relevant image locally
related_image = results.results[0].img_base64
image_data = base64.b64decode(related_image)
image = Image.open(io.BytesIO(image_data))
image.save("tempfile.jpeg")
return "tempfile.jpeg"
class RAG:
def __init__(self,
retriever,
llm_name = "deepseek-ai/Janus-Pro-1B"
):
self.llm_name = llm_name
self._setup_llm()
self.retriever = retriever
def _setup_llm(self):
self.vl_chat_processor = VLChatProcessor.from_pretrained(self.llm_name, cache_dir="./Janus/hf_cache")
self.tokenizer = self.vl_chat_processor.tokenizer
self.vl_gpt = AutoModelForCausalLM.from_pretrained(
self.llm_name, trust_remote_code=True, cache_dir="./Janus/hf_cache"
).to(torch.bfloat16).eval()
def generate_context(self, query):
return self.retriever.search(query)
def query(self, query):
image_context = self.generate_context(query=query)
qa_prompt_tmpl_str = f"""The user has asked the following question:
---------------------
Query: {query}
---------------------
Some images are available to you
for this question. You have
to understand these images thoroughly and
extract all relevant information that will
help you answer the query.
---------------------
"""
conversation = [
{
"role": "User",
"content": f"<image_placeholder> \n {qa_prompt_tmpl_str}",
"images": [image_context],
},
{"role": "Assistant", "content": ""},
]
pil_images = load_pil_images(conversation)
prepare_inputs = self.vl_chat_processor(
conversations=conversation, images=pil_images, force_batchify=True
).to(self.vl_gpt.device)
inputs_embeds = self.vl_gpt.prepare_inputs_embeds(**prepare_inputs)
outputs = self.vl_gpt.language_model.generate(
inputs_embeds=inputs_embeds,
attention_mask=prepare_inputs.attention_mask,
pad_token_id=self.tokenizer.eos_token_id,
bos_token_id=self.tokenizer.bos_token_id,
eos_token_id=self.tokenizer.eos_token_id,
max_new_tokens=512,
do_sample=False,
use_cache=True,
)
streaming_response = self.tokenizer.decode(outputs[0].cpu().tolist(), skip_special_tokens=True)
return streaming_response
Binary file not shown.
File diff suppressed because one or more lines are too long
+32
View File
@@ -0,0 +1,32 @@
# DeepSeek Finetuning
This project fine-tunes DeepSeek (distilled Llama variant) using Unsloth and Ollama.
---
## Setup and installations
**Setup Ollama**:
```bash
# setup ollama on linux
curl -fsSL https://ollama.com/install.sh | sh
```
**Install Dependencies**:
Ensure you have Python 3.11 or later installed.
```bash
pip install unsloth ollama
```
---
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
---
## Contribution
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 patchy631
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Binary file not shown.
+49
View File
@@ -0,0 +1,49 @@
# LaTeX-OCR
This project leverages Llama 3.2 vision and Streamlit to create a LaTeX OCR app that converts images of LaTeX equations to LaTeX code.
## Demo Video
Click below to watch the demo video of the AI Assistant in action:
[Watch the video](LaTeX-OCR.mp4)
## Installation and setup
**Setup Ollama**:
*On Linux*:
```bash
curl -fsSL https://ollama.com/install.sh | sh
# pull llama 3.2 vision model
ollama run llama3.2-vision
```
*On MacOS*:
```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # get homebrew
xcode-select --install
brew install ollama # install ollama
ollama pull llama3.2-vision # pull llama 3.2 vision model
ollama run llama3.2-vision
```
**Install Dependencies**:
Ensure you have Python 3.11 or later installed.
```bash
pip install streamlit ollama
```
---
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
---
## Contribution
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
+75
View File
@@ -0,0 +1,75 @@
import streamlit as st
import ollama
from PIL import Image
import io
# Page configuration
st.set_page_config(
page_title="LaTeX OCR with Llama 3.2 Vision",
page_icon="🦙",
layout="wide",
initial_sidebar_state="expanded"
)
# Title and description in main area
st.title("🦙 LaTeX OCR with Llama 3.2 Vision")
# Add clear button to top right
col1, col2 = st.columns([6,1])
with col2:
if st.button("Clear 🗑️"):
if 'ocr_result' in st.session_state:
del st.session_state['ocr_result']
st.rerun()
st.markdown('<p style="margin-top: -20px;">Extract LaTeX code from images using Llama 3.2 Vision!</p>', unsafe_allow_html=True)
st.markdown("---")
# Move upload controls to sidebar
with st.sidebar:
st.header("Upload Image")
uploaded_file = st.file_uploader("Choose an image...", type=['png', 'jpg', 'jpeg'])
if uploaded_file is not None:
# Display the uploaded image
image = Image.open(uploaded_file)
st.image(image, caption="Uploaded Image")
if st.button("Extract LaTeX 🔍", type="primary"):
with st.spinner("Processing image..."):
try:
response = ollama.chat(
model='llama3.2-vision',
messages=[{
'role': 'user',
'content': """Understand the mathematical equation in the provided image and output the corresponding LaTeX code.
Here are some guidelines you MUST follow or you will be penalized:
- NEVER include any additional text or explanations.
- DON'T add dollar signs ($) around the LaTeX code.
- DO NOT extract simplified versions of the equations.
- NEVER add documentclass, packages or begindocument.
- DO NOT explain the symbols used in the equation.
- Output only the LaTeX code corresponding to the mathematical equations in the image.""",
'images': [uploaded_file.getvalue()]
}]
)
st.session_state['ocr_result'] = response.message.content
except Exception as e:
st.error(f"Error processing image: {str(e)}")
# Main content area for results
if 'ocr_result' in st.session_state:
st.markdown("### LaTeX Code")
st.code(st.session_state['ocr_result'], language='latex')
st.markdown("### LaTeX Rendered")
cleaned_latex = st.session_state['ocr_result'].replace(r"\[", "").replace(r"\]", "")
st.latex(cleaned_latex)
else:
st.info("Upload an image and click 'Extract LaTeX' to see the results here.")
# Footer
st.markdown("---")
st.markdown("Made with ❤️ using Llama Vision Model2 | [Report an Issue](https://github.com/patchy631/ai-engineering-hub/issues)")
Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

@@ -0,0 +1,11 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
*.env
@@ -0,0 +1 @@
3.11
@@ -0,0 +1,59 @@
# Agentic Deep Researcher
We're building an MCP-powered multi-agent deep researcher, it can perform deep web searches using [Linkup](https://www.linkup.so/) amd the agents are orchestrated using CrewAI.
We use:
- [LinkUp](https://www.linkup.so/) (Search Tool)
- CrewAI (Agentic design)
- Deepseek R1 (LLM)
- Streamlit to wrap the logic in an interactive UI
### SetUp
Run these commands in project root
```
uv sync
```
### Run the Application
Run the application with:
```bash
streamlit run app.py
```
### Use as MCP server
```json
{
"mcpServers": {
"crew_research": {
"command": "uv",
"args": [
"--directory",
"./Multi-Agent-deep-researcher-mcp-windows-linux",
"run",
"server.py"
],
"env": {
"LINKUP_API_KEY": "your_linkup_api_key_here"
}
}
}
}
```
[Get your Linkup API keys here](https://www.linkup.so/)
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
## Contribution
Contributions are welcome! Feel free to fork this repository and submit pull requests with your improvements.
@@ -0,0 +1,136 @@
import os
from typing import Type
from dotenv import load_dotenv
from pydantic import BaseModel, Field
from linkup import LinkupClient
from crewai import Agent, Task, Crew, Process, LLM
from crewai.tools import BaseTool
# Load environment variables (for non-LinkUp settings)
load_dotenv()
def get_llm_client():
"""Initialize and return the LLM client"""
return LLM(
model="ollama/deepseek-r1:7b",
base_url="http://localhost:11434"
)
# Define LinkUp Search Tool
class LinkUpSearchInput(BaseModel):
"""Input schema for LinkUp Search Tool."""
query: str = Field(description="The search query to perform")
depth: str = Field(default="standard",
description="Depth of search: 'standard' or 'deep'")
output_type: str = Field(
default="searchResults", description="Output type: 'searchResults', 'sourcedAnswer', or 'structured'")
class LinkUpSearchTool(BaseTool):
name: str = "LinkUp Search"
description: str = "Search the web for information using LinkUp and return comprehensive results"
args_schema: Type[BaseModel] = LinkUpSearchInput
def __init__(self):
super().__init__()
def _run(self, query: str, depth: str = "standard", output_type: str = "searchResults") -> str:
"""Execute LinkUp search and return results."""
try:
# Initialize LinkUp client with API key from environment variables
linkup_client = LinkupClient(api_key=os.getenv("LINKUP_API_KEY"))
# Perform search
search_response = linkup_client.search(
query=query,
depth=depth,
output_type=output_type
)
return str(search_response)
except Exception as e:
return f"Error occurred while searching: {str(e)}"
def create_research_crew(query: str):
"""Create and configure the research crew with all agents and tasks"""
# Initialize tools
linkup_search_tool = LinkUpSearchTool()
# Get LLM client
client = get_llm_client()
web_searcher = Agent(
role="Web Searcher",
goal="Find the most relevant information on the web, along with source links (urls).",
backstory="An expert at formulating search queries and retrieving relevant information. Passes the results to the 'Research Analyst' only.",
verbose=True,
allow_delegation=True,
tools=[linkup_search_tool],
llm=client,
)
# Define the research analyst
research_analyst = Agent(
role="Research Analyst",
goal="Analyze and synthesize raw information into structured insights, along with source links (urls) as citations.",
backstory="An expert at analyzing information, identifying patterns, and extracting key insights. If required, can delagate the task of fact checking/verification to 'Web Searcher' only. Passes the final results to the 'Technical Writer' only.",
verbose=True,
allow_delegation=True,
llm=client,
)
# Define the technical writer
technical_writer = Agent(
role="Technical Writer",
goal="Create well-structured, clear, and comprehensive responses in markdown format, with citations/source links (urls).",
backstory="An expert at communicating complex information in an accessible way.",
verbose=True,
allow_delegation=False,
llm=client,
)
# Define tasks
search_task = Task(
description=f"Search for comprehensive information about: {query}.",
agent=web_searcher,
expected_output="Detailed raw search results including sources (urls).",
tools=[linkup_search_tool]
)
analysis_task = Task(
description="Analyze the raw search results, identify key information, verify facts and prepare a structured analysis.",
agent=research_analyst,
expected_output="A structured analysis of the information with verified facts and key insights, along with source links",
context=[search_task]
)
writing_task = Task(
description="Create a comprehensive, well-organized response based on the research analysis.",
agent=technical_writer,
expected_output="A clear, comprehensive response that directly answers the query with proper citations/source links (urls).",
context=[analysis_task]
)
# Create the crew
crew = Crew(
agents=[web_searcher, research_analyst, technical_writer],
tasks=[search_task, analysis_task, writing_task],
verbose=True,
process=Process.sequential
)
return crew
def run_research(query: str):
"""Run the research process and return results"""
try:
crew = create_research_crew(query)
result = crew.kickoff()
return result.raw
except Exception as e:
return f"Error: {str(e)}"
@@ -0,0 +1,83 @@
import streamlit as st
from agents import run_research
import os
# Set up page configuration
st.set_page_config(page_title="🔍 Agentic Deep Researcher", layout="wide")
# Initialize session state variables
if "linkup_api_key" not in st.session_state:
st.session_state.linkup_api_key = ""
if "messages" not in st.session_state:
st.session_state.messages = []
def reset_chat():
st.session_state.messages = []
# Sidebar: Linkup Configuration with updated logo link
with st.sidebar:
col1, col2 = st.columns([1, 3])
with col1:
st.write("")
st.image(
"https://avatars.githubusercontent.com/u/175112039?s=200&v=4", width=65)
with col2:
st.header("Linkup Configuration")
st.write("Deep Web Search")
st.markdown("[Get your API key](https://app.linkup.so/sign-up)",
unsafe_allow_html=True)
linkup_api_key = st.text_input(
"Enter your Linkup API Key", type="password")
if linkup_api_key:
st.session_state.linkup_api_key = linkup_api_key
# Update the environment variable
os.environ["LINKUP_API_KEY"] = linkup_api_key
st.success("API Key stored successfully!")
# Main Chat Interface Header with powered by logos from original code links
col1, col2 = st.columns([6, 1])
with col1:
st.markdown("<h2 style='color: #0066cc;'>🔍 Agentic Deep Researcher</h2>",
unsafe_allow_html=True)
powered_by_html = """
<div style='display: flex; align-items: center; gap: 10px; margin-top: 5px;'>
<span style='font-size: 20px; color: #666;'>Powered by</span>
<img src="https://cdn.prod.website-files.com/66cf2bfc3ed15b02da0ca770/66d07240057721394308addd_Logo%20(1).svg" width="80">
<span style='font-size: 20px; color: #666;'>and</span>
<img src="https://framerusercontent.com/images/wLLGrlJoyqYr9WvgZwzlw91A8U.png?scale-down-to=512" width="100">
</div>
"""
st.markdown(powered_by_html, unsafe_allow_html=True)
with col2:
st.button("Clear ↺", on_click=reset_chat)
# Add spacing between header and chat history
st.markdown("<div style='height: 30px;'></div>", unsafe_allow_html=True)
# Display chat history
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Accept user input and process the research query
if prompt := st.chat_input("Ask a question about your documents..."):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
if not st.session_state.linkup_api_key:
response = "Please enter your Linkup API Key in the sidebar."
else:
with st.spinner("Researching... This may take a moment..."):
try:
result = run_research(prompt)
response = result
except Exception as e:
response = f"An error occurred: {str(e)}"
with st.chat_message("assistant"):
st.markdown(response)
st.session_state.messages.append(
{"role": "assistant", "content": response})
@@ -0,0 +1,15 @@
[project]
name = "agentic_deep_researcher"
version = "0.1.0"
description = "Deep Research Agent using Crew AI and LinkUp API"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"crewai>=0.114.0",
"linkup-sdk>=0.2.4",
"mcp>=1.6.0",
"openai>=1.75.0",
"python-dotenv>=1.1.0",
"streamlit>=1.44.1",
"streamlit-crewai-process-output>=0.1.1",
]
@@ -0,0 +1,42 @@
import asyncio
from mcp.server.fastmcp import FastMCP
from agents import run_research
# Create FastMCP instance
mcp = FastMCP("crew_research")
@mcp.tool()
async def crew_research(query: str) -> str:
"""Run CrewAI-based research system for given user query. Can do both standard and deep web search.
Args:
query (str): The research query or question.
Returns:
str: The research response from the CrewAI pipeline.
"""
return run_research(query)
# Run the server
if __name__ == "__main__":
mcp.run(transport="stdio")
# add this inside ./.cursor/mcp.json
# {
# "mcpServers": {
# "crew_research": {
# "command": "uv",
# "args": [
# "--directory",
# "/Users/akshay/Eigen/ai-engineering-hub/Multi-Agent-deep-researcher-mcp-windows-linux",
# "run",
# "server.py"
# ],
# "env": {
# "LINKUP_API_KEY": "your_linkup_api_key_here"
# }
# }
# }
# }
File diff suppressed because it is too large Load Diff
+223
View File
@@ -0,0 +1,223 @@
<p align="center">
<a href="https://trendshift.io/repositories/12800">
<img src="assets/TRENDING-BADGE.png" alt="Trending Badge" style="width: 250px; height: 55px;" width="250" height="55"/>
</a>
</p>
<p align="center">
<img src="assets/ai-eng-hub.gif" alt="AI Engineering Hub Banner">
</p>
---
# AI Engineering Hub 🚀
Welcome to the **AI Engineering Hub** - your comprehensive resource for learning and building with AI!
## 🌟 Why This Repo?
AI Engineering is advancing rapidly, and staying at the forefront requires both deep understanding and hands-on experience. Here, you will find:
- **93+ Production-Ready Projects** across all skill levels
- In-depth tutorials on **LLMs, RAG, Agents, and more**
- Real-world **AI agent** applications
- Examples to implement, adapt, and scale in your projects
Whether you're a beginner, practitioner, or researcher, this repo provides resources for all skill levels to experiment and succeed in AI engineering.
---
## 📋 Table of Contents
- [Getting Started](#-getting-started)
- [Newsletter](#-stay-updated-with-our-newsletter)
- [Projects by Difficulty](#-projects-by-difficulty)
- [Beginner Projects (22)](#-beginner-projects)
- [Intermediate Projects (48)](#-intermediate-projects)
- [Advanced Projects (23)](#-advanced-projects)
- [Contributing](#-contribute-to-the-ai-engineering-hub)
- [License](#-license)
---
## 🎯 Getting Started
New to AI Engineering? Start here:
1. **Complete Beginners**: Check out the [AI Engineering Roadmap](./ai-engineering-roadmap) for a comprehensive learning path
2. **Learn the Basics**: Start with [Beginner Projects](#-beginner-projects) like OCR apps and simple RAG implementations
3. **Build Your Skills**: Move to [Intermediate Projects](#-intermediate-projects) with agents and complex workflows
4. **Master Advanced Concepts**: Tackle [Advanced Projects](#-advanced-projects) including fine-tuning and production systems
---
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
---
## 🎓 Projects by Difficulty
### 🟢 Beginner Projects
Perfect for getting started with AI engineering. These projects focus on single components and straightforward implementations.
#### OCR & Vision
- [**LaTeX OCR with Llama**](./LaTeX-OCR-with-Llama) - Convert LaTeX equation images to code using Llama 3.2 vision
- [**Llama OCR**](./llama-ocr) - 100% local OCR app with Llama 3.2 and Streamlit
- [**Gemma-3 OCR**](./gemma3-ocr) - Local OCR with structured text extraction using Gemma-3
- [**Qwen 2.5 OCR**](./qwen-2.5VL-ocr) - Text extraction using Qwen 2.5 VL model
#### Chat Interfaces & UI
- [**Local ChatGPT with DeepSeek**](./local-chatgpt%20with%20DeepSeek) - Mini-ChatGPT with DeepSeek-R1 and Chainlit
- [**Local ChatGPT with Llama**](./local-chatgpt) - ChatGPT clone using Llama 3.2 vision
- [**Local ChatGPT with Gemma 3**](./local-chatgpt%20with%20Gemma%203) - Local chat interface with Gemma 3
- [**DeepSeek Thinking UI**](./deepseek-thinking-ui) - ChatGPT with visible reasoning using DeepSeek-R1
- [**Qwen3 Thinking UI**](./qwen3-thinking-ui) - Thinking UI with Qwen3:4B and Streamlit
- [**GPT-OSS Thinking UI**](./gpt-oss-thinking-ui) - GPT-OSS with reasoning visualization
- [**Streaming AI Chatbot**](./streaming-ai-chatbot) - Real-time AI streaming with Motia framework
#### Basic RAG
- [**Simple RAG Workflow**](./simple-rag-workflow) - Basic RAG with LlamaIndex and Ollama
- [**Document Chat RAG**](./document-chat-rag) - Chat with documents using Llama 3.3
- [**Fastest RAG Stack**](./fastest-rag-stack) - Fast RAG with SambaNova, LlamaIndex, and Qdrant
- [**GitHub RAG**](./github-rag) - Chat with GitHub repos locally
- [**ModernBERT RAG**](./modernbert-rag) - RAG with ModernBert embeddings
- [**Llama 4 RAG**](./llama-4-rag) - RAG powered by Meta's Llama 4
#### Multimodal & Media
- [**Image Generation with Janus-Pro**](./imagegen-janus-pro) - Local image generation with DeepSeek Janus-pro 7B
- [**Video RAG with Gemini**](./video-rag-gemini) - Chat with videos using Gemini AI
#### Other Tools
- [**Website to API with FireCrawl**](./Website-to-API-with-FireCrawl) - Convert websites to APIs
- [**AI News Generator**](./ai_news_generator) - News generation with CrewAI and Cohere
- [**Siamese Network**](./siamese-network) - Digit similarity detection on MNIST
---
### 🟡 Intermediate Projects
Multi-component systems, agentic workflows, and advanced features for experienced practitioners.
#### AI Agents & Workflows
- [**YouTube Trend Analysis**](./Youtube-trend-analysis) - Analyze YouTube trends with CrewAI and BrightData
- [**AutoGen Stock Analyst**](./autogen-stock-analyst) - Advanced analyst with Microsoft AutoGen
- [**Agentic RAG**](./agentic_rag) - RAG with document search and web fallback
- [**Agentic RAG with DeepSeek**](./agentic_rag_deepseek) - Enterprise agentic RAG with GroundX
- [**Book Writer Flow**](./book-writer-flow) - Automated book writing with CrewAI
- [**Content Planner Flow**](./content_planner_flow) - Content workflow with CrewAI Flow
- [**Brand Monitoring**](./brand-monitoring) - Automated brand monitoring system
- [**Hotel Booking Crew**](./hotel-booking-crew) - Multi-agent hotel booking with DeepSeek-R1
- [**Deploy Agentic RAG**](./deploy-agentic-rag) - Private Agentic RAG API with LitServe
- [**Zep Memory Assistant**](./zep-memory-assistant) - AI Agent with human-like memory
- [**Agent with MCP Memory**](./agent-with-mcp-memory) - Agents with Graphiti memory and Opik
- [**ACP Code**](./acp-code) - Agent Communication Protocol demo
- [**Motia Content Creation**](./motia-content-creation) - Social media automation workflow
#### Voice & Audio
- [**Real-time Voice Bot**](./real-time-voicebot) - Conversational travel guide with AssemblyAI
- [**RAG Voice Agent**](./rag-voice-agent) - Real-time RAG Voice Agent with Cartesia
- [**Chat with Audios**](./chat-with-audios) - RAG over audio files
- [**Audio Analysis Toolkit**](./audio-analysis-toolkit) - Audio analysis with AssemblyAI
- [**Multilingual Meeting Notes**](./multilingual-meeting-notes-generator) - Auto meeting notes with language detection
#### Advanced RAG
- [**RAG with Dockling**](./rag-with-dockling) - RAG over Excel with IBM's Docling
- [**Trustworthy RAG**](./trustworthy-rag) - RAG over complex docs with TLM
- [**Fastest RAG with Milvus and Groq**](./fastest-rag-milvus-groq) - Sub-15ms retrieval latency
- [**Chat with Code**](./chat-with-code) - Chat with code using Qwen3-Coder
- [**RAG SQL Router**](./rag-sql-router) - Agent with RAG and SQL routing
#### Multimodal
- [**DeepSeek Multimodal RAG**](./deepseek-multimodal-RAG) - MultiModal RAG with DeepSeek-Janus-Pro
- [**ColiVara Website RAG**](./Colivara-deepseek-website-RAG) - MultiModal RAG for websites
- [**Multimodal RAG with AssemblyAI**](./multimodal-rag-assemblyai) - Audio + vector database + CrewAI
#### MCP (Model Context Protocol)
- [**Cursor Linkup MCP**](./cursor_linkup_mcp) - Custom MCP with deep web search
- [**EyeLevel MCP RAG**](./eyelevel-mcp-rag) - MCP for RAG over complex docs
- [**LlamaIndex MCP**](./llamaindex-mcp) - Local MCP client with LlamaIndex
- [**MCP Agentic RAG**](./mcp-agentic-rag) - MCP-powered Agentic RAG for Cursor
- [**MCP Agentic RAG Firecrawl**](./mcp-agentic-rag-firecrawl) - Agentic RAG with Firecrawl
- [**MCP Video RAG**](./mcp-video-rag) - Video RAG using Ragie via MCP
- [**MCP Voice Agent**](./mcp-voice-agent) - Voice agent with Firecrawl and Supabase
- [**SDV MCP**](./sdv-mcp) - Synthetic Data Vault orchestration
- [**KitOps MCP**](./kitops-mcp) - ML model management with KitOps
- [**Stagehand × MCP-Use**](./stagehand%20x%20mcp-use) - Web automation with Stagehand MCP
#### Model Comparison & Evaluation
- [**Evaluation and Observability**](./eval-and-observability) - E2E RAG evaluation with CometML Opik
- [**Llama 4 vs DeepSeek-R1**](./llama-4_vs_deepseek-r1) - Compare models using RAG
- [**Qwen3 vs DeepSeek-R1**](./qwen3_vs_deepseek-r1) - Model comparison with Opik
- [**O3 vs Claude Code**](./o3-vs-claude-code) - Compare Claude 3.7 and o3
- [**Sonnet4 vs O4**](./sonnet4-vs-o4) - Code generation comparison
- [**Sonnet4 vs Qwen3-Coder**](./sonnet4-vs-qwen3-coder) - Coder model comparison
- [**Code Model Comparison**](./code-model-comparison) - Frontier model code comparison
- [**GPT-OSS vs Qwen3**](./gpt-oss-vs-qwen3) - Reasoning capabilities comparison
---
### 🔴 Advanced Projects
Complex systems, fine-tuning, production deployments, and cutting-edge implementations.
#### Fine-tuning & Model Development
- [**DeepSeek Fine-tuning**](./DeepSeek-finetuning) - Fine-tune DeepSeek with Unsloth and Ollama
- [**Build Reasoning Model**](./Build-reasoning-model) - Build DeepSeek-R1-like reasoning models
- [**Attention Is All You Need Implementation**](./attention-is-all-you-need-impl) - Transformer architecture from scratch
#### Advanced Agent Systems
- [**NVIDIA Demo**](./nvidia-demo) - Documentation writer with CrewAI Flows and NVIDIA NIM
- [**Documentation Writer Flow**](./documentation-writer-flow) - Agentic documentation workflow
- [**Multi-Agent Deep Researcher**](./Multi-Agent-deep-researcher-mcp-windows-linux) - MCP-powered deep researcher
- [**Multiplatform Deep Researcher**](./multiplatform_deep_researcher) - Multi-platform research with BrightData
- [**Web Browsing Agent**](./web-browsing-agent) - Browser automation with CrewAI and Stagehand
- [**Paralegal Agent Crew**](./paralegal-agent-crew) - Intelligent paralegal with RAG
- [**FireCrawl Agent**](./firecrawl-agent) - Corrective RAG with web search fallback
- [**Context Engineering Workflow**](./context-engineering-workflow) - Research assistant with TensorLake and Zep
- [**Parlant Conversational Agent**](./parlant-conversational-agent) - Compliance-driven conversational agent
- [**Stock Portfolio Analysis Agent**](./stock-portfolio-analysis-agent) - Portfolio analysis with React frontend
- [**Guidelines vs Traditional Prompt**](./guidelines-vs-traditional-prompt) - Structured guidelines comparison
#### Advanced MCP & Infrastructure
- [**MindsDB MCP**](./mindsdb-mcp) - Unified MCP for all data sources
- [**Financial Analyst DeepSeek**](./financial-analyst-deepseek) - MCP financial analysis workflow
- [**Graphiti MCP**](./graphiti-mcp) - Persistent memory with Zep's Graphiti
- [**Pixeltable MCP**](./pixeltable-mcp) - Unified multimodal data orchestration
- [**Ultimate AI Assistant**](./ultimate-ai-assitant-using-mcp) - Multi-MCP server interface
#### Production Systems
- [**GroundX Document Pipeline**](./groundX-doc-pipeline) - World-class document processing
- [**NotebookLM Clone**](./notebook-lm-clone) - Full NotebookLM with RAG, citations, and podcasts
#### Learning Resources
- [**AI Engineering Roadmap**](./ai-engineering-roadmap) - Complete guide from Python to production AI
---
## 📢 Contribute to the AI Engineering Hub!
We welcome contributors! Whether you want to add new tutorials, improve existing code, or report issues, your contributions make this community thrive. Here's how to get involved:
1. **Fork** the repository
2. Create a new branch for your contribution
3. Submit a **Pull Request** and describe the improvements
Check out our [contributing guidelines](CONTRIBUTING.md) for more details.
---
## 📜 License
This repository is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
---
## 💬 Connect
For discussions, suggestions, and more, feel free to [create an issue](https://github.com/patchy631/ai-engineering/issues) or reach out directly!
**Happy Coding!** 🎉
+7
View File
@@ -0,0 +1,7 @@
# WeHub 来源说明
- 原始项目:`patchy631/ai-engineering-hub`
- 原始仓库:https://github.com/patchy631/ai-engineering-hub
- 导入方式:上游默认分支的最新快照
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
+49
View File
@@ -0,0 +1,49 @@
# Convert ANY website into an API using Firecrawl
This project lets you convert ANY website into an API using Firecrawl.
- [Firecrawl](https://www.firecrawl.dev/i/api) is used to scrape websites.
- Streamlit is used to create a web interface for the project.
---
## Setup and installations
**Get Firecrawl API Key**:
- Go to [Firecrawl](https://www.firecrawl.dev/i/api) and sign up for an account.
- Once you have an account, go to the API Key page and copy your API key.
- Paste your API key by creating a `.env` file as follows:
```
FIRECRAWL_API_KEY=your_api_key
```
**Install Dependencies**:
Ensure you have Python 3.11 or later installed.
```bash
pip install streamlit firecrawl
```
---
## Run the project
Finally, run the project by running the following command:
```bash
streamlit run app.py
```
---
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
---
## Contribution
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
+175
View File
@@ -0,0 +1,175 @@
import streamlit as st
import os
import gc
from firecrawl import FirecrawlApp
from dotenv import load_dotenv
import time
import pandas as pd
from typing import Dict, Any
import base64
from pydantic import BaseModel, Field
import inspect
load_dotenv()
firecrawl_api_key = os.getenv("FIRECRAWL_API_KEY")
@st.cache_resource
def load_app():
app = FirecrawlApp(api_key=firecrawl_api_key)
return app
# Initialize session state
if "messages" not in st.session_state:
st.session_state.messages = []
if "schema_fields" not in st.session_state:
st.session_state.schema_fields = [{"name": "", "type": "str"}]
def reset_chat():
st.session_state.messages = []
gc.collect()
def create_dynamic_model(fields):
"""Create a dynamic Pydantic model from schema fields."""
field_annotations = {}
for field in fields:
if field["name"]:
# Convert string type names to actual types
type_mapping = {
"str": str,
"bool": bool,
"int": int,
"float": float
}
field_annotations[field["name"]] = type_mapping[field["type"]]
# Dynamically create the model class
return type(
"ExtractSchema",
(BaseModel,),
{
"__annotations__": field_annotations
}
)
def create_schema_from_fields(fields):
"""Create schema using Pydantic model."""
if not any(field["name"] for field in fields):
return None
model_class = create_dynamic_model(fields)
return model_class.model_json_schema()
def convert_to_table(data):
"""Convert a list of dictionaries to a markdown table."""
if not data:
return ""
# Convert only the data field to a pandas DataFrame
df = pd.DataFrame(data)
# Convert DataFrame to markdown table
return df.to_markdown(index=False)
def stream_text(text: str, delay: float = 0.001) -> None:
"""Stream text with a typing effect."""
placeholder = st.empty()
displayed_text = ""
for char in text:
displayed_text += char
placeholder.markdown(displayed_text)
time.sleep(delay)
return placeholder
# Main app layout
st.markdown("""
# Convert ANY website into an API using <img src="data:image/png;base64,{}" width="250" style="vertical-align: -25px;">
""".format(base64.b64encode(open("assets/firecrawl.png", "rb").read()).decode()), unsafe_allow_html=True)
# Sidebar
with st.sidebar:
st.header("Configuration")
# Website URL input
website_url = st.text_input("Enter Website URL", placeholder="https://example.com")
st.divider()
# Schema Builder
st.subheader("Schema Builder (Optional)")
for i, field in enumerate(st.session_state.schema_fields):
col1, col2 = st.columns([2, 1])
with col1:
field["name"] = st.text_input(
"Field Name",
value=field["name"],
key=f"name_{i}",
placeholder="e.g., company_mission"
)
with col2:
field["type"] = st.selectbox(
"Type",
options=["str", "bool", "int", "float"],
key=f"type_{i}",
index=0 if field["type"] == "str" else ["str", "bool", "int", "float"].index(field["type"])
)
if len(st.session_state.schema_fields) < 5: # Limit to 5 fields
if st.button("Add Field "):
st.session_state.schema_fields.append({"name": "", "type": "str"})
# Chat interface
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
if prompt := st.chat_input("Ask about the website..."):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
if not website_url:
st.error("Please enter a website URL first!")
else:
try:
with st.spinner("Extracting data from website..."):
app = load_app()
schema = create_schema_from_fields(st.session_state.schema_fields)
print(schema)
extract_params = {
'prompt': prompt
}
if schema:
extract_params['schema'] = schema
data = app.extract(
[website_url],
extract_params
)
print(data)
# check if data['data'] is a list, if yes, pass data['data'] to convert_to_table
if isinstance(data['data'], list):
table = convert_to_table(data['data'])
else:
# find the first key in data['data']
key = list(data['data'].keys())[0]
table = convert_to_table(data['data'][key])
placeholder = stream_text(table)
st.session_state.messages.append({"role": "assistant", "content": table})
# st.markdown(table)
except Exception as e:
st.error(f"An error occurred: {str(e)}")
# Footer
st.markdown("---")
st.markdown("Built with Firecrawl and Streamlit")
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,211 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!pip install -U firecrawl"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from firecrawl import FirecrawlApp\n",
"from dotenv import load_dotenv\n",
"import pandas as pd\n",
"from typing import Dict, Any\n",
"from pydantic import BaseModel\n",
"import time\n",
"\n",
"class WebsiteScraper:\n",
" def __init__(self):\n",
" load_dotenv()\n",
" self.firecrawl_api_key = os.getenv(\"FIRECRAWL_API_KEY\")\n",
" self.app = FirecrawlApp(api_key=self.firecrawl_api_key)\n",
" self.schema_fields = [{\"name\": \"\", \"type\": \"str\"}]\n",
"\n",
" def create_dynamic_model(self, fields):\n",
" \"\"\"Create a dynamic Pydantic model from schema fields.\"\"\"\n",
" field_annotations = {}\n",
" for field in fields:\n",
" if field[\"name\"]:\n",
" type_mapping = {\n",
" \"str\": str,\n",
" \"bool\": bool,\n",
" \"int\": int,\n",
" \"float\": float\n",
" }\n",
" field_annotations[field[\"name\"]] = type_mapping[field[\"type\"]]\n",
" \n",
" return type(\n",
" \"ExtractSchema\",\n",
" (BaseModel,),\n",
" {\n",
" \"__annotations__\": field_annotations\n",
" }\n",
" )\n",
"\n",
" def create_schema_from_fields(self, fields):\n",
" \"\"\"Create schema using Pydantic model.\"\"\"\n",
" if not any(field[\"name\"] for field in fields):\n",
" return None\n",
" \n",
" model_class = self.create_dynamic_model(fields)\n",
" return model_class.model_json_schema()\n",
"\n",
" def convert_to_table(self, data: Dict[str, Any]) -> str:\n",
" \"\"\"Convert data to a pandas DataFrame and return as string.\"\"\"\n",
" if not data or 'data' not in data:\n",
" return \"\"\n",
" \n",
" df = pd.DataFrame([data['data']])\n",
" return df.to_string(index=False)\n",
"\n",
" def scrape_website(self, website_url: str, prompt: str, schema_fields=None):\n",
" \"\"\"Main function to scrape website data.\"\"\"\n",
" if not website_url:\n",
" raise ValueError(\"Please provide a website URL\")\n",
"\n",
" try:\n",
" schema = self.create_schema_from_fields(schema_fields) if schema_fields else None\n",
" \n",
" extract_params = {'prompt': prompt}\n",
" if schema:\n",
" extract_params['schema'] = schema\n",
"\n",
" data = self.app.extract([website_url,],\n",
" extract_params\n",
" )\n",
" \n",
" return data\n",
" \n",
" except Exception as e:\n",
" raise Exception(f\"An error occurred: {str(e)}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"scraper = WebsiteScraper()\n",
" \n",
"# Get user input\n",
"website_url = \"https://blog.dailydoseofds.com/*\"\n",
"prompt = \"extract publish date, title and link of all articles related to LLMs\"\n",
" \n",
"# Optional: Add schema fields\n",
"schema_fields = [\n",
" {\"name\": \"Article_title\", \"type\": \"str\"},\n",
" {\"name\": \"Publish_date\", \"type\": \"str\"},\n",
" {\"name\": \"Article_link\", \"type\": \"str\"}\n",
"]\n",
"\n",
"# Get results\n",
"result = scraper.scrape_website(website_url, prompt, [])\n",
"print(\"Results:\\n\")\n",
"print(result)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"result['data']"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [],
"source": [
"class ExtractSchema(BaseModel):\n",
" mission: str\n",
" supports_sso: bool\n",
" is_open_source: bool\n",
" is_in_yc: bool"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ExtractSchema.model_json_schema()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"scraper.create_schema_from_fields(schema_fields)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from firecrawl import FirecrawlApp\n",
"from pydantic import BaseModel, Field\n",
"\n",
"# Initialize the FirecrawlApp with your API key\n",
"app = FirecrawlApp(api_key=os.getenv(\"FIRECRAWL_API_KEY\"))\n",
"\n",
"class ExtractSchema(BaseModel):\n",
" article_title: str\n",
" publish_date: str\n",
" article_link: str\n",
"\n",
"data = app.extract([\n",
" \"https://blog.dailydoseofds.com/*\"], {\n",
" 'prompt': 'Extract the article title, publish date, and article link of all articles related to LLMs.',\n",
" 'schema': ExtractSchema.model_json_schema(),\n",
"})\n",
"print(data)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+59
View File
@@ -0,0 +1,59 @@
# YouTube Trend Analysis with CrewAI and BrightData
This project implements a YouTube Trend Analysis with CrewAI and BrightData.
- [Bright Data](https://brdta.com/dailydoseofds) is used to scrape YouTube videos.
- CrewAI is used to analyze the transcripts of the videos and generate a summary.
- Streamlit is used to create a web interface for the project.
---
## Setup and installations
**Get BrightData API Key**:
- Go to [Bright Data](https://brdta.com/dailydoseofds) and sign up for an account.
- Once you have an account, go to the API Key page and copy your API key.
- Paste your API key by creating a `.env` file as follows:
```
BRIGHT_DATA_API_KEY=your_api_key
```
**Setup Ollama**:
```bash
# setup ollama on linux
curl -fsSL https://ollama.com/install.sh | sh
# pull llama 3.2 model
ollama pull llama3.2
```
**Install Dependencies**:
Ensure you have Python 3.11 or later installed.
```bash
pip install streamlit ollama crewai crewai-tools
```
---
## Run the project
Finally, run the project by running the following command:
```bash
streamlit run app.py
```
---
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
---
## Contribution
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
+266
View File
@@ -0,0 +1,266 @@
import streamlit as st
import os
import tempfile
import gc
import base64
import time
import yaml
from tqdm import tqdm
from brightdata_scrapper import *
from dotenv import load_dotenv
load_dotenv()
from crewai import Agent, Crew, Process, Task, LLM
from crewai_tools import FileReadTool
docs_tool = FileReadTool()
bright_data_api_key = os.getenv("BRIGHT_DATA_API_KEY")
@st.cache_resource
def load_llm():
llm = LLM(model="gpt-4o", api_key=os.getenv("OPENAI_API_KEY"))
# llm = LLM(
# model="ollama/llama3.2",
# base_url="http://localhost:11434"
# )
return llm
# ===========================
# Define Agents & Tasks
# ===========================
def create_agents_and_tasks():
"""Creates a Crew for analysis of the channel scrapped output"""
with open("config.yaml", 'r') as file:
config = yaml.safe_load(file)
analysis_agent = Agent(
role=config["agents"][0]["role"],
goal=config["agents"][0]["goal"],
backstory=config["agents"][0]["backstory"],
verbose=True,
tools=[docs_tool],
llm=load_llm()
)
response_synthesizer_agent = Agent(
role=config["agents"][1]["role"],
goal=config["agents"][1]["goal"],
backstory=config["agents"][1]["backstory"],
verbose=True,
llm=load_llm()
)
analysis_task = Task(
description=config["tasks"][0]["description"],
expected_output=config["tasks"][0]["expected_output"],
agent=analysis_agent
)
response_task = Task(
description=config["tasks"][1]["description"],
expected_output=config["tasks"][1]["expected_output"],
agent=response_synthesizer_agent
)
crew = Crew(
agents=[analysis_agent, response_synthesizer_agent],
tasks=[analysis_task, response_task],
process=Process.sequential,
verbose=True
)
return crew
# ===========================
# Streamlit Setup
# ===========================
st.markdown("""
# YouTube Trend Analysis powered by <img src="data:image/png;base64,{}" width="120" style="vertical-align: -3px;"> & <img src="data:image/png;base64,{}" width="120" style="vertical-align: -3px;">
""".format(base64.b64encode(open("assets/crewai.png", "rb").read()).decode(), base64.b64encode(open("assets/brightdata.png", "rb").read()).decode()), unsafe_allow_html=True)
if "messages" not in st.session_state:
st.session_state.messages = [] # Chat history
if "response" not in st.session_state:
st.session_state.response = None
if "crew" not in st.session_state:
st.session_state.crew = None # Store the Crew object
def reset_chat():
st.session_state.messages = []
gc.collect()
def start_analysis():
# Create a status container
with st.spinner('Scraping videos... This may take a moment.'):
status_container = st.empty()
status_container.info("Extracting videos from the channels...")
channel_snapshot_id = trigger_scraping_channels(bright_data_api_key, st.session_state.youtube_channels, 10, st.session_state.start_date, st.session_state.end_date, "Latest", "")
status = get_progress(bright_data_api_key, channel_snapshot_id['snapshot_id'])
while status['status'] != "ready":
status_container.info(f"Current status: {status['status']}")
time.sleep(10)
status = get_progress(bright_data_api_key, channel_snapshot_id['snapshot_id'])
if status['status'] == "failed":
status_container.error(f"Scraping failed: {status}")
return
if status['status'] == "ready":
status_container.success("Scraping completed successfully!")
# Show a list of YouTube vidoes here in a scrollable container
channel_scrapped_output = get_output(bright_data_api_key, status['snapshot_id'], format="json")
st.markdown("## YouTube Videos Extracted")
# Create a container for the carousel
carousel_container = st.container()
# Calculate number of videos per row (adjust as needed)
videos_per_row = 3
with carousel_container:
# Calculate number of rows needed
num_videos = len(channel_scrapped_output[0])
num_rows = (num_videos + videos_per_row - 1) // videos_per_row
for row in range(num_rows):
# Create columns for each row
cols = st.columns(videos_per_row)
# Fill each column with a video
for col_idx in range(videos_per_row):
video_idx = row * videos_per_row + col_idx
# Check if we still have videos to display
if video_idx < num_videos:
with cols[col_idx]:
st.video(channel_scrapped_output[0][video_idx]['url'])
status_container.info("Processing transcripts...")
st.session_state.all_files = []
# Calculate transcripts
for i in tqdm(range(len(channel_scrapped_output[0]))):
# save transcript to file
youtube_video_id = channel_scrapped_output[0][i]['shortcode']
file = "transcripts/" + youtube_video_id + ".txt"
st.session_state.all_files.append(file)
with open(file, "w") as f:
for j in range(len(channel_scrapped_output[0][i]['formatted_transcript'])):
text = channel_scrapped_output[0][i]['formatted_transcript'][j]['text']
start_time = channel_scrapped_output[0][i]['formatted_transcript'][j]['start_time']
end_time = channel_scrapped_output[0][i]['formatted_transcript'][j]['end_time']
f.write(f"({start_time:.2f}-{end_time:.2f}): {text}\n")
f.close()
st.session_state.channel_scrapped_output = channel_scrapped_output
status_container.success("Scraping complete! We shall now analyze the videos and report trends...")
else:
status_container.error(f"Scraping failed with status: {status}")
if status['status'] == "ready":
status_container = st.empty()
with st.spinner('The agent is analyzing the videos... This may take a moment.'):
# create crew
st.session_state.crew = create_agents_and_tasks()
st.session_state.response = st.session_state.crew.kickoff(inputs={"file_paths": ", ".join(st.session_state.all_files)})
# ===========================
# Sidebar
# ===========================
with st.sidebar:
st.header("YouTube Channels")
# Initialize the channels list in session state if it doesn't exist
if "youtube_channels" not in st.session_state:
st.session_state.youtube_channels = [""] # Start with one empty field
# Function to add new channel field
def add_channel_field():
st.session_state.youtube_channels.append("")
# Create input fields for each channel
for i, channel in enumerate(st.session_state.youtube_channels):
col1, col2 = st.columns([6, 1])
with col1:
st.session_state.youtube_channels[i] = st.text_input(
"Channel URL",
value=channel,
key=f"channel_{i}",
label_visibility="collapsed"
)
# Show remove button for all except the first field
with col2:
if i > 0:
if st.button("", key=f"remove_{i}"):
st.session_state.youtube_channels.pop(i)
st.rerun()
# Add channel button
st.button("Add Channel ", on_click=add_channel_field)
st.divider()
st.subheader("Date Range")
col1, col2 = st.columns(2)
with col1:
start_date = st.date_input("Start Date")
st.session_state.start_date = start_date
# store date as string
st.session_state.start_date = start_date.strftime("%Y-%m-%d")
with col2:
end_date = st.date_input("End Date")
st.session_state.end_date = end_date
st.session_state.end_date = end_date.strftime("%Y-%m-%d")
st.divider()
st.button("Start Analysis 🚀", type="primary", on_click=start_analysis)
# st.button("Clear Chat", on_click=reset_chat)
# ===========================
# Main Chat Interface
# ===========================
# Main content area
if st.session_state.response:
with st.spinner('Generating content... This may take a moment.'):
try:
result = st.session_state.response
st.markdown("### Generated Analysis")
st.markdown(result)
# Add download button
st.download_button(
label="Download Content",
data=result.raw,
file_name=f"youtube_trend_analysis.md",
mime="text/markdown"
)
except Exception as e:
st.error(f"An error occurred: {str(e)}")
# Footer
st.markdown("---")
st.markdown("Built with CrewAI, Bright Data and Streamlit")
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

@@ -0,0 +1,119 @@
import subprocess
import json
import time
import os
from dotenv import load_dotenv
load_dotenv()
api_key = os.getenv("BRIGHT_DATA_API_KEY")
def trigger_scraping_niche(api_key, keyword, num_of_posts, start_date, end_date, country, endpoint):
payload = [{"keyword": keyword,
"num_of_posts": num_of_posts,
"start_date": start_date,
"end_date": end_date,
"country": country}]
# Define the curl command
command = [
"curl",
"-H", f"Authorization: Bearer {api_key}",
"-H", "Content-Type: application/json",
"-d", json.dumps(payload), # Convert payload to a JSON string
endpoint
]
# Execute the command and capture the output
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# Check if the command was successful
if result.returncode == 0:
try:
# Parse the JSON response
return json.loads(result.stdout.strip())
except json.JSONDecodeError:
print("Failed to parse JSON response.")
return None
else:
# Print the error if the command fails
print(f"Error: {result.stderr}")
return None
def trigger_scraping_channels(api_key, channel_urls, num_of_posts, start_date, end_date, order_by, country):
dataset_id = "gd_lk56epmy2i5g7lzu0k"
endpoint = f"https://api.brightdata.com/datasets/v3/trigger?dataset_id={dataset_id}&include_errors=true&type=discover_new&discover_by=url"
payload = [
{
"url": url,
"num_of_posts": num_of_posts,
"start_date": start_date,
"end_date": end_date,
"order_by": order_by,
"country": country
}
for url in channel_urls
]
# Define the curl command
command = [
"curl",
"-H", f"Authorization: Bearer {api_key}",
"-H", "Content-Type: application/json",
"-d", json.dumps(payload), # Convert payload to JSON string
endpoint
]
# Execute the command and capture the output
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# Check if the command was successful
if result.returncode == 0:
try:
# Parse and return the JSON response
return json.loads(result.stdout.strip())
except json.JSONDecodeError:
print("Failed to parse JSON response.")
return None
else:
# Print and return the error if the command fails
print(f"Error: {result.stderr}")
return None
def get_progress(api_key, snapshot_id):
command = [
"curl",
"-H", f"Authorization: Bearer {api_key}",
f"https://api.brightdata.com/datasets/v3/progress/{snapshot_id}"
]
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode == 0:
return json.loads(result.stdout.strip())
else:
print(f"Error: {result.stderr}")
return None
def get_output(api_key, snapshot_id, format="json"):
# Define the curl command as a list
command = [
"curl",
"-H", f"Authorization: Bearer {api_key}",
f"https://api.brightdata.com/datasets/v3/snapshot/{snapshot_id}?format={format}"
]
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode == 0:
json_lines = result.stdout.strip().split("\n")
print(json_lines)
json_objects = [json.loads(line) for line in json_lines]
return json_objects
else:
print(f"Error: {result.stderr}")
+65
View File
@@ -0,0 +1,65 @@
agents:
- name: analysis_agent
role: "YouTube Transcript Analyzer"
goal: >
Analyze the transcripts of several videos located in {file_paths}.
Break down the analysis into structured sections, including:
1. Key topics discussed.
2. Emerging trends or patterns across multiple transcripts.
3. Speaker sentiment and tone analysis.
4. Any recurring keywords or phrases.
Provide a comprehensive, sectioned report with granular insights.
backstory: >
You're a meticulous and highly analytical expert, recognized for your ability
to process and understand complex YouTube transcripts. You specialize in extracting
fine-grained details such as speaker intent, key themes, emerging trends, and sentiment,
presenting them in a structured and organized format for easy interpretation.
verbose: true
- name: response_synthesizer_agent
role: "Response Synthesizer Agent"
goal: >
Synthesize the detailed, sectioned analysis from the transcripts into a coherent and concise response.
The response should:
1. Summarize the key findings from each section.
2. Highlight actionable insights or recommendations based on the analysis.
3. Ensure clarity and readability while retaining important nuances.
backstory: >
You are a skilled communicator and synthesis expert. Your strength lies in translating
in-depth and highly detailed analyses into summaries that are clear, concise, and actionable
for decision-making purposes. You excel at preserving the essence of complex data in simple language.
verbose: true
tasks:
- name: analysis_task
description: >
Conduct a fine-grained analysis of the transcripts of several videos located in {file_paths}.
Break the analysis into the following sections:
1. Key topics and themes discussed in the videos.
2. Emerging trends or patterns across multiple videos.
3. Speaker sentiment and tone, noting any shifts.
4. Recurring keywords or phrases and their contexts.
Provide a detailed, structured report with insights for each section.
expected_output: >
A multi-section report containing:
1. Key topics and themes.
2. Emerging trends or patterns.
3. Speaker sentiment analysis.
4. Recurring keywords or phrases.
Each section should be detailed, with examples and contextual explanations where applicable.
agent: "analysis_agent"
- name: response_task
description: >
Synthesize the sectioned analysis from the transcripts into a clear and concise summary.
Ensure that the summary:
1. Provides a high-level overview of key findings in each section.
2. Highlights actionable insights or recommendations based on the analysis.
3. Maintains clarity, precision, and coherence in the language used.
expected_output: >
A concise summary including:
1. High-level findings from each section of the analysis.
2. Actionable insights or recommendations.
3. Clear and easy-to-understand language suitable for decision-making.
agent: "response_synthesizer_agent"
+85
View File
@@ -0,0 +1,85 @@
# Summary Generator multi-agent workflow with ACP
A simple demonstration of the Agent Communication Protocol (ACP), showcasing how two agents built using different frameworks (CrewAI and Smolagents) can collaborate seamlessly to generate and verify a research summary.
---
## Setup and Installation
1. **Install Ollama:**
```bash
# Setting up Ollama on linux
curl -fsSL https://ollama.com/install.sh | sh
# Pull the Qwen2.5 model
ollama pull qwen2.5:14b
```
2. **Install project dependencies:**
Ensure you have Python 3.10 or later installed on your system.
First, install `uv` and set up the environment:
```bash
# MacOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
Install dependencies:
```bash
# Create a new directory for our project
uv init acp-project
cd acp-project
# Create virtual environment and activate it
uv venv
source .venv/bin/activate # MacOS/Linux
.venv\Scripts\activate # Windows
# Install dependencies
uv add acp-sdk crewai smolagents duckduckgo-search ollama
```
You can also use any other LLM providers such as OpenAI or Anthropic. Create a `.env` file and add your API keys
```
OPENAI_API_KEY=your_openai_key
ANTHROPIC_API_KEY=your_anthropic_key
```
## Usage
Start the two ACP servers in separate terminals:
```bash
# Terminal 1
uv run crew_acp_server.py
# Terminal 2
uv run smolagents_acp_server.py
```
Run the ACP client to trigger the agent workflow:
```bash
uv run acp_client.py
```
Output:
A general summary from the first agent
A fact-checked and updated version from the second agent
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
---
## Contribution
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
+24
View File
@@ -0,0 +1,24 @@
import asyncio
from acp_sdk.client import Client
async def run_workflow() -> None:
async with Client(base_url="http://localhost:8000") as drafter, \
Client(base_url="http://localhost:8001") as verifier:
topic = "Impact of climate change on agriculture in 2025."
response1 = await drafter.run_sync(
agent="research_drafter",
input=topic
)
draft = response1.output[0].parts[0].content
print(f"\nDraft Summary:\n{draft}")
response2 = await verifier.run_sync(
agent="research_verifier",
input=f"Enhance the following research summary using the latest information available online providing a more accurate and updated version:\n{draft}"
)
final_summary = response2.output[0].parts[0].content
print(f"\nVerified & Enriched Summary:\n{final_summary}")
if __name__ == "__main__":
asyncio.run(run_workflow())
+36
View File
@@ -0,0 +1,36 @@
from collections.abc import AsyncGenerator
from acp_sdk.models import Message, MessagePart
from acp_sdk.server import RunYield, RunYieldResume, Server
from crewai import Crew, Task, Agent, LLM
server = Server()
llm = LLM(
model="ollama_chat/qwen2.5:14b",
base_url="http://localhost:11434",
max_tokens=8192
)
@server.agent()
async def research_drafter(input: list[Message]) -> AsyncGenerator[RunYield, RunYieldResume]:
"""Agent that creates a general research summary on a given topic."""
agent = Agent(
role="Research summarizer",
goal="Draft an informative and structured research summary based on the topic",
backstory="You are a researcher who summarizes complex topics for general readers.",
llm=llm
)
task = Task(
description=f"Write a brief, clear summary on: {input[0].parts[0].content}",
expected_output="A concise paragraph summarizing the topic",
agent=agent
)
crew = Crew(agents=[agent], tasks=[task])
task_output = await crew.kickoff_async()
yield Message(parts=[MessagePart(content=str(task_output))])
if __name__ == "__main__":
server.run(port=8000)
+30
View File
@@ -0,0 +1,30 @@
from collections.abc import AsyncGenerator
from acp_sdk.models import Message, MessagePart
from acp_sdk.server import RunYield, RunYieldResume, Server
from smolagents import CodeAgent, LiteLLMModel, DuckDuckGoSearchTool
import warnings
warnings.filterwarnings("ignore")
server = Server()
model = LiteLLMModel(
model_id="ollama_chat/qwen2.5:14b",
api_base="http://localhost:11434",
# api_key="your-api-key",
num_ctx=8192
)
@server.agent()
async def research_verifier(input: list[Message]) -> AsyncGenerator[RunYield, RunYieldResume]:
"""Agent that fact-checks and enhances a research summary using web search."""
agent = CodeAgent(tools=[DuckDuckGoSearchTool()], model=model)
prompt = input[0].parts[0].content
response = agent.run(prompt)
yield Message(parts=[MessagePart(content=str(response))])
if __name__ == "__main__":
server.run(port=8001)
+10
View File
@@ -0,0 +1,10 @@
OPENAI_API_KEY=your_api_key
LINKUP_API_KEY=you_api_key
# Neo4j Database Configuration
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=demodemo
# OpenAI API Configuration
MODEL_NAME=gpt-4.1-mini
+114
View File
@@ -0,0 +1,114 @@
# Crash Course: Building AI Agents with Open-Source Tools
This project is a hands-on crash course on building AI agents using a 100% open-source tech stack! You'll learn:
- What is an AI agent
- Connecting agents to tools
- Overview of MCP (Multi-Component Protocol)
- Replacing tools with MCP servers
- Setting up observability and tracing
All concepts are demonstrated with real, runnable code.
### Watch this tutorial on YouTube
<a href="https://youtu.be/R6sMAZaTCR4">
<img src="assets/thumbnail.jpeg" alt="Watch this tutorial on YouTube" width="550"/>
</a>
## What is an AI Agent?
An AI agent uses an LLM as its brain, has memory to retain context, and can take real-world actions through tools (like browsing the web, running code, etc.).
In short: it thinks, remembers, and acts.
## Tech Stack
- [CrewAI](https://github.com/crewAIInc) — Build MCP-ready agents
- [Zep Graphiti](https://github.com/getzep/graphiti) — Add human-like memory
- [CometML Opik](https://github.com/comet-ml/opik) — Observability and tracing
- 100% open-source!
## System Overview
Here's how the system works:
1. User sends a query
2. Assistant runs a web search via MCP
3. Query + results go to the Memory Manager
4. Memory Manager stores context in Graphiti
5. Response agent crafts the final answer
---
### SetUp
- **Setup ollama:**
1. Install Ollama by following the official instructions for your OS:
**For macOS:**
```bash
curl -fsSL https://ollama.com/install.sh | sh
```
**For Linux:**
```bash
curl -fsSL https://ollama.com/install.sh | sh
```
**For Windows:**
Download and install from [Ollama's official website](https://ollama.com/download)
2. Pull the required model:
```bash
ollama pull llama3.2
```
You should see a response from the model. If you get any errors, check that Ollama is running with:
- **Add all necessary keys:**
Create a new `.env` file in the project root, using `.env.example` as a template. Copy the example file and fill in your own API keys and secrets as needed.
```bash
cp .env.example .env
# Then edit .env to add your keys
```
- **Install dependencies:**
Run the following command in the project root to install all required dependencies:
```bash
uv sync
```
#### Start MCP servers:
- **Start Linkup server:**
[Get your Linkup API keys here](https://www.linkup.so/)
Run the following command in the project root:
```bash
python server.py
```
- **Start the Graphiti MCP server:**
This is only for advanced usage, you cna still learn all the fundamentals with just Linkup MCP server also.
Follow the instructions in the [Graphiti MCP README](https://github.com/patchy631/ai-engineering-hub/blob/main/graphiti-mcp/README.md)
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
## Contribution
Contributions are welcome! Feel free to fork this repository and submit pull requests with your improvements.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
[project]
name = "agent-with-mcp-memory"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"crewai>=0.134.0",
"ipykernel>=6.29.5",
"linkup-sdk>=0.2.7",
"mcp[cli]>=1.10.1",
"opik>=1.7.42",
]
+22
View File
@@ -0,0 +1,22 @@
from dotenv import load_dotenv
from linkup import LinkupClient
from mcp.server.fastmcp import FastMCP
load_dotenv()
mcp = FastMCP('linkup-server', port=8080)
client = LinkupClient()
@mcp.tool()
def web_search(query: str = "") -> str:
"""Search the web for the given query."""
search_response = client.search(
query=query,
depth="standard", # "standard" or "deep"
output_type="sourcedAnswer", # "searchResults" or "sourcedAnswer" or "structured"
structured_output_schema=None, # must be filled if output_type is "structured"
)
return str(search_response)
if __name__ == "__main__":
mcp.run(transport="sse")
+3321
View File
File diff suppressed because it is too large Load Diff
+44
View File
@@ -0,0 +1,44 @@
from python_a2a import A2AServer, skill, agent, run_server, TaskStatus, TaskState
import math
import re
@agent(
name="Sine Agent",
description="Provides the sine of a number",
version="1.0.0"
)
class SineAgent(A2AServer):
@skill(
name="Get Sine",
description="Get the sine of a number",
tags=["sine", "sin"]
)
def get_sine(self, number):
"""Get the sine of a number."""
# Mock implementation
return f"The sine of {number} is {math.sin(number)}"
def handle_task(self, task):
# Extract location from message
input_message = task.message["content"]["text"]
# regex to extract the number from the text
match = re.search(r"([-+]?[0-9]*\.?[0-9]+)", input_message)
number = float(match.group(1))
print("number", number)
# Get weather and create response
sine_output = self.get_sine(number)
task.artifacts = [{
"parts": [{"type": "text", "text": sine_output}]
}]
task.status = TaskStatus(state=TaskState.COMPLETED)
return task
# Run the server
if __name__ == "__main__":
agent = SineAgent()
run_server(agent, port=4737)
+44
View File
@@ -0,0 +1,44 @@
from python_a2a import A2AServer, skill, agent, run_server, TaskStatus, TaskState
import math
import re
@agent(
name="Cosine Agent",
description="Provides the cosine of a number",
version="1.0.0"
)
class CosineAgent(A2AServer):
@skill(
name="Get Cos",
description="Get the cosine of a number",
tags=["cos", "cosine"]
)
def get_cosine(self, number):
"""Get the cosine of a number."""
# Mock implementation
return f"The cosine of {number} is {math.cos(number)}"
def handle_task(self, task):
# Extract location from message
input_message = task.message["content"]["text"]
# regex to extract the number from the text
match = re.search(r"([-+]?[0-9]*\.?[0-9]+)", input_message)
number = float(match.group(1))
print("number", number)
# Get weather and create response
cosine_output = self.get_cosine(number)
task.artifacts = [{
"parts": [{"type": "text", "text": cosine_output}]
}]
task.status = TaskStatus(state=TaskState.COMPLETED)
return task
# Run the server
if __name__ == "__main__":
agent = CosineAgent()
run_server(agent, port=4738)
+44
View File
@@ -0,0 +1,44 @@
from python_a2a import A2AServer, skill, agent, run_server, TaskStatus, TaskState
import math
import re
@agent(
name="Tangent Agent",
description="Provides the tangent of a number",
version="1.0.0"
)
class TangentAgent(A2AServer):
@skill(
name="Get Tangent",
description="Get the tangent of a number",
tags=["tangent", "tan"]
)
def get_tangent(self, number):
"""Get the tangent of a number."""
# Mock implementation
return f"The tangent of {number} is {math.tan(number)}"
def handle_task(self, task):
# Extract location from message
input_message = task.message["content"]["text"]
# regex to extract the number from the text
match = re.search(r"([-+]?[0-9]*\.?[0-9]+)", input_message)
number = float(match.group(1))
print("number", number)
# Get weather and create response
tangent_output = self.get_tangent(number)
task.artifacts = [{
"parts": [{"type": "text", "text": tangent_output}]
}]
task.status = TaskStatus(state=TaskState.COMPLETED)
return task
# Run the server
if __name__ == "__main__":
agent = TangentAgent()
run_server(agent, port=4739)
+112
View File
@@ -0,0 +1,112 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!curl -LsSf https://astral.sh/uv/install.sh | sh\n",
"!uv pip install python-a2a"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"# open a terminal and run: python agent1.py\n",
"# open another terminal and run: python agent2.py\n",
"# open another terminal and run: python agent3.py\n",
"\n",
"# after that, run the following cells"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from python_a2a import AgentNetwork, A2AClient, AIAgentRouter\n",
"\n",
"# Create an agent network\n",
"network = AgentNetwork(name=\"Math Assistant Network\")\n",
"\n",
"# Add agents to the network\n",
"network.add(\"Sine\", \"http://localhost:4737\")\n",
"network.add(\"Cosine\", \"http://localhost:4738\")\n",
"network.add(\"Tangent\", \"http://localhost:4739\")\n",
"\n",
"# Create a router to intelligently direct queries to the best agent\n",
"router = AIAgentRouter(\n",
" llm_client=A2AClient(\"http://localhost:5000/openai\"), # LLM for making routing decisions\n",
" agent_network=network\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Route a query to the appropriate agent\n",
"query = \"Tan of 0.78545\"\n",
"agent_name, confidence = router.route_query(query)\n",
"print(f\"Routing to {agent_name} with {confidence:.2f} confidence\")\n",
"\n",
"# Get the selected agent and ask the question\n",
"agent = network.get_agent(agent_name)\n",
"response = agent.ask(query)\n",
"print(f\"Response: {response}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Route a query to the appropriate agent\n",
"query = \"Sine of 3.14159\"\n",
"agent_name, confidence = router.route_query(query)\n",
"print(f\"Routing to {agent_name} with {confidence:.2f} confidence\")\n",
"\n",
"# Get the selected agent and ask the question\n",
"agent = network.get_agent(agent_name)\n",
"response = agent.ask(query)\n",
"print(f\"Response: {response}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+6
View File
@@ -0,0 +1,6 @@
# you only need this if you're working with OpenAI
MODEL=your_model_name
OPENAI_API_KEY=your_openai_api_key
SERPER_API_KEY=your_serper_api_key
# Just set this if you're working with Local LLama 3.2 variant
FIRECRAWL_API_KEY=your_firecrawl_api_key
+2
View File
@@ -0,0 +1,2 @@
.env
__pycache__/
+42
View File
@@ -0,0 +1,42 @@
# Agentic RAG using CrewAI
This project leverages CrewAI to build an Agentic RAG that can search through your docs and fallbacks to web search in case it doesn't find the answer in the docs, have option to use either of deep-seek-r1 or llama 3.2 that runs locally. More details un Running the app section below!
Before that, make sure you grab your FireCrawl API keys to search the web.
**Get API Keys**:
- [FireCrawl](https://www.firecrawl.dev/i/api)
### Watch Demo on YouTube
[![Watch Demo on YouTube](https://github.com/patchy631/ai-engineering-hub/blob/main/agentic_rag/thumbnail/thumbnail.png)](https://youtu.be/O4yBW_GTRk0)
## Installation and setup
**Get API Keys**:
- [FireCrawl](https://www.firecrawl.dev/i/api)
**Install Dependencies**:
Ensure you have Python 3.11 or later installed.
```bash
pip install crewai crewai-tools chonkie[semantic] markitdown qdrant-client fastembed
```
**Running the app**:
To use deep-seek-rq use command ``` streamlit run app_deep_seek.py ```, for llama 3.2 use command ``` streamlit run app_llama3.2.py ```
---
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
---
## Contribution
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
+432
View File
@@ -0,0 +1,432 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from crewai import Agent, Task, Crew\n",
"\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"llm = ChatOpenAI(\n",
" openai_api_base=\"https://api.groq.com/openai/v1\",\n",
" openai_api_key=os.environ['GROQ_API_KEY'],\n",
" model_name=\"llama3-8b-8192\",\n",
" temperature=0,\n",
" max_tokens=1000,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from crewai_tools import PDFSearchTool\n",
"\n",
"rag_tool = PDFSearchTool(pdf='/content/17.pdf',\n",
" config=dict(\n",
" llm=dict(\n",
" provider=\"groq\", # or google, openai, anthropic, llama2, ...\n",
" config=dict(\n",
" model=\"llama3-8b-8192\",\n",
" # temperature=0.5,\n",
" # top_p=1,\n",
" # stream=true,\n",
" ),\n",
" ),\n",
" embedder=dict(\n",
" provider=\"huggingface\", # or openai, ollama, ...\n",
" config=dict(\n",
" model=\"BAAI/bge-small-en-v1.5\",\n",
" #task_type=\"retrieval_document\",\n",
" # title=\"Embeddings\",\n",
" ),\n",
" ),\n",
" )\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"rag_tool.run(\"How does exercise price determine for ESOP?\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"os.environ['TAVILY_API_KEY'] = userdata.get('TAVILY_API_KEY')\n",
"web_search_tool = TavilySearchResults(k=3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"web_search_tool.run(\"How does exercise price determine for ESOP?\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from crewai_tools import tool\n",
"@tool\n",
"def router_tool(question):\n",
" \"\"\"Router Function\"\"\"\n",
" if 'ESOP' in question:\n",
" return 'vectorstore'\n",
" else:\n",
" return 'web_search'"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Router Agent"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Router_Agent = Agent(\n",
" role='Router',\n",
" goal='Route user question to a vectorstore or web search',\n",
" backstory=(\n",
" \"You are an expert at routing a user question to a vectorstore or web search.\"\n",
" \"Use the vectorstore for questions on concepta related to Retrieval-Augmented Generation.\"\n",
" \"You do not need to be stringent with the keywords in the question related to these topics. Otherwise, use web-search.\"\n",
" ),\n",
" verbose=True,\n",
" allow_delegation=False,\n",
" llm=llm,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Retriever_Agent"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Retriever_Agent = Agent(\n",
"role=\"Retriever\",\n",
"goal=\"Use the information retrieved from the vectorstore to answer the question\",\n",
"backstory=(\n",
" \"You are an assistant for question-answering tasks.\"\n",
" \"Use the information present in the retrieved context to answer the question.\"\n",
" \"You have to provide a clear concise answer.\"\n",
"),\n",
"verbose=True,\n",
"allow_delegation=False,\n",
"llm=llm,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Grader Agent"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Grader_agent = Agent(\n",
" role='Answer Grader',\n",
" goal='Filter out erroneous retrievals',\n",
" backstory=(\n",
" \"You are a grader assessing relevance of a retrieved document to a user question.\"\n",
" \"If the document contains keywords related to the user question, grade it as relevant.\"\n",
" \"It does not need to be a stringent test.You have to make sure that the answer is relevant to the question.\"\n",
" ),\n",
" verbose=True,\n",
" allow_delegation=False,\n",
" llm=llm,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Hallucination Grader Agent"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hallucination_grader = Agent(\n",
" role=\"Hallucination Grader\",\n",
" goal=\"Filter out hallucination\",\n",
" backstory=(\n",
" \"You are a hallucination grader assessing whether an answer is grounded in / supported by a set of facts.\"\n",
" \"Make sure you meticulously review the answer and check if the response provided is in alignmnet with the question asked\"\n",
" ),\n",
" verbose=True,\n",
" allow_delegation=False,\n",
" llm=llm,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Answer Grader Agent"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"answer_grader = Agent(\n",
" role=\"Answer Grader\",\n",
" goal=\"Filter out hallucination from the answer.\",\n",
" backstory=(\n",
" \"You are a grader assessing whether an answer is useful to resolve a question.\"\n",
" \"Make sure you meticulously review the answer and check if it makes sense for the question asked\"\n",
" \"If the answer is relevant generate a clear and concise response.\"\n",
" \"If the answer gnerated is not relevant then perform a websearch using 'web_search_tool'\"\n",
" ),\n",
" verbose=True,\n",
" allow_delegation=False,\n",
" llm=llm,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Router Task"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"router_task = Task(\n",
" description=(\"Analyse the keywords in the question {question}\"\n",
" \"Based on the keywords decide whether it is eligible for a vectorstore search or a web search.\"\n",
" \"Return a single word 'vectorstore' if it is eligible for vectorstore search.\"\n",
" \"Return a single word 'websearch' if it is eligible for web search.\" \n",
" \"Do not provide any other premable or explaination.\"\n",
" ),\n",
" expected_output=(\"Give a binary choice 'websearch' or 'vectorstore' based on the question\"\n",
" \"Do not provide any other premable or explaination.\"),\n",
" agent=Router_Agent,\n",
" tools=[router_tool],\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Retriever Task"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"retriever_task = Task(\n",
" description=(\"Based on the response from the router task extract information for the question {question} with the help of the respective tool.\"\n",
" \"Use the web_serach_tool to retrieve information from the web in case the router task output is 'websearch'.\"\n",
" \"Use the rag_tool to retrieve information from the vectorstore in case the router task output is 'vectorstore'.\"\n",
" ),\n",
" expected_output=(\"You should analyse the output of the 'router_task'\"\n",
" \"If the response is 'websearch' then use the web_search_tool to retrieve information from the web.\"\n",
" \"If the response is 'vectorstore' then use the rag_tool to retrieve information from the vectorstore.\"\n",
" \"Return a claer and consise text as response.\"),\n",
" agent=Retriever_Agent,\n",
" context=[router_task],\n",
" #tools=[retriever_tool],\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Grader Task"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"grader_task = Task(\n",
" description=(\"Based on the response from the retriever task for the quetion {question} evaluate whether the retrieved content is relevant to the question.\"\n",
" ),\n",
" expected_output=(\"Binary score 'yes' or 'no' score to indicate whether the document is relevant to the question\"\n",
" \"You must answer 'yes' if the response from the 'retriever_task' is in alignment with the question asked.\"\n",
" \"You must answer 'no' if the response from the 'retriever_task' is not in alignment with the question asked.\"\n",
" \"Do not provide any preamble or explanations except for 'yes' or 'no'.\"),\n",
" agent=Grader_agent,\n",
" context=[retriever_task],\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Hallucination Grader Task"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hallucination_task = Task(\n",
" description=(\"Based on the response from the grader task for the quetion {question} evaluate whether the answer is grounded in / supported by a set of facts.\"),\n",
" expected_output=(\"Binary score 'yes' or 'no' score to indicate whether the answer is sync with the question asked\"\n",
" \"Respond 'yes' if the answer is in useful and contains fact about the question asked.\"\n",
" \"Respond 'no' if the answer is not useful and does not contains fact about the question asked.\"\n",
" \"Do not provide any preamble or explanations except for 'yes' or 'no'.\"),\n",
" agent=hallucination_grader,\n",
" context=[grader_task],\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Answer grader Task"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"answer_task = Task( \n",
" description=(\"Based on the response from the hallucination task for the quetion {question} evaluate whether the answer is useful to resolve the question.\"\n",
" \"If the answer is 'yes' return a clear and concise answer.\"\n",
" \"If the answer is 'no' then perform a 'websearch' and return the response\"),\n",
" expected_output=(\"Return a clear and concise response if the response from 'hallucination_task' is 'yes'.\"\n",
" \"Perform a web search using 'web_search_tool' and return ta clear and concise response only if the response from 'hallucination_task' is 'no'.\"\n",
" \"Otherwise respond as 'Sorry! unable to find a valid response'.\"), \n",
" context=[hallucination_task],\n",
" agent=answer_grader,\n",
" #tools=[answer_grader_tool],\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Setup the Crew"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"rag_crew = Crew(\n",
" agents=[Router_Agent, Retriever_Agent, Grader_agent, hallucination_grader, answer_grader],\n",
" tasks=[router_task, retriever_task, grader_task, hallucination_task, answer_task],\n",
" verbose=True,\n",
" \n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"inputs ={\"question\":\"Does the ESOP supplement the salary of an employee?\"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"result = rag_crew.kickoff(inputs=inputs)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"result"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "env_crewai",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.10.15"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+186
View File
@@ -0,0 +1,186 @@
import streamlit as st
import os
import tempfile
import gc
import base64
import time
from crewai import Agent, Crew, Process, Task
from crewai_tools import SerperDevTool
from src.agentic_rag.tools.custom_tool import DocumentSearchTool
# ===========================
# Define Agents & Tasks
# ===========================
def create_agents_and_tasks(pdf_tool):
"""Creates a Crew with the given PDF tool (if any) and a web search tool."""
web_search_tool = SerperDevTool()
retriever_agent = Agent(
role="Retrieve relevant information to answer the user query: {query}",
goal=(
"Retrieve the most relevant information from the available sources "
"for the user query: {query}. Always try to use the PDF search tool first. "
"If you are not able to retrieve the information from the PDF search tool, "
"then try to use the web search tool."
),
backstory=(
"You're a meticulous analyst with a keen eye for detail. "
"You're known for your ability to understand user queries: {query} "
"and retrieve knowledge from the most suitable knowledge base."
),
verbose=True,
tools=[t for t in [pdf_tool, web_search_tool] if t],
)
response_synthesizer_agent = Agent(
role="Response synthesizer agent for the user query: {query}",
goal=(
"Synthesize the retrieved information into a concise and coherent response "
"based on the user query: {query}. If you are not able to retrieve the "
'information then respond with "I\'m sorry, I couldn\'t find the information '
'you\'re looking for."'
),
backstory=(
"You're a skilled communicator with a knack for turning "
"complex information into clear and concise responses."
),
verbose=True
)
retrieval_task = Task(
description=(
"Retrieve the most relevant information from the available "
"sources for the user query: {query}"
),
expected_output=(
"The most relevant information in the form of text as retrieved "
"from the sources."
),
agent=retriever_agent
)
response_task = Task(
description="Synthesize the final response for the user query: {query}",
expected_output=(
"A concise and coherent response based on the retrieved information "
"from the right source for the user query: {query}. If you are not "
"able to retrieve the information, then respond with: "
'"I\'m sorry, I couldn\'t find the information you\'re looking for."'
),
agent=response_synthesizer_agent
)
crew = Crew(
agents=[retriever_agent, response_synthesizer_agent],
tasks=[retrieval_task, response_task],
process=Process.sequential, # or Process.hierarchical
verbose=True
)
return crew
# ===========================
# Streamlit Setup
# ===========================
if "messages" not in st.session_state:
st.session_state.messages = [] # Chat history
if "pdf_tool" not in st.session_state:
st.session_state.pdf_tool = None # Store the DocumentSearchTool
if "crew" not in st.session_state:
st.session_state.crew = None # Store the Crew object
def reset_chat():
st.session_state.messages = []
gc.collect()
def display_pdf(file_bytes: bytes, file_name: str):
"""Displays the uploaded PDF in an iframe."""
base64_pdf = base64.b64encode(file_bytes).decode("utf-8")
pdf_display = f"""
<iframe
src="data:application/pdf;base64,{base64_pdf}"
width="100%"
height="600px"
type="application/pdf"
>
</iframe>
"""
st.markdown(f"### Preview of {file_name}")
st.markdown(pdf_display, unsafe_allow_html=True)
# ===========================
# Sidebar
# ===========================
with st.sidebar:
st.header("Add Your PDF Document")
uploaded_file = st.file_uploader("Choose a PDF file", type=["pdf"])
if uploaded_file is not None:
# If there's a new file and we haven't set pdf_tool yet...
if st.session_state.pdf_tool is None:
with tempfile.TemporaryDirectory() as temp_dir:
temp_file_path = os.path.join(temp_dir, uploaded_file.name)
with open(temp_file_path, "wb") as f:
f.write(uploaded_file.getvalue())
with st.spinner("Indexing PDF... Please wait..."):
st.session_state.pdf_tool = DocumentSearchTool(file_path=temp_file_path)
st.success("PDF indexed! Ready to chat.")
# Optionally display the PDF in the sidebar
display_pdf(uploaded_file.getvalue(), uploaded_file.name)
st.button("Clear Chat", on_click=reset_chat)
# ===========================
# Main Chat Interface
# ===========================
st.markdown("""
# Agentic RAG powered by <img src="data:image/png;base64,{}" width="120" style="vertical-align: -3px;">
""".format(base64.b64encode(open("assets/crewai.png", "rb").read()).decode()), unsafe_allow_html=True)
# Render existing conversation
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Chat input
prompt = st.chat_input("Ask a question about your PDF...")
if prompt:
# 1. Show user message immediately
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
# 2. Build or reuse the Crew (only once after PDF is loaded)
if st.session_state.crew is None:
st.session_state.crew = create_agents_and_tasks(st.session_state.pdf_tool)
# 3. Get the response
with st.chat_message("assistant"):
message_placeholder = st.empty()
full_response = ""
# Get the complete response first
with st.spinner("Thinking..."):
inputs = {"query": prompt}
result = st.session_state.crew.kickoff(inputs=inputs).raw
# Split by lines first to preserve code blocks and other markdown
lines = result.split('\n')
for i, line in enumerate(lines):
full_response += line
if i < len(lines) - 1: # Don't add newline to the last line
full_response += '\n'
message_placeholder.markdown(full_response + "")
time.sleep(0.15) # Adjust the speed as needed
# Show the final response without the cursor
message_placeholder.markdown(full_response)
# 4. Save assistant's message to session
st.session_state.messages.append({"role": "assistant", "content": result})
+196
View File
@@ -0,0 +1,196 @@
import streamlit as st
import os
import tempfile
import gc
import base64
import time
from crewai import Agent, Crew, Process, Task, LLM
from src.agentic_rag.tools.custom_tool import FireCrawlWebSearchTool
from src.agentic_rag.tools.custom_tool import DocumentSearchTool
@st.cache_resource
def load_llm():
llm = LLM(
model="ollama/deepseek-r1:7b",
base_url="http://localhost:11434"
)
return llm
# ===========================
# Define Agents & Tasks
# ===========================
def create_agents_and_tasks(pdf_tool):
"""Creates a Crew with the given PDF tool (if any) and a web search tool."""
web_search_tool = FireCrawlWebSearchTool()
retriever_agent = Agent(
role="Retrieve relevant information to answer the user query: {query}",
goal=(
"Retrieve the most relevant information from the available sources "
"for the user query: {query}. Always try to use the PDF search tool first. "
"If you are not able to retrieve the information from the PDF search tool, "
"then try to use the web search tool."
),
backstory=(
"You're a meticulous analyst with a keen eye for detail. "
"You're known for your ability to understand user queries: {query} "
"and retrieve knowledge from the most suitable knowledge base."
),
verbose=True,
tools=[t for t in [pdf_tool, web_search_tool] if t],
llm=load_llm()
)
response_synthesizer_agent = Agent(
role="Response synthesizer agent for the user query: {query}",
goal=(
"Synthesize the retrieved information into a concise and coherent response "
"based on the user query: {query}. If you are not able to retrieve the "
'information then respond with "I\'m sorry, I couldn\'t find the information '
'you\'re looking for."'
),
backstory=(
"You're a skilled communicator with a knack for turning "
"complex information into clear and concise responses."
),
verbose=True,
llm=load_llm()
)
retrieval_task = Task(
description=(
"Retrieve the most relevant information from the available "
"sources for the user query: {query}"
),
expected_output=(
"The most relevant information in the form of text as retrieved "
"from the sources."
),
agent=retriever_agent
)
response_task = Task(
description="Synthesize the final response for the user query: {query}",
expected_output=(
"A concise and coherent response based on the retrieved information "
"from the right source for the user query: {query}. If you are not "
"able to retrieve the information, then respond with: "
'"I\'m sorry, I couldn\'t find the information you\'re looking for."'
),
agent=response_synthesizer_agent
)
crew = Crew(
agents=[retriever_agent, response_synthesizer_agent],
tasks=[retrieval_task, response_task],
process=Process.sequential, # or Process.hierarchical
verbose=True
)
return crew
# ===========================
# Streamlit Setup
# ===========================
if "messages" not in st.session_state:
st.session_state.messages = [] # Chat history
if "pdf_tool" not in st.session_state:
st.session_state.pdf_tool = None # Store the DocumentSearchTool
if "crew" not in st.session_state:
st.session_state.crew = None # Store the Crew object
def reset_chat():
st.session_state.messages = []
gc.collect()
def display_pdf(file_bytes: bytes, file_name: str):
"""Displays the uploaded PDF in an iframe."""
base64_pdf = base64.b64encode(file_bytes).decode("utf-8")
pdf_display = f"""
<iframe
src="data:application/pdf;base64,{base64_pdf}"
width="100%"
height="600px"
type="application/pdf"
>
</iframe>
"""
st.markdown(f"### Preview of {file_name}")
st.markdown(pdf_display, unsafe_allow_html=True)
# ===========================
# Sidebar
# ===========================
with st.sidebar:
st.header("Add Your PDF Document")
uploaded_file = st.file_uploader("Choose a PDF file", type=["pdf"])
if uploaded_file is not None:
# If there's a new file and we haven't set pdf_tool yet...
if st.session_state.pdf_tool is None:
with tempfile.TemporaryDirectory() as temp_dir:
temp_file_path = os.path.join(temp_dir, uploaded_file.name)
with open(temp_file_path, "wb") as f:
f.write(uploaded_file.getvalue())
with st.spinner("Indexing PDF... Please wait..."):
st.session_state.pdf_tool = DocumentSearchTool(file_path=temp_file_path)
st.success("PDF indexed! Ready to chat.")
# Optionally display the PDF in the sidebar
display_pdf(uploaded_file.getvalue(), uploaded_file.name)
st.button("Clear Chat", on_click=reset_chat)
# ===========================
# Main Chat Interface
# ===========================
st.markdown("""
# Agentic RAG powered by <img src="data:image/png;base64,{}" width="170" style="vertical-align: -3px;">
""".format(base64.b64encode(open("assets/deep-seek.png", "rb").read()).decode()), unsafe_allow_html=True)
# Render existing conversation
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Chat input
prompt = st.chat_input("Ask a question about your PDF...")
if prompt:
# 1. Show user message immediately
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
# 2. Build or reuse the Crew (only once after PDF is loaded)
if st.session_state.crew is None:
st.session_state.crew = create_agents_and_tasks(st.session_state.pdf_tool)
# 3. Get the response
with st.chat_message("assistant"):
message_placeholder = st.empty()
full_response = ""
# Get the complete response first
with st.spinner("Thinking..."):
inputs = {"query": prompt}
result = st.session_state.crew.kickoff(inputs=inputs).raw
# Split by lines first to preserve code blocks and other markdown
lines = result.split('\n')
for i, line in enumerate(lines):
full_response += line
if i < len(lines) - 1: # Don't add newline to the last line
full_response += '\n'
message_placeholder.markdown(full_response + "")
time.sleep(0.15) # Adjust the speed as needed
# Show the final response without the cursor
message_placeholder.markdown(full_response)
# 4. Save assistant's message to session
st.session_state.messages.append({"role": "assistant", "content": result})
+196
View File
@@ -0,0 +1,196 @@
import streamlit as st
import os
import tempfile
import gc
import base64
import time
from crewai import Agent, Crew, Process, Task, LLM
from src.agentic_rag.tools.custom_tool import FireCrawlWebSearchTool
from src.agentic_rag.tools.custom_tool import DocumentSearchTool
@st.cache_resource
def load_llm():
llm = LLM(
model="ollama/llama3.2",
base_url="http://localhost:11434"
)
return llm
# ===========================
# Define Agents & Tasks
# ===========================
def create_agents_and_tasks(pdf_tool):
"""Creates a Crew with the given PDF tool (if any) and a web search tool."""
web_search_tool = FireCrawlWebSearchTool()
retriever_agent = Agent(
role="Retrieve relevant information to answer the user query: {query}",
goal=(
"Retrieve the most relevant information from the available sources "
"for the user query: {query}. Always try to use the PDF search tool first. "
"If you are not able to retrieve the information from the PDF search tool, "
"then try to use the web search tool."
),
backstory=(
"You're a meticulous analyst with a keen eye for detail. "
"You're known for your ability to understand user queries: {query} "
"and retrieve knowledge from the most suitable knowledge base."
),
verbose=True,
tools=[t for t in [pdf_tool, web_search_tool] if t],
llm=load_llm()
)
response_synthesizer_agent = Agent(
role="Response synthesizer agent for the user query: {query}",
goal=(
"Synthesize the retrieved information into a concise and coherent response "
"based on the user query: {query}. If you are not able to retrieve the "
'information then respond with "I\'m sorry, I couldn\'t find the information '
'you\'re looking for."'
),
backstory=(
"You're a skilled communicator with a knack for turning "
"complex information into clear and concise responses."
),
verbose=True,
llm=load_llm()
)
retrieval_task = Task(
description=(
"Retrieve the most relevant information from the available "
"sources for the user query: {query}"
),
expected_output=(
"The most relevant information in the form of text as retrieved "
"from the sources."
),
agent=retriever_agent
)
response_task = Task(
description="Synthesize the final response for the user query: {query}",
expected_output=(
"A concise and coherent response based on the retrieved information "
"from the right source for the user query: {query}. If you are not "
"able to retrieve the information, then respond with: "
'"I\'m sorry, I couldn\'t find the information you\'re looking for."'
),
agent=response_synthesizer_agent
)
crew = Crew(
agents=[retriever_agent, response_synthesizer_agent],
tasks=[retrieval_task, response_task],
process=Process.sequential, # or Process.hierarchical
verbose=True
)
return crew
# ===========================
# Streamlit Setup
# ===========================
if "messages" not in st.session_state:
st.session_state.messages = [] # Chat history
if "pdf_tool" not in st.session_state:
st.session_state.pdf_tool = None # Store the DocumentSearchTool
if "crew" not in st.session_state:
st.session_state.crew = None # Store the Crew object
def reset_chat():
st.session_state.messages = []
gc.collect()
def display_pdf(file_bytes: bytes, file_name: str):
"""Displays the uploaded PDF in an iframe."""
base64_pdf = base64.b64encode(file_bytes).decode("utf-8")
pdf_display = f"""
<iframe
src="data:application/pdf;base64,{base64_pdf}"
width="100%"
height="600px"
type="application/pdf"
>
</iframe>
"""
st.markdown(f"### Preview of {file_name}")
st.markdown(pdf_display, unsafe_allow_html=True)
# ===========================
# Sidebar
# ===========================
with st.sidebar:
st.header("Add Your PDF Document")
uploaded_file = st.file_uploader("Choose a PDF file", type=["pdf"])
if uploaded_file is not None:
# If there's a new file and we haven't set pdf_tool yet...
if st.session_state.pdf_tool is None:
with tempfile.TemporaryDirectory() as temp_dir:
temp_file_path = os.path.join(temp_dir, uploaded_file.name)
with open(temp_file_path, "wb") as f:
f.write(uploaded_file.getvalue())
with st.spinner("Indexing PDF... Please wait..."):
st.session_state.pdf_tool = DocumentSearchTool(file_path=temp_file_path)
st.success("PDF indexed! Ready to chat.")
# Optionally display the PDF in the sidebar
display_pdf(uploaded_file.getvalue(), uploaded_file.name)
st.button("Clear Chat", on_click=reset_chat)
# ===========================
# Main Chat Interface
# ===========================
st.markdown("""
# Agentic RAG powered by <img src="data:image/png;base64,{}" width="120" style="vertical-align: -3px;">
""".format(base64.b64encode(open("assets/crewai.png", "rb").read()).decode()), unsafe_allow_html=True)
# Render existing conversation
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Chat input
prompt = st.chat_input("Ask a question about your PDF...")
if prompt:
# 1. Show user message immediately
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
# 2. Build or reuse the Crew (only once after PDF is loaded)
if st.session_state.crew is None:
st.session_state.crew = create_agents_and_tasks(st.session_state.pdf_tool)
# 3. Get the response
with st.chat_message("assistant"):
message_placeholder = st.empty()
full_response = ""
# Get the complete response first
with st.spinner("Thinking..."):
inputs = {"query": prompt}
result = st.session_state.crew.kickoff(inputs=inputs).raw
# Split by lines first to preserve code blocks and other markdown
lines = result.split('\n')
for i, line in enumerate(lines):
full_response += line
if i < len(lines) - 1: # Don't add newline to the last line
full_response += '\n'
message_placeholder.markdown(full_response + "")
time.sleep(0.15) # Adjust the speed as needed
# Show the final response without the cursor
message_placeholder.markdown(full_response)
# 4. Save assistant's message to session
st.session_state.messages.append({"role": "assistant", "content": result})
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

+516
View File
@@ -0,0 +1,516 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 100% local Agentic RAG"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/akshaypachaar/miniconda3/envs/env_crewai/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
" from .autonotebook import tqdm as notebook_tqdm\n"
]
}
],
"source": [
"import warnings\n",
"from crewai import Agent, Crew, Task, LLM, Process\n",
"from src.agentic_rag.tools.custom_tool import DocumentSearchTool\n",
"from src.agentic_rag.tools.custom_tool import FireCrawlWebSearchTool\n",
"\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Setup LLM\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"llm = LLM(\n",
" model=\"ollama/llama3.2\",\n",
" base_url=\"http://localhost:11434\"\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Setup Tools"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"pdf_tool = DocumentSearchTool(file_path='/Users/akshaypachaar/Eigen/ai-engineering/agentic_rag/knowledge/dspy.pdf')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Make sure you have the API key for FireCrawl in your environment variables."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"web_search_tool = FireCrawlWebSearchTool()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Agents"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"retriever_agent = Agent(\n",
" role=\"\"\"Retrieve relevant information to answer the user query: {query}\"\"\",\n",
" goal=\"\"\"Retrieve the most relevant information from the available sources \n",
" for the user query: {query}, always try to use the pdf search tool first. \n",
" If you are not able to retrieve the information from the pdf search tool \n",
" then try to use the web search tool.\"\"\",\n",
" backstory=\"\"\"You're a meticulous analyst with a keen eye for detail. \n",
" You're known for your ability understand the user query: {query} \n",
" and retrieve knowlege from the most suitable knowledge base.\"\"\",\n",
" verbose=True,\n",
" tools=[\n",
" pdf_tool,\n",
" web_search_tool\n",
" ],\n",
" # llm=llm\n",
")\n",
"\n",
"response_synthesizer_agent = Agent(\n",
" role=\"\"\"Response synthesizer agent for the user query: {query}\"\"\",\n",
" goal=\"\"\"Synthesize the retrieved information into a concise and coherent response \n",
" based on the user query: {query}. If you are not able to retrieve the \n",
" information then respond with \"I'm sorry, I couldn't find the information \n",
" you're looking for.\"\"\",\n",
" backstory=\"\"\"You're a skilled communicator with a knack for turning complex \n",
" information into clear and concise responses.\"\"\",\n",
" verbose=True,\n",
" # llm=llm\n",
")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Tasks"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"retrieval_task = Task(\n",
" description=\"\"\"Retrieve the most relevant information from the available \n",
" sources for the user query: {query}\"\"\",\n",
" expected_output=\"\"\"The most relevant information in form of text as retrieved\n",
" from the sources.\"\"\",\n",
" agent=retriever_agent\n",
")\n",
"\n",
"response_task = Task(\n",
" description=\"\"\"Synthesize the final response for the user query: {query}\"\"\",\n",
" expected_output=\"\"\"A concise and coherent response based on the retrieved infromation\n",
" from the right source for the user query: {query}. If you are not \n",
" able to retrieve the information then respond with \n",
" I'm sorry, I couldn't find the information you're looking for.\"\"\",\n",
" agent=response_synthesizer_agent\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Initialize Crew"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"crew = Crew(\n",
"\t\t\tagents=[retriever_agent, response_synthesizer_agent], \n",
"\t\t\ttasks=[retrieval_task, response_task],\n",
"\t\t\tprocess=Process.sequential,\n",
"\t\t\tverbose=True,\n",
"\t\t\t# process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/\n",
"\t\t)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Kickoff Crew"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: When is Australian open 2025 happening?\u001b[00m\n",
"\u001b[95m## Task:\u001b[00m \u001b[92mRetrieve the most relevant information from the available \n",
" sources for the user query: When is Australian open 2025 happening?\u001b[00m\n",
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: When is Australian open 2025 happening?\u001b[00m\n",
"\u001b[95m## Thought:\u001b[00m \u001b[92mI will start by searching the document for information regarding the dates of the Australian Open in 2025.\u001b[00m\n",
"\u001b[95m## Using tool:\u001b[00m \u001b[92mDocumentSearchTool\u001b[00m\n",
"\u001b[95m## Tool Input:\u001b[00m \u001b[92m\n",
"\"{\\\"query\\\": \\\"Australian Open 2025 dates\\\"}\"\u001b[00m\n",
"\u001b[95m## Tool Output:\u001b[00m \u001b[92m\n",
"02406, 2022.\n",
"\n",
"\n",
"___\n",
" In International Conference on Machine\n",
"Learning, pp.\n",
"___\n",
" arXiv preprint arXiv:2305.03495, 2023.\n",
"\n",
"Peng Qi, Xiaowen Lin, Leo Mehr, Zijian Wang, and Christopher D. Manning. Answering complex\n",
"In Proceedings of the 2019 Con-\n",
"\n",
"___\n",
"Takuya Akiba, Shotaro Sano, Toshihiko Yanase, Takeru Ohta, and Masanori Koyama. Optuna:\n",
"In Proceedings of the 25th ACM\n",
"A next-generation hyperparameter optimization framework.\n",
"\n",
"___\n",
" arXiv preprint arXiv:1809.09600, 2018.\n",
"\n",
"\n",
"___\n",
"Q: Olivia has $23. She bought five bagels for $3 each. How much money does she have left?\n",
"\n",
"# solution in Python:\n",
"\n",
"def solution():\n",
"\n",
"\"\"\"Olivia has $23. She bought five bagels for $3 each. How much money does she have left?\"\"\"\n",
"money initial = 23\n",
"bagels = 5\n",
"bagel cost = 3\n",
"money spent = bagels * bagel cost\n",
"money left = money initial - money spent\n",
"result = money left\n",
"return result\n",
"\n",
"Q: Michael had 58 golf balls. On tuesday, he lost 23 golf balls. On wednesday, he lost 2 more. How many golf balls did he\n",
"have at the end of wednesday?\n",
"\n",
"# solution in Python:\n",
"\n",
"def solution():\n",
"\n",
"\"\"\"Michael had 58 golf balls. On tuesday, he lost 23 golf balls. On wednesday, he lost 2 more. How many golf balls\n",
"\n",
"did he have at the end of wednesday?\"\"\"\n",
"\n",
"golf balls initial = 58\n",
"golf balls lost tuesday = 23\n",
"golf balls lost wednesday = 2\n",
"golf balls left = golf balls initial - golf balls lost tuesday - golf balls lost wednesday\n",
"result = golf balls left\n",
"return result\n",
"\n",
"Q: There were nine computers in the server room. Five more computers were installed each day, from monday to thursday.\n",
"How many computers are now in the server room?\n",
"\n",
"# solution in Python:\n",
"\n",
"def solution():\n",
"\n",
"\"\"\"There were nine computers in the server room. Five more computers were installed each day, from monday to thursday.\n",
"\n",
"How many computers are now in the server room?\"\"\"\n",
"\n",
"computers initial = 9\n",
"computers per day = 5\n",
"num days = 4\n",
"computers added = computers per day * num days\n",
"computers total = computers initial + computers added\n",
"result = computers total\n",
"return result\n",
"\n",
"Q: Shawn has five toys. For Christmas, he got two toys each from his mom and dad. How many toys does he have now?\n",
"\n",
"# solution in Python:\n",
"\n",
"def solution():\n",
"\n",
"\"\"\"Shawn has five toys. For Christmas, he got two toys each from his mom and dad. How many toys does he have now?\"\"\"\n",
"toys initial = 5\n",
"mom toys = 2\n",
"dad toys = 2\n",
"total received = mom toys + dad toys\n",
"total toys = toys initial + total received\n",
"\n",
"___\n",
"I checked: {query}\n",
"\n",
"___\n",
"======== table info ========\n",
"{table info}\n",
"Question: {input}\n",
"SQLQuery:\n",
"\n",
"\n",
"___\n",
"# Step 2: Evaluate the generated candidate program .\n",
"score = evaluate_program ( candidate_program , self . metric , valset )\n",
"candidates . append (( score , candidate_program ) )\n",
"\n",
"# Create a new basic bootstrap few - shot program .\n",
"shuffled_trainset = shuffle ( trainset , seed = seed )\n",
"tp = BootstrapFewShot ( metric = metric , max_bootstrap_demos = random_size () )\n",
"candidate_program = tp . compile ( student , shuffled_trainset , teacher )\n",
"\n",
"28\n",
"\n",
"\fPreprint\n",
"\n",
"E.3 BOOTSTRAPFEWSHOTWITHOPTUNA\n",
"\n",
"pool = self . pool\n",
"\n",
"def objective ( self , trial ):\n",
"\n",
"def __init__ ( self , metric , trials =16) :\n",
"\n",
"self . metric = metric\n",
"self . trials = trials\n",
"\n",
"# Step 1: Create copy of student program .\n",
"candidate_program = self . student . reset_copy ()\n",
"\n",
"\n",
"___\n",
"1 class SimplifiedBootstrapFewShotWithOptuna ( Teleprompter ) :\n",
"2\n",
"3\n",
"4\n",
"5\n",
"6\n",
"7\n",
"8\n",
"9\n",
"10\n",
"11\n",
"12\n",
"13\n",
"14\n",
"15\n",
"16\n",
"17\n",
"18\n",
"19\n",
"20\n",
"21\n",
"22\n",
"23\n",
"24\n",
"25\n",
"26\n",
"27\n",
"28\n",
"29\n",
"30\n",
"31\n",
"32\n",
"33\n",
"34\n",
"35\n",
"36\n",
"37\n",
"38\n",
"39\n",
"40\n",
"41\n",
"42\n",
"43\n",
"44\n",
"\n",
"print ( Best score : , best_program . score )\n",
"print ( Best program : , best_program )\n",
"\u001b[00m\n",
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: When is Australian open 2025 happening?\u001b[00m\n",
"\u001b[95m## Thought:\u001b[00m \u001b[92mThought: It seems that the document search did not yield any relevant information regarding the dates for the Australian Open 2025. I will now search the internet to find the information.\u001b[00m\n",
"\u001b[95m## Using tool:\u001b[00m \u001b[92mSearch the internet\u001b[00m\n",
"\u001b[95m## Tool Input:\u001b[00m \u001b[92m\n",
"\"{\\\"search_query\\\": \\\"Australian Open 2025 schedule dates\\\"}\"\u001b[00m\n",
"\u001b[95m## Tool Output:\u001b[00m \u001b[92m\n",
"\n",
"Search results: Title: 2025 Australian Open Tennis Schedule - Roadtrips\n",
"Link: https://www.roadtrips.com/tennis-packages/australian-open/schedule/\n",
"Snippet: 2025 Australian Open Schedule ; 9, Day, Mon, January 20 ; 9, Night, Mon, January 20 ; 10, Day, Tues, January 21 ; 10, Night, Tues, January 21 ...\n",
"---\n",
"Title: 2025 Australian Open Schedule of Play - Grand Slam Tennis Tours\n",
"Link: https://www.grandslamtennistours.com/australian-open/schedule-of-play\n",
"Snippet: 2025 Australian Open Schedule of Play ; 22 · 23 ; 5:00 PM · 10:00 AM.\n",
"---\n",
"Title: Australian Open 2025 dates announced | AO\n",
"Link: https://ausopen.com/articles/news/australian-open-2025-dates-announced\n",
"Snippet: Australian Open 2025 dates are set for 6-26 January at Melbourne Park, guaranteeing fans three weeks of thrilling Grand Slam tennis.\n",
"---\n",
"Title: Australian Open 2025 draw: How to watch and follow along | AO\n",
"Link: https://ausopen.com/articles/news/australian-open-2025-draw-how-watch-and-follow-along\n",
"Snippet: Thursday 9 January marks the date the Australian Open men's and women's singles draws will be revealed. From 2.30pm AEDT, the draw will be ...\n",
"---\n",
"Title: Australian Open 2025: Schedule, format and how to watch\n",
"Link: https://www.usatoday.com/story/sports/tennis/aus/2025/01/02/australian-open-schedule-format-how-to-watch/77360328007/\n",
"Snippet: Date: Sunday, Jan. 5 to Saturday, Jan. 25 · TV: ESPN family of networks, Tennis Channel · Streaming: ESPN+, Fubo · Location: Multiple venues ( ...\n",
"---\n",
"Title: Official Australian Open 2025 Packages & Tickets | Book Now\n",
"Link: https://events.com.au/tennis/australian-open-2025/\n",
"Snippet: Australian Open 2025 will start on Sunday, 12 January 2025 and finish Sunday, 26 January 2025. It's a two week long tournament. Where is Australian ...\n",
"---\n",
"Title: 2025 Australian Open: Dates, schedule, draw, and odds\n",
"Link: https://tenngrand.com/2025-australian-open-dates-schedule-draw-and-odds/\n",
"Snippet: The Australian Open will take place January 13-26 at Melbourne Park in Melbourne, Australia. Qualifying begins on Monday, January 6. Schedule.\n",
"---\n",
"Title: Australian Open 2025 provisional schedule: Plan your three weeks ...\n",
"Link: https://ausopen.com/articles/news/australian-open-2025-provisional-schedule-plan-your-three-weeks-melbourne-park\n",
"Snippet: The men's singles semifinals one beginning in the afternoon, one that same evening are scheduled at Rod Laver Arena on Friday 24 January, ...\n",
"---\n",
"\n",
"\n",
"\n",
"You ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n",
"\n",
"Tool Name: DocumentSearchTool\n",
"Tool Arguments: {'query': {'description': 'Query to search the document.', 'type': 'str'}}\n",
"Tool Description: Search the document for the given query.\n",
"Tool Name: Search the internet\n",
"Tool Arguments: {'search_query': {'description': 'Mandatory search query you want to use to search the internet', 'type': 'str'}}\n",
"Tool Description: A tool that can be used to search the internet with a search_query.\n",
"\n",
"Use the following format:\n",
"\n",
"Thought: you should always think about what to do\n",
"Action: the action to take, only one name of [DocumentSearchTool, Search the internet], just the name, exactly as it's written.\n",
"Action Input: the input to the action, just a simple python dictionary, enclosed in curly braces, using \" to wrap keys and values.\n",
"Observation: the result of the action\n",
"\n",
"Once all necessary information is gathered:\n",
"\n",
"Thought: I now know the final answer\n",
"Final Answer: the final answer to the original input question\n",
"\u001b[00m\n",
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: When is Australian open 2025 happening?\u001b[00m\n",
"\u001b[95m## Final Answer:\u001b[00m \u001b[92m\n",
"The Australian Open 2025 will take place from January 6 to January 26, 2025, at Melbourne Park in Melbourne, Australia.\u001b[00m\n",
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mResponse synthesizer agent for the user query: When is Australian open 2025 happening?\u001b[00m\n",
"\u001b[95m## Task:\u001b[00m \u001b[92mSynthesize the final response for the user query: When is Australian open 2025 happening?\u001b[00m\n",
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mResponse synthesizer agent for the user query: When is Australian open 2025 happening?\u001b[00m\n",
"\u001b[95m## Final Answer:\u001b[00m \u001b[92m\n",
"The Australian Open 2025 will take place from January 6 to January 26, 2025, at Melbourne Park in Melbourne, Australia.\u001b[00m\n",
"\n",
"\n"
]
}
],
"source": [
"result = crew.kickoff(inputs={\"query\": \"When is Australian open 2025 happening?\"})"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The Australian Open 2025 will take place from January 6 to January 26, 2025, at Melbourne Park in Melbourne, Australia.\n"
]
}
],
"source": [
"print(result)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "env_crewai",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.15"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Binary file not shown.
+20
View File
@@ -0,0 +1,20 @@
[project]
name = "agentic_rag"
version = "0.1.0"
description = "agentic-rag using crewAI"
authors = [{ name = "Your Name", email = "you@example.com" }]
requires-python = ">=3.10,<=3.13"
dependencies = [
"crewai[tools]>=0.86.0,<1.0.0"
]
[project.scripts]
agentic_rag = "agentic_rag.main:run"
run_crew = "agentic_rag.main:run"
train = "agentic_rag.main:train"
replay = "agentic_rag.main:replay"
test = "agentic_rag.main:test"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
@@ -0,0 +1,498 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"# !pip install \"chonkie[semantic]\"\n",
"# !pip install markitdown\n",
"# !pip install qdrant-client\n",
"# !pip install fastembed"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"from crewai import Agent, Crew, Process, Task\n",
"from crewai.project import CrewBase, agent, crew, task\n",
"from crewai_tools import SerperDevTool\n",
"from crewai_tools import PDFSearchTool\n",
"# from tools.custom_tool import DocumentSearchTool\n",
"from tools.custom_tool import DocumentSearchTool\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/akshaypachaar/miniconda3/envs/env_crewai/lib/python3.10/site-packages/model2vec/hf_utils.py:152: ResourceWarning: unclosed file <_io.TextIOWrapper name='/Users/akshaypachaar/.cache/huggingface/hub/models--minishlab--potion-base-8M/snapshots/dcbec7aa2d52fc76754ac6291803feedd8c619ce/config.json' mode='r' encoding='UTF-8'>\n",
" config = json.load(open(config_path))\n",
"ResourceWarning: Enable tracemalloc to get the object allocation traceback\n"
]
}
],
"source": [
"pdf_tool = DocumentSearchTool(file_path='/Users/akshaypachaar/Eigen/ai-engineering/agentic_rag/knowledge/dspy.pdf')"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"web_search_tool = SerperDevTool()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"import yaml\n",
"with open('config/agents.yaml', 'r') as f:\n",
" agents_config = yaml.safe_load(f)\n",
"\n",
"with open('config/tasks.yaml', 'r') as f:\n",
" tasks_config = yaml.safe_load(f)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"retriever_agent = Agent(\n",
" config=agents_config['retriever_agent'],\n",
" verbose=True,\n",
" tools=[\n",
" pdf_tool,\n",
" web_search_tool\n",
" ]\n",
")\n",
"\n",
"response_synthesizer_agent = Agent(\n",
" config=agents_config['response_synthesizer_agent'],\n",
" verbose=True\n",
")\n",
"\n",
"retrieval_task = Task(\n",
" description=tasks_config['retrieval_task']['description'],\n",
" expected_output=tasks_config['retrieval_task']['expected_output'], \n",
" agent=retriever_agent\n",
")\n",
"\n",
"response_task = Task(\n",
" description=tasks_config['response_task']['description'],\n",
" expected_output=tasks_config['response_task']['expected_output'], \n",
" agent=response_synthesizer_agent\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2025-01-06 00:22:52,271 - 8394103616 - __init__.py-__init__:537 - WARNING: Overriding of current TracerProvider is not allowed\n"
]
}
],
"source": [
"crew = Crew(\n",
"\t\t\tagents=[retriever_agent, response_synthesizer_agent], \n",
"\t\t\ttasks=[retrieval_task, response_task],\n",
"\t\t\tprocess=Process.sequential,\n",
"\t\t\tverbose=True,\n",
"\t\t\t# process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/\n",
"\t\t)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Task:\u001b[00m \u001b[92mRetrieve the most relevant information from the available sources for the user query: Who is elon musk?\n",
"\u001b[00m\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/akshaypachaar/miniconda3/envs/env_crewai/lib/python3.10/site-packages/crewai/tools/tool_usage.py:162: PydanticDeprecatedSince20: The `schema` method is deprecated; use `model_json_schema` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n",
" acceptable_args = tool.args_schema.schema()[\"properties\"].keys() # type: ignore # Item \"None\" of \"type[BaseModel] | None\" has no attribute \"schema\"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Thought:\u001b[00m \u001b[92mI will first search for relevant information in the available documents about Elon Musk.\u001b[00m\n",
"\u001b[95m## Using tool:\u001b[00m \u001b[92mDocumentSearchTool\u001b[00m\n",
"\u001b[95m## Tool Input:\u001b[00m \u001b[92m\n",
"\"{\\\"query\\\": \\\"Who is elon musk?\\\"}\"\u001b[00m\n",
"\u001b[95m## Tool Output:\u001b[00m \u001b[92m\n",
"Content: (b) if Google believes, in good faith, that the Distributor has violated or caused Google to violate any\n",
"Anti-Bribery Laws (as defined in Clause 8.5) or that such a violation is reasonably likely to occur,\n",
"Source: 4-pl\n",
"=========\n",
"FINAL ANSWER: This Agreement is governed by English law.\n",
"SOURCES: 28-pl\n",
"\n",
"QUESTION: What did the president say about Michael Jackson?\n",
"=========\n",
"Content: Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet.\n",
"Justices of the Supreme Court. My fellow Americans.\n",
"Last year COVID-19 kept us apart. This year we are finally together again.\n",
"Tonight, we meet as Democrats Republicans and Independents. But most importantly as Americans.\n",
"With a duty to one another to the American people to the Constitution.\n",
"And with an unwavering resolve that freedom will always triumph over tyranny.\n",
"Six days ago, Russias Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to\n",
"his menacing ways. But he badly miscalculated.\n",
"He thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined.\n",
"He met the Ukrainian people.\n",
"From President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world.\n",
"Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending\n",
"their homeland.\n",
"Source: 0-pl\n",
"\n",
"___\n",
"result = total toys\n",
"return result\n",
"\n",
"Q: Jason had 20 lollipops. He gave Denny some lollipops. Now Jason has 12 lollipops. How many lollipops did Jason give to\n",
"Denny?\n",
"\n",
"\n",
"___\n",
"My fellow Americans|tonight , we have gathered in a sacred space|the citadel of our democracy.\n",
"In this Capitol, generation after generation, Americans have debated great questions amid great strife, and have done\n",
"great things.\n",
"We have fought for freedom, expanded liberty, defeated totalitarianism and terror.\n",
"And built the strongest, freest, and most prosperous nation the world has ever known.\n",
"Now is the hour.\n",
"Our moment of responsibility.\n",
"\n",
"___\n",
"To get there, I call on Congress to fund ARPA-H, the Advanced Research Projects Agency for Health.\n",
"Its based on DARPA|the Defense Department project that led to the Internet, GPS, and so much more.\n",
"\n",
"___\n",
"well as the NSF under CAREER grant CNS-1651570. Any opinions, findings, and conclusions or\n",
"recommendations expressed in this material are those of the authors and do not necessarily reflect\n",
"the views of the National Science Foundation. Omar Khattab is supported by the Apple Scholars in\n",
"AI/ML fellowship.\n",
"\n",
"\\usepackage[pdftex]{graphicx} ...\n",
"\\includegraphics[width=0.8\\linewidth]{myfile.pdf}\n",
"\n",
"REFERENCES\n",
"\n",
"\n",
"___\n",
"I checked: {query}\n",
"\n",
"___\n",
" In International Conference on Machine\n",
"Learning, pp.\n",
"___\n",
" arXiv preprint arXiv:2305.03495, 2023.\n",
"\n",
"Peng Qi, Xiaowen Lin, Leo Mehr, Zijian Wang, and Christopher D. Manning. Answering complex\n",
"In Proceedings of the 2019 Con-\n",
"\n",
"___\n",
"(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it\n",
"will return some similar entities to search.\n",
"(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.\n",
"(3) Finish[answer], which returns the answer and finishes the task.\n",
"Here are some examples.\n",
"Question: What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?\n",
"Action 1: Search[Colorado orogeny]\n",
"Observation 1: The Colorado orogeny was an episode of mountain building (an orogeny) in Colorado and surrounding areas.\n",
"Action 2: Lookup[eastern sector]\n",
"Observation 2: (Result 1 / 1) The eastern sector extends into the High Plains and is called the Central Plains orogeny.\n",
"Action 3: Search[High Plains]\n",
"Observation 3: High Plains refers to one of two distinct land regions:\n",
"Action 4: Search[High Plains (United States)]\n",
"Observation 4: The High Plains are a subregion of the Great Plains. From east to west, the High Plains rise in elevation\n",
"from around 1,800 to 7,000 ft (550 to 2,130 m).[3]\n",
"Action 5: Finish[1,800 to 7,000 ft]\n",
"Question: Musician and satirist Allie Goertz wrote a song about the \"The Simpsons\" character Milhouse, who Matt Groening\n",
"named after who?\n",
"Action 1: Search[Milhouse]\n",
"Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons\n",
"voiced by Pamela Hayden and created by Matt Groening.\n",
"Action 2: Lookup[named after]\n",
"Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.\n",
"Action 3: Finish[Richard Nixon]\n",
"Question: Which documentary is about Finnish rock groups, Adam Clayton Powell or The Saimaa Gesture?\n",
"Action 1: Search[Adam Clayton Powell]\n",
"Observation 1: Could not find [Adam Clayton Powell]. Similar: [Adam Clayton Powell III, Seventh Avenue (Manhattan),\n",
"Adam Clayton Powell Jr. State Office Building, Isabel Washington Powell, Adam Powell, Adam Clayton Powell (film),\n",
"\n",
"___\n",
" He gave Denny some lollipops. Now Jason has 12 lollipops. How many lollipops did Jason give\n",
"\n",
"to Denny?\"\"\"\n",
"\n",
"jason lollipops initial = 20\n",
"jason lollipops after = 12\n",
"denny lollipops = jason lollipops initial - jason lollipops after\n",
"result = denny lollipops\n",
"return result\n",
"\n",
"Q: Leah had 32 chocolates and her sister had 42. If they ate 35, how many pieces do they have left in total?\n",
"\n",
"# solution in Python:\n",
"\n",
"def solution():\n",
"\n",
"\"\"\"Leah had 32 chocolates and her sister had 42. If they ate 35, how many pieces do they have left in total?\"\"\"\n",
"leah chocolates = 32\n",
"sister chocolates = 42\n",
"total chocolates = leah chocolates + sister chocolates\n",
"chocolates eaten = 35\n",
"chocolates left = total chocolates - chocolates eaten\n",
"result = chocolates left\n",
"return result\n",
"\n",
"\u001b[00m\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/akshaypachaar/miniconda3/envs/env_crewai/lib/python3.10/site-packages/crewai/tools/tool_usage.py:162: PydanticDeprecatedSince20: The `schema` method is deprecated; use `model_json_schema` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n",
" acceptable_args = tool.args_schema.schema()[\"properties\"].keys() # type: ignore # Item \"None\" of \"type[BaseModel] | None\" has no attribute \"schema\"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Thought:\u001b[00m \u001b[92mThought: The search in the document did not yield relevant information regarding Elon Musk. I will now conduct a web search to find the information.\u001b[00m\n",
"\u001b[95m## Using tool:\u001b[00m \u001b[92mSearch the internet\u001b[00m\n",
"\u001b[95m## Tool Input:\u001b[00m \u001b[92m\n",
"\"{\\\"search_query\\\": \\\"Who is Elon Musk?\\\"}\"\u001b[00m\n",
"\u001b[95m## Tool Output:\u001b[00m \u001b[92m\n",
"\n",
"Search results: Title: Who is Elon Musk and what is his net worth? - BBC\n",
"Link: https://www.bbc.com/news/business-61234231\n",
"Snippet: The boss of X (formerly Twitter), Tesla and SpaceX is a billionaire and a business celebrity.\n",
"---\n",
"Title: Mr Elon Musk FRS - Fellow Detail Page | Royal Society\n",
"Link: https://royalsociety.org/people/elon-musk-13829/\n",
"Snippet: In 2017, SpaceX made further history by re-flying both a Falcon 9 rocket and Dragon spacecraft for the first time. By pioneering the development of reusable ...\n",
"---\n",
"Title: Elon Musk - Tesla Investor Relations\n",
"Link: https://ir.tesla.com/corporate/elon-musk\n",
"Snippet: Elon is Technoking of Tesla and has served as our Chief Executive Officer since October 2008 and as a member of the Board since April 2004.\n",
"---\n",
"Title: Elon Musk - Forbes\n",
"Link: https://www.forbes.com/profile/elon-musk/\n",
"Snippet: Real Time Net Worth · Elon Musk cofounded seven companies, including electric car maker Tesla, rocket producer SpaceX and artificial intelligence startup xAI.\n",
"---\n",
"Title: Elon Musk changes his name to Kekius Maximus on X - BBC\n",
"Link: https://www.bbc.com/news/articles/cy53vz1qpx1o\n",
"Snippet: The world's richest man sparks speculation after changing his name and using a picture of Pepe the Frog.\n",
"---\n",
"Title: Elon Musk | Tesla\n",
"Link: https://www.tesla.com/elon-musk\n",
"Snippet: As the co-founder and CEO of Tesla, Elon leads all product design, engineering and global manufacturing of the company's electric vehicles, battery products ...\n",
"---\n",
"Title: Elon Musk - WSJ Spotlight Coverage, Recent News\n",
"Link: https://www.wsj.com/topics/person/elon-musk\n",
"Snippet: Elon Musk is the CEO of electric-vehicle maker Tesla, whose board he joined in 2004. During his two decades there, the company has grown to be the global ...\n",
"---\n",
"Title: Elon Musk | Biography, SpaceX, Tesla, Twitter, X, Trump, & Facts\n",
"Link: https://www.britannica.com/money/Elon-Musk\n",
"Snippet: Elon Musk, the South African-born entrepreneur widely known as the founder of SpaceX and Tesla, is renowned for his advances in electric vehicles and space ...\n",
"---\n",
"Title: How did Elon Musk become the most powerful man in Washington?\n",
"Link: https://www.youtube.com/watch?v=8-iFXyLQQsg\n",
"Snippet: Elon Musk, the world's richest man, is also the most powerful man in Washington right now. The panel discusses how this came to be and if ...\n",
"---\n",
"\n",
"\n",
"\n",
"You ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n",
"\n",
"Tool Name: DocumentSearchTool\n",
"Tool Arguments: {'query': {'description': 'Query to search the document.', 'type': 'str'}}\n",
"Tool Description: Search the document for the given query.\n",
"Tool Name: Search the internet\n",
"Tool Arguments: {'search_query': {'description': 'Mandatory search query you want to use to search the internet', 'type': 'str'}}\n",
"Tool Description: A tool that can be used to search the internet with a search_query.\n",
"\n",
"Use the following format:\n",
"\n",
"Thought: you should always think about what to do\n",
"Action: the action to take, only one name of [DocumentSearchTool, Search the internet], just the name, exactly as it's written.\n",
"Action Input: the input to the action, just a simple python dictionary, enclosed in curly braces, using \" to wrap keys and values.\n",
"Observation: the result of the action\n",
"\n",
"Once all necessary information is gathered:\n",
"\n",
"Thought: I now know the final answer\n",
"Final Answer: the final answer to the original input question\n",
"\u001b[00m\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/akshaypachaar/miniconda3/envs/env_crewai/lib/python3.10/site-packages/crewai/tools/tool_usage.py:162: PydanticDeprecatedSince20: The `schema` method is deprecated; use `model_json_schema` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n",
" acceptable_args = tool.args_schema.schema()[\"properties\"].keys() # type: ignore # Item \"None\" of \"type[BaseModel] | None\" has no attribute \"schema\"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Thought:\u001b[00m \u001b[92mThought: I have found multiple relevant sources from the web about Elon Musk. I will now extract the complete content from the best source.\u001b[00m\n",
"\u001b[95m## Using tool:\u001b[00m \u001b[92mSearch the internet\u001b[00m\n",
"\u001b[95m## Tool Input:\u001b[00m \u001b[92m\n",
"\"{\\\"search_query\\\": \\\"Elon Musk biography\\\"}\"\u001b[00m\n",
"\u001b[95m## Tool Output:\u001b[00m \u001b[92m\n",
"\n",
"Search results: Title: Elon Musk - Wikipedia\n",
"Link: https://en.wikipedia.org/wiki/Elon_Musk\n",
"Snippet: Elon Reeve Musk is a businessman known for his key roles in the space company SpaceX and the automotive company Tesla, Inc. He is also known for his ...\n",
"---\n",
"Title: Elon Musk: Biography, Entrepreneur, SpaceX and Tesla Founder\n",
"Link: https://www.biography.com/business-leaders/elon-musk\n",
"Snippet: Elon Reeve Musk was born on June 28, 1971, in Pretoria, South Africa. His mother, Maye Musk, is a Canadian model and the oldest woman to star in ...\n",
"---\n",
"Title: Elon Musk: Isaacson, Walter: 9781982181284 - Amazon.com\n",
"Link: https://www.amazon.com/Elon-Musk-Walter-Isaacson/dp/1982181281\n",
"Snippet: The #1 New York Times bestseller from the author of Steve Jobs--this is the astonishingly intimate story of the most fascinating and controversial innovator of our era--a rule-breaking visionary who helped to lead the world into the era of electric vehicles, private space exploration, and artificial intelligence.\n",
"---\n",
"Title: Elon Musk | Book by Walter Isaacson | Official Publisher Page\n",
"Link: https://www.simonandschuster.com/books/Elon-Musk/Walter-Isaacson/9781982181284\n",
"Snippet: The #1 New York Times bestseller from the author of Steve Jobs--this is the astonishingly intimate story of the most fascinating and controversial innovator of our era--a rule-breaking visionary who helped to lead the world into the era of electric vehicles, private space exploration, and artificial intelligence.\n",
"---\n",
"Title: Elon Musk | Biography, SpaceX, Tesla, Twitter, X, Trump, & Facts\n",
"Link: https://www.britannica.com/money/Elon-Musk\n",
"Snippet: South African-born American entrepreneur who cofounded the electronic-payment firm PayPal and formed SpaceX, maker of launch vehicles and spacecraft.\n",
"---\n",
"Title: Book Review: 'Elon Musk,' by Walter Isaacson - The New York Times\n",
"Link: https://www.nytimes.com/2023/09/09/books/review/elon-musk-walter-isaacson.html\n",
"Snippet: Walter Isaacson's biography of the billionaire entrepreneur depicts a mercurial “man-child” with grandiose ambitions and an ego to match.\n",
"---\n",
"Title: Who is Elon Musk and what is his net worth? - BBC\n",
"Link: https://www.bbc.com/news/business-61234231\n",
"Snippet: Born in Pretoria, South Africa, Mr Musk showed his talents for entrepreneurship early, going door-to-door with his brother selling homemade ...\n",
"---\n",
"Title: Elon Musk: Tesla, SpaceX, and the Quest for a Fantastic Future\n",
"Link: https://www.amazon.com/Elon-Musk-SpaceX-Fantastic-Future/dp/0062301233\n",
"Snippet: In the spirit of Steve Jobs and Moneyball, Elon Musk is both an illuminating and authorized look at the extraordinary life of one of Silicon Valley's most exciting, unpredictable, and ambitious entrepreneurs--a real-life Tony Stark--and a fascinating exploration of the renewal of American invention and its new ``makers.''\n",
"---\n",
"Title: Elon Musk's Biography Summary - Dante\n",
"Link: https://dantekim.com/notes/elon-musk-tesla-spacex-and-the-quest-for-a-fantastic-future/\n",
"Snippet: In his biography you get to dig much deeper and see his flaws, extreme perseverance and genius in such close detail.\n",
"---\n",
"Title: Elon Musk By Walter Isaacson: A Biography Book On Hardcore Hope\n",
"Link: https://www.youtube.com/watch?v=srlhWHvRVuQ\n",
"Snippet: Elon Musk: the name synonymous with Tesla, SpaceX, and audacious dreams of colonizing Mars. But what's the man behind the ambition?\n",
"---\n",
"\u001b[00m\n",
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Final Answer:\u001b[00m \u001b[92m\n",
"**Elon Reeve Musk** (born June 28, 1971) is a South African-born American entrepreneur and business magnate. He is the founder, CEO, and chief engineer of SpaceX; early investor, CEO, and product architect of Tesla, Inc.; founder of The Boring Company; co-founder of Neuralink; and co-founder and initial co-chairman of OpenAI. Musk is known for his ambitious vision for the future, including the colonization of Mars and the advent of sustainable energy. \n",
"\n",
"Born in Pretoria, South Africa, Musk showed an early interest in computing and technology. He taught himself computer programming at a young age and created a video game, which he sold at the age of 12. After moving to the U.S. to study at the University of Pennsylvania, Musk dropped out to pursue his entrepreneurial ambitions, co-founding Zip2, X.com, which later became PayPal, and then aiming to revolutionize transportation with his work on electric vehicles through Tesla.\n",
"\n",
"Musk established SpaceX in 2002 to reduce space transportation costs and enable the colonization of Mars. He made significant advancements with the Falcon rocket series and the Dragon spacecraft. With Tesla, Musk aims to accelerate the world's transition to sustainable energy, to help combat climate change.\n",
"\n",
"Musk has been recognized for his contributions and endeavors in the technology industry and has been listed among the most powerful and influential people in the world.\n",
"\n",
"For a more detailed exploration of his life, contributions, and impact, you can read further on his [Wikipedia page](https://en.wikipedia.org/wiki/Elon_Musk).\u001b[00m\n",
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mResponse synthesizer agent for the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Task:\u001b[00m \u001b[92mSynthesize the final response for the user query: Who is elon musk?\n",
"\u001b[00m\n",
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mResponse synthesizer agent for the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Final Answer:\u001b[00m \u001b[92m\n",
"**Elon Reeve Musk** (born June 28, 1971) is a South African-born American entrepreneur and business magnate, renowned for his groundbreaking contributions in various technology sectors. He is the founder, CEO, and chief engineer of SpaceX; an early investor, CEO, and product architect of Tesla, Inc.; the founder of The Boring Company; and a co-founder of Neuralink and OpenAI. Musk is celebrated for his ambitious vision for the future, which includes plans for the colonization of Mars and the promotion of sustainable energy.\n",
"\n",
"Born in Pretoria, South Africa, Musk displayed a keen interest in computing from a young age, teaching himself programming and creating a video game that he sold by the age of 12. He moved to the United States to study at the University of Pennsylvania but left before completing his degree to pursue entrepreneurial ventures, co-founding Zip2 and later X.com, which evolved into PayPal. His ambitions continued with Tesla, where he aims to revolutionize transportation through electric vehicles and combat climate change.\n",
"\n",
"Musk started SpaceX in 2002 with the goal of reducing space transportation costs and enabling the colonization of Mars, achieving significant milestones with the Falcon rocket series and the Dragon spacecraft. Recognized globally, Musk has been cited as one of the most powerful and influential people in the world due to his innovative works and contributions to technology. For further details about his life and impact, refer to his [Wikipedia page](https://en.wikipedia.org/wiki/Elon_Musk).\u001b[00m\n",
"\n",
"\n",
"**Elon Reeve Musk** (born June 28, 1971) is a South African-born American entrepreneur and business magnate, renowned for his groundbreaking contributions in various technology sectors. He is the founder, CEO, and chief engineer of SpaceX; an early investor, CEO, and product architect of Tesla, Inc.; the founder of The Boring Company; and a co-founder of Neuralink and OpenAI. Musk is celebrated for his ambitious vision for the future, which includes plans for the colonization of Mars and the promotion of sustainable energy.\n",
"\n",
"Born in Pretoria, South Africa, Musk displayed a keen interest in computing from a young age, teaching himself programming and creating a video game that he sold by the age of 12. He moved to the United States to study at the University of Pennsylvania but left before completing his degree to pursue entrepreneurial ventures, co-founding Zip2 and later X.com, which evolved into PayPal. His ambitions continued with Tesla, where he aims to revolutionize transportation through electric vehicles and combat climate change.\n",
"\n",
"Musk started SpaceX in 2002 with the goal of reducing space transportation costs and enabling the colonization of Mars, achieving significant milestones with the Falcon rocket series and the Dragon spacecraft. Recognized globally, Musk has been cited as one of the most powerful and influential people in the world due to his innovative works and contributions to technology. For further details about his life and impact, refer to his [Wikipedia page](https://en.wikipedia.org/wiki/Elon_Musk).\n"
]
}
],
"source": [
"result = crew.kickoff(inputs={\"query\": \"Who is elon musk?\"})\n",
"print(result)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "env_crewai",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.15"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,24 @@
# routing_agent:
# role: >
# Understand the user query: {query}, and route it to the most appropriate knowledge base
# goal: >
# Route user queries to the most appropriate knowledge base
# backstory: >
# You are a user query routing agent. You are knowln for your ability to carefully understand user queries and route them to the most appropriate knowledge base.
retriever_agent:
role: >
Retrieve relevant information to answer the user query: {query}
goal: >
Retrieve the most relevant information from the available sources for the user query: {query}, always try to use the pdf search tool first. If you are not able to retrieve the information from the pdf search tool then try to use the web search tool.
backstory: >
You're a meticulous analyst with a keen eye for detail. You're known for
your ability understand the user query: {query} and retrieve knowlege from the most suitable knowledge base.
response_synthesizer_agent:
role: >
Response synthesizer agent for the user query: {query}
goal: >
Synthesize the retrieved information into a concise and coherent response based on the user query: {query}. If you are not ble to retrieve the information then respond with "I'm sorry, I couldn't find the information you're looking for."
backstory: >
You're a skilled communicator with a knack for turning complex information into clear and concise responses.
@@ -0,0 +1,20 @@
# routing_task:
# description: >
# Understand the user query: {query}, and route it to the most appropriate knowledge base
# expected_output: >
# The most appropriate knowledge base to route the user query to, the answer should be either 'pdf_search' or 'pdf_search'
# agent: routing_agent
retrieval_task:
description: >
Retrieve the most relevant information from the available sources for the user query: {query}
expected_output: >
The most relevant information in form of text as retrieved from the sources.
agent: retriever_agent
response_task:
description: >
Synthesize the final response for the user query: {query}
expected_output: >
A concise and coherent response based on the retrieved infromation from the right source for the user query: {query}. If you are not ble to retrieve the information then respond with "I'm sorry, I couldn't find the information you're looking for."
agent: response_synthesizer_agent
+76
View File
@@ -0,0 +1,76 @@
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
from crewai_tools import SerperDevTool
from crewai_tools import PDFSearchTool
# from tools.custom_tool import DocumentSearchTool
from agentic_rag.tools.custom_tool import DocumentSearchTool
# Initialize the tool with a specific PDF path for exclusive search within that document
pdf_tool = DocumentSearchTool(pdf='/Users/akshaypachaar/Eigen/ai-engineering/agentic_rag/knowledge/dspy.pdf')
web_search_tool = SerperDevTool()
@CrewBase
class AgenticRag():
"""AgenticRag crew"""
agents_config = 'config/agents.yaml'
tasks_config = 'config/tasks.yaml'
# If you would like to add tools to your agents, you can learn more about it here:
# https://docs.crewai.com/concepts/agents#agent-tools
# @agent
# def routing_agent(self) -> Agent:
# return Agent(
# config=self.agents_config['routing_agent'],
# verbose=True
# )
@agent
def retriever_agent(self) -> Agent:
return Agent(
config=self.agents_config['retriever_agent'],
verbose=True,
tools=[
pdf_tool,
web_search_tool
]
)
@agent
def response_synthesizer_agent(self) -> Agent:
return Agent(
config=self.agents_config['response_synthesizer_agent'],
verbose=True
)
# @task
# def routing_task(self) -> Task:
# return Task(
# config=self.tasks_config['routing_task'],
# )
@task
def retrieval_task(self) -> Task:
return Task(
config=self.tasks_config['retrieval_task'],
)
@task
def response_task(self) -> Task:
return Task(
config=self.tasks_config['response_task'],
)
@crew
def crew(self) -> Crew:
"""Creates the AgenticRag crew"""
# To learn how to add knowledge sources to your crew, check out the documentation:
# https://docs.crewai.com/concepts/knowledge#what-is-knowledge
return Crew(
agents=self.agents, # Automatically created by the @agent decorator
tasks=self.tasks, # Automatically created by the @task decorator
process=Process.sequential,
verbose=True,
# process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/
)
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env python
import sys
import warnings
from agentic_rag.crew import AgenticRag
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
# This main file is intended to be a way for you to run your
# crew locally, so refrain from adding unnecessary logic into this file.
# Replace with inputs you want to test with, it will automatically
# interpolate any tasks and agents information
def run():
"""
Run the crew.
"""
inputs = {
'query': 'Who is elon musk?'
}
AgenticRag().crew().kickoff(inputs=inputs)
def train():
"""
Train the crew for a given number of iterations.
"""
inputs = {
"topic": "AI LLMs"
}
try:
AgenticRag().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs)
except Exception as e:
raise Exception(f"An error occurred while training the crew: {e}")
def replay():
"""
Replay the crew execution from a specific task.
"""
try:
AgenticRag().crew().replay(task_id=sys.argv[1])
except Exception as e:
raise Exception(f"An error occurred while replaying the crew: {e}")
def test():
"""
Test the crew execution and returns the results.
"""
inputs = {
"topic": "AI LLMs"
}
try:
AgenticRag().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs)
except Exception as e:
raise Exception(f"An error occurred while replaying the crew: {e}")
@@ -0,0 +1,81 @@
import os
from crewai.tools import BaseTool
from typing import Type
from pydantic import BaseModel, Field, ConfigDict
from markitdown import MarkItDown
from chonkie import SemanticChunker
from qdrant_client import QdrantClient
class DocumentSearchToolInput(BaseModel):
"""Input schema for DocumentSearchTool."""
query: str = Field(..., description="Query to search the document.")
class DocumentSearchTool(BaseTool):
name: str = "DocumentSearchTool"
description: str = "Search the document for the given query."
args_schema: Type[BaseModel] = DocumentSearchToolInput
model_config = ConfigDict(extra="allow")
def __init__(self, file_path: str):
"""Initialize the searcher with a PDF file path and set up the Qdrant collection."""
super().__init__()
self.file_path = file_path
self.client = QdrantClient(":memory:") # For small experiments
self._process_document()
def _extract_text(self) -> str:
"""Extract raw text from PDF using MarkItDown."""
md = MarkItDown()
result = md.convert(self.file_path)
return result.text_content
def _create_chunks(self, raw_text: str) -> list:
"""Create semantic chunks from raw text."""
chunker = SemanticChunker(
embedding_model="minishlab/potion-base-8M",
threshold=0.5,
chunk_size=512,
min_sentences=1
)
return chunker.chunk(raw_text)
def _process_document(self):
"""Process the document and add chunks to Qdrant collection."""
raw_text = self._extract_text()
chunks = self._create_chunks(raw_text)
docs = [chunk.text for chunk in chunks]
metadata = [{"source": os.path.basename(self.file_path)} for _ in range(len(chunks))]
ids = list(range(len(chunks)))
self.client.add(
collection_name="demo_collection",
documents=docs,
metadata=metadata,
ids=ids
)
def _run(self, query: str) -> list:
"""Search the document with a query string."""
relevant_chunks = self.client.query(
collection_name="demo_collection",
query_text=query
)
docs = [chunk.document for chunk in relevant_chunks]
separator = "\n___\n"
return separator.join(docs)
# Test the implementation
def test_document_searcher():
# Test file path
pdf_path = "/Users/akshaypachaar/Eigen/ai-engineering/agentic_rag/knowledge/dspy.pdf"
# Create instance
searcher = DocumentSearchTool(file_path=pdf_path)
# Test search
result = searcher._run("What is the purpose of DSpy?")
print("Search Results:", result)
if __name__ == "__main__":
test_document_searcher()
Binary file not shown.

After

Width:  |  Height:  |  Size: 755 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 KiB

+2
View File
@@ -0,0 +1,2 @@
GROUNDX_API_KEY=your_groundx_api_key
SERPER_API_KEY=your_serper_api_key
+45
View File
@@ -0,0 +1,45 @@
# Enterprise-grade, agentic RAG over complex real-world docs
The project uses EyelevelAI's state of the art document parsing and retrieval system GroundX. It's integrated as a custom tool with CrewAI.
Before you start, quickly test it on your own document [here](https://dashboard.eyelevel.ai/xray)
GroundX can also be deployed completely on premise as well, the code is open-source, here's their [GitHub repo](https://github.com/eyelevelai/groundx-on-prem).
Grab your API keys's here.
- [GroundX API keys](https://docs.eyelevel.ai/documentation/fundamentals/quickstart#step-1-getting-your-api-key)
- [SERPER API keys](https://serper.dev/)
### Watch this tutorial on YouTube
[![Watch this tutorial on YouTube](https://github.com/patchy631/ai-engineering-hub/blob/main/agentic_rag_deepseek/assets/thumbnail.png)](https://www.youtube.com/watch?v=79xvgj4wvHQ)
---
## Setup and installations
**Setup Environment**:
- Paste your API keys by creating a `.env`
- Refer `.env.example` file
**Install Dependencies**:
Ensure you have Python 3.11 or later installed.
```bash
pip install groundx crewai crewai-tools
```
**Running the app**:
```bash
streamlit run app_deep_seek.py
```
---
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
---
## Contribution
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
+201
View File
@@ -0,0 +1,201 @@
import streamlit as st
import os
import tempfile
import gc
import base64
import time
from crewai import Agent, Crew, Process, Task, LLM
from crewai_tools import SerperDevTool
from src.agentic_rag.tools.custom_tool import DocumentSearchTool
@st.cache_resource
def load_llm():
llm = LLM(
model="ollama/deepseek-r1:7b",
base_url="http://localhost:11434"
)
return llm
# ===========================
# Define Agents & Tasks
# ===========================
def create_agents_and_tasks(pdf_tool):
"""Creates a Crew with the given PDF tool (if any) and a web search tool."""
web_search_tool = SerperDevTool()
retriever_agent = Agent(
role="Retrieve relevant information to answer the user query: {query}",
goal=(
"Retrieve the most relevant information from the available sources "
"for the user query: {query}. Always try to use the PDF search tool first. "
"If you are not able to retrieve the information from the PDF search tool, "
"then try to use the web search tool."
),
backstory=(
"You're a meticulous analyst with a keen eye for detail. "
"You're known for your ability to understand user queries: {query} "
"and retrieve knowledge from the most suitable knowledge base."
),
verbose=True,
tools=[t for t in [pdf_tool, web_search_tool] if t],
llm=load_llm()
)
response_synthesizer_agent = Agent(
role="Response synthesizer agent for the user query: {query}",
goal=(
"Synthesize the retrieved information into a concise and coherent response "
"based on the user query: {query}. If you are not able to retrieve the "
'information then respond with "I\'m sorry, I couldn\'t find the information '
'you\'re looking for."'
),
backstory=(
"You're a skilled communicator with a knack for turning "
"complex information into clear and concise responses."
),
verbose=True,
llm=load_llm()
)
retrieval_task = Task(
description=(
"Retrieve the most relevant information from the available "
"sources for the user query: {query}"
),
expected_output=(
"The most relevant information in the form of text as retrieved "
"from the sources."
),
agent=retriever_agent
)
response_task = Task(
description="Synthesize the final response for the user query: {query}",
expected_output=(
"A concise and coherent response based on the retrieved information "
"from the right source for the user query: {query}. If you are not "
"able to retrieve the information, then respond with: "
'"I\'m sorry, I couldn\'t find the information you\'re looking for."'
),
agent=response_synthesizer_agent
)
crew = Crew(
agents=[retriever_agent, response_synthesizer_agent],
tasks=[retrieval_task, response_task],
process=Process.sequential, # or Process.hierarchical
verbose=True
)
return crew
# ===========================
# Streamlit Setup
# ===========================
if "messages" not in st.session_state:
st.session_state.messages = [] # Chat history
if "pdf_tool" not in st.session_state:
st.session_state.pdf_tool = None # Store the DocumentSearchTool
if "crew" not in st.session_state:
st.session_state.crew = None # Store the Crew object
def reset_chat():
st.session_state.messages = []
gc.collect()
def display_pdf(file_bytes: bytes, file_name: str):
"""Displays the uploaded PDF in an iframe."""
base64_pdf = base64.b64encode(file_bytes).decode("utf-8")
pdf_display = f"""
<iframe
src="data:application/pdf;base64,{base64_pdf}"
width="100%"
height="600px"
type="application/pdf"
>
</iframe>
"""
st.markdown(f"### Preview of {file_name}")
st.markdown(pdf_display, unsafe_allow_html=True)
# ===========================
# Sidebar
# ===========================
with st.sidebar:
st.header("Add Your PDF Document")
uploaded_file = st.file_uploader("Choose a PDF file", type=["pdf"])
if uploaded_file is not None:
# If there's a new file and we haven't set pdf_tool yet...
if st.session_state.pdf_tool is None:
with tempfile.TemporaryDirectory() as temp_dir:
temp_file_path = os.path.join(temp_dir, uploaded_file.name)
with open(temp_file_path, "wb") as f:
f.write(uploaded_file.getvalue())
with st.spinner("Indexing PDF... Please wait..."):
# st.session_state.pdf_tool = DocumentSearchTool(file_path="/Users/akshay/Eigen/ai-engineering-hub/agentic_rag_deepseek/knowledge/dspy.pdf")
st.session_state.pdf_tool = DocumentSearchTool(file_path=temp_file_path)
# Test search
# result = st.session_state.pdf_tool._run("What is the purpose of DSpy?")
# st.info("Initial Test Search Results:", icon="🔍")
# st.write(result)
st.success("PDF indexed! Ready to chat.")
# Optionally display the PDF in the sidebar
display_pdf(uploaded_file.getvalue(), uploaded_file.name)
st.button("Clear Chat", on_click=reset_chat)
# ===========================
# Main Chat Interface
# ===========================
st.markdown("""
# Agentic RAG over complex real-world documents powered by <img src="data:image/png;base64,{}" width="220" style="vertical-align: -12px;">
""".format(base64.b64encode(open("assets/groundx.png", "rb").read()).decode()), unsafe_allow_html=True)
# Render existing conversation
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Chat input
prompt = st.chat_input("Ask a question about your PDF...")
if prompt:
# 1. Show user message immediately
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
# 2. Build or reuse the Crew (only once after PDF is loaded)
if st.session_state.crew is None:
st.session_state.crew = create_agents_and_tasks(st.session_state.pdf_tool)
# 3. Get the response
with st.chat_message("assistant"):
message_placeholder = st.empty()
full_response = ""
# Get the complete response first
with st.spinner("Thinking..."):
inputs = {"query": prompt}
result = st.session_state.crew.kickoff(inputs=inputs).raw
# Split by lines first to preserve code blocks and other markdown
lines = result.split('\n')
for i, line in enumerate(lines):
full_response += line
if i < len(lines) - 1: # Don't add newline to the last line
full_response += '\n'
message_placeholder.markdown(full_response + "")
time.sleep(0.15) # Adjust the speed as needed
# Show the final response without the cursor
message_placeholder.markdown(full_response)
# 4. Save assistant's message to session
st.session_state.messages.append({"role": "assistant", "content": result})
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 KiB

Binary file not shown.
@@ -0,0 +1,498 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"# !pip install \"chonkie[semantic]\"\n",
"# !pip install markitdown\n",
"# !pip install qdrant-client\n",
"# !pip install fastembed"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"from crewai import Agent, Crew, Process, Task\n",
"from crewai.project import CrewBase, agent, crew, task\n",
"from crewai_tools import SerperDevTool\n",
"from crewai_tools import PDFSearchTool\n",
"# from tools.custom_tool import DocumentSearchTool\n",
"from tools.custom_tool import DocumentSearchTool\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/akshaypachaar/miniconda3/envs/env_crewai/lib/python3.10/site-packages/model2vec/hf_utils.py:152: ResourceWarning: unclosed file <_io.TextIOWrapper name='/Users/akshaypachaar/.cache/huggingface/hub/models--minishlab--potion-base-8M/snapshots/dcbec7aa2d52fc76754ac6291803feedd8c619ce/config.json' mode='r' encoding='UTF-8'>\n",
" config = json.load(open(config_path))\n",
"ResourceWarning: Enable tracemalloc to get the object allocation traceback\n"
]
}
],
"source": [
"pdf_tool = DocumentSearchTool(file_path='/Users/akshaypachaar/Eigen/ai-engineering/agentic_rag/knowledge/dspy.pdf')"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"web_search_tool = SerperDevTool()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"import yaml\n",
"with open('config/agents.yaml', 'r') as f:\n",
" agents_config = yaml.safe_load(f)\n",
"\n",
"with open('config/tasks.yaml', 'r') as f:\n",
" tasks_config = yaml.safe_load(f)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"retriever_agent = Agent(\n",
" config=agents_config['retriever_agent'],\n",
" verbose=True,\n",
" tools=[\n",
" pdf_tool,\n",
" web_search_tool\n",
" ]\n",
")\n",
"\n",
"response_synthesizer_agent = Agent(\n",
" config=agents_config['response_synthesizer_agent'],\n",
" verbose=True\n",
")\n",
"\n",
"retrieval_task = Task(\n",
" description=tasks_config['retrieval_task']['description'],\n",
" expected_output=tasks_config['retrieval_task']['expected_output'], \n",
" agent=retriever_agent\n",
")\n",
"\n",
"response_task = Task(\n",
" description=tasks_config['response_task']['description'],\n",
" expected_output=tasks_config['response_task']['expected_output'], \n",
" agent=response_synthesizer_agent\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2025-01-06 00:22:52,271 - 8394103616 - __init__.py-__init__:537 - WARNING: Overriding of current TracerProvider is not allowed\n"
]
}
],
"source": [
"crew = Crew(\n",
"\t\t\tagents=[retriever_agent, response_synthesizer_agent], \n",
"\t\t\ttasks=[retrieval_task, response_task],\n",
"\t\t\tprocess=Process.sequential,\n",
"\t\t\tverbose=True,\n",
"\t\t\t# process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/\n",
"\t\t)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Task:\u001b[00m \u001b[92mRetrieve the most relevant information from the available sources for the user query: Who is elon musk?\n",
"\u001b[00m\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/akshaypachaar/miniconda3/envs/env_crewai/lib/python3.10/site-packages/crewai/tools/tool_usage.py:162: PydanticDeprecatedSince20: The `schema` method is deprecated; use `model_json_schema` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n",
" acceptable_args = tool.args_schema.schema()[\"properties\"].keys() # type: ignore # Item \"None\" of \"type[BaseModel] | None\" has no attribute \"schema\"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Thought:\u001b[00m \u001b[92mI will first search for relevant information in the available documents about Elon Musk.\u001b[00m\n",
"\u001b[95m## Using tool:\u001b[00m \u001b[92mDocumentSearchTool\u001b[00m\n",
"\u001b[95m## Tool Input:\u001b[00m \u001b[92m\n",
"\"{\\\"query\\\": \\\"Who is elon musk?\\\"}\"\u001b[00m\n",
"\u001b[95m## Tool Output:\u001b[00m \u001b[92m\n",
"Content: (b) if Google believes, in good faith, that the Distributor has violated or caused Google to violate any\n",
"Anti-Bribery Laws (as defined in Clause 8.5) or that such a violation is reasonably likely to occur,\n",
"Source: 4-pl\n",
"=========\n",
"FINAL ANSWER: This Agreement is governed by English law.\n",
"SOURCES: 28-pl\n",
"\n",
"QUESTION: What did the president say about Michael Jackson?\n",
"=========\n",
"Content: Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet.\n",
"Justices of the Supreme Court. My fellow Americans.\n",
"Last year COVID-19 kept us apart. This year we are finally together again.\n",
"Tonight, we meet as Democrats Republicans and Independents. But most importantly as Americans.\n",
"With a duty to one another to the American people to the Constitution.\n",
"And with an unwavering resolve that freedom will always triumph over tyranny.\n",
"Six days ago, Russias Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to\n",
"his menacing ways. But he badly miscalculated.\n",
"He thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined.\n",
"He met the Ukrainian people.\n",
"From President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world.\n",
"Groups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending\n",
"their homeland.\n",
"Source: 0-pl\n",
"\n",
"___\n",
"result = total toys\n",
"return result\n",
"\n",
"Q: Jason had 20 lollipops. He gave Denny some lollipops. Now Jason has 12 lollipops. How many lollipops did Jason give to\n",
"Denny?\n",
"\n",
"\n",
"___\n",
"My fellow Americans|tonight , we have gathered in a sacred space|the citadel of our democracy.\n",
"In this Capitol, generation after generation, Americans have debated great questions amid great strife, and have done\n",
"great things.\n",
"We have fought for freedom, expanded liberty, defeated totalitarianism and terror.\n",
"And built the strongest, freest, and most prosperous nation the world has ever known.\n",
"Now is the hour.\n",
"Our moment of responsibility.\n",
"\n",
"___\n",
"To get there, I call on Congress to fund ARPA-H, the Advanced Research Projects Agency for Health.\n",
"Its based on DARPA|the Defense Department project that led to the Internet, GPS, and so much more.\n",
"\n",
"___\n",
"well as the NSF under CAREER grant CNS-1651570. Any opinions, findings, and conclusions or\n",
"recommendations expressed in this material are those of the authors and do not necessarily reflect\n",
"the views of the National Science Foundation. Omar Khattab is supported by the Apple Scholars in\n",
"AI/ML fellowship.\n",
"\n",
"\\usepackage[pdftex]{graphicx} ...\n",
"\\includegraphics[width=0.8\\linewidth]{myfile.pdf}\n",
"\n",
"REFERENCES\n",
"\n",
"\n",
"___\n",
"I checked: {query}\n",
"\n",
"___\n",
" In International Conference on Machine\n",
"Learning, pp.\n",
"___\n",
" arXiv preprint arXiv:2305.03495, 2023.\n",
"\n",
"Peng Qi, Xiaowen Lin, Leo Mehr, Zijian Wang, and Christopher D. Manning. Answering complex\n",
"In Proceedings of the 2019 Con-\n",
"\n",
"___\n",
"(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it\n",
"will return some similar entities to search.\n",
"(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.\n",
"(3) Finish[answer], which returns the answer and finishes the task.\n",
"Here are some examples.\n",
"Question: What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?\n",
"Action 1: Search[Colorado orogeny]\n",
"Observation 1: The Colorado orogeny was an episode of mountain building (an orogeny) in Colorado and surrounding areas.\n",
"Action 2: Lookup[eastern sector]\n",
"Observation 2: (Result 1 / 1) The eastern sector extends into the High Plains and is called the Central Plains orogeny.\n",
"Action 3: Search[High Plains]\n",
"Observation 3: High Plains refers to one of two distinct land regions:\n",
"Action 4: Search[High Plains (United States)]\n",
"Observation 4: The High Plains are a subregion of the Great Plains. From east to west, the High Plains rise in elevation\n",
"from around 1,800 to 7,000 ft (550 to 2,130 m).[3]\n",
"Action 5: Finish[1,800 to 7,000 ft]\n",
"Question: Musician and satirist Allie Goertz wrote a song about the \"The Simpsons\" character Milhouse, who Matt Groening\n",
"named after who?\n",
"Action 1: Search[Milhouse]\n",
"Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons\n",
"voiced by Pamela Hayden and created by Matt Groening.\n",
"Action 2: Lookup[named after]\n",
"Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.\n",
"Action 3: Finish[Richard Nixon]\n",
"Question: Which documentary is about Finnish rock groups, Adam Clayton Powell or The Saimaa Gesture?\n",
"Action 1: Search[Adam Clayton Powell]\n",
"Observation 1: Could not find [Adam Clayton Powell]. Similar: [Adam Clayton Powell III, Seventh Avenue (Manhattan),\n",
"Adam Clayton Powell Jr. State Office Building, Isabel Washington Powell, Adam Powell, Adam Clayton Powell (film),\n",
"\n",
"___\n",
" He gave Denny some lollipops. Now Jason has 12 lollipops. How many lollipops did Jason give\n",
"\n",
"to Denny?\"\"\"\n",
"\n",
"jason lollipops initial = 20\n",
"jason lollipops after = 12\n",
"denny lollipops = jason lollipops initial - jason lollipops after\n",
"result = denny lollipops\n",
"return result\n",
"\n",
"Q: Leah had 32 chocolates and her sister had 42. If they ate 35, how many pieces do they have left in total?\n",
"\n",
"# solution in Python:\n",
"\n",
"def solution():\n",
"\n",
"\"\"\"Leah had 32 chocolates and her sister had 42. If they ate 35, how many pieces do they have left in total?\"\"\"\n",
"leah chocolates = 32\n",
"sister chocolates = 42\n",
"total chocolates = leah chocolates + sister chocolates\n",
"chocolates eaten = 35\n",
"chocolates left = total chocolates - chocolates eaten\n",
"result = chocolates left\n",
"return result\n",
"\n",
"\u001b[00m\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/akshaypachaar/miniconda3/envs/env_crewai/lib/python3.10/site-packages/crewai/tools/tool_usage.py:162: PydanticDeprecatedSince20: The `schema` method is deprecated; use `model_json_schema` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n",
" acceptable_args = tool.args_schema.schema()[\"properties\"].keys() # type: ignore # Item \"None\" of \"type[BaseModel] | None\" has no attribute \"schema\"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Thought:\u001b[00m \u001b[92mThought: The search in the document did not yield relevant information regarding Elon Musk. I will now conduct a web search to find the information.\u001b[00m\n",
"\u001b[95m## Using tool:\u001b[00m \u001b[92mSearch the internet\u001b[00m\n",
"\u001b[95m## Tool Input:\u001b[00m \u001b[92m\n",
"\"{\\\"search_query\\\": \\\"Who is Elon Musk?\\\"}\"\u001b[00m\n",
"\u001b[95m## Tool Output:\u001b[00m \u001b[92m\n",
"\n",
"Search results: Title: Who is Elon Musk and what is his net worth? - BBC\n",
"Link: https://www.bbc.com/news/business-61234231\n",
"Snippet: The boss of X (formerly Twitter), Tesla and SpaceX is a billionaire and a business celebrity.\n",
"---\n",
"Title: Mr Elon Musk FRS - Fellow Detail Page | Royal Society\n",
"Link: https://royalsociety.org/people/elon-musk-13829/\n",
"Snippet: In 2017, SpaceX made further history by re-flying both a Falcon 9 rocket and Dragon spacecraft for the first time. By pioneering the development of reusable ...\n",
"---\n",
"Title: Elon Musk - Tesla Investor Relations\n",
"Link: https://ir.tesla.com/corporate/elon-musk\n",
"Snippet: Elon is Technoking of Tesla and has served as our Chief Executive Officer since October 2008 and as a member of the Board since April 2004.\n",
"---\n",
"Title: Elon Musk - Forbes\n",
"Link: https://www.forbes.com/profile/elon-musk/\n",
"Snippet: Real Time Net Worth · Elon Musk cofounded seven companies, including electric car maker Tesla, rocket producer SpaceX and artificial intelligence startup xAI.\n",
"---\n",
"Title: Elon Musk changes his name to Kekius Maximus on X - BBC\n",
"Link: https://www.bbc.com/news/articles/cy53vz1qpx1o\n",
"Snippet: The world's richest man sparks speculation after changing his name and using a picture of Pepe the Frog.\n",
"---\n",
"Title: Elon Musk | Tesla\n",
"Link: https://www.tesla.com/elon-musk\n",
"Snippet: As the co-founder and CEO of Tesla, Elon leads all product design, engineering and global manufacturing of the company's electric vehicles, battery products ...\n",
"---\n",
"Title: Elon Musk - WSJ Spotlight Coverage, Recent News\n",
"Link: https://www.wsj.com/topics/person/elon-musk\n",
"Snippet: Elon Musk is the CEO of electric-vehicle maker Tesla, whose board he joined in 2004. During his two decades there, the company has grown to be the global ...\n",
"---\n",
"Title: Elon Musk | Biography, SpaceX, Tesla, Twitter, X, Trump, & Facts\n",
"Link: https://www.britannica.com/money/Elon-Musk\n",
"Snippet: Elon Musk, the South African-born entrepreneur widely known as the founder of SpaceX and Tesla, is renowned for his advances in electric vehicles and space ...\n",
"---\n",
"Title: How did Elon Musk become the most powerful man in Washington?\n",
"Link: https://www.youtube.com/watch?v=8-iFXyLQQsg\n",
"Snippet: Elon Musk, the world's richest man, is also the most powerful man in Washington right now. The panel discusses how this came to be and if ...\n",
"---\n",
"\n",
"\n",
"\n",
"You ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n",
"\n",
"Tool Name: DocumentSearchTool\n",
"Tool Arguments: {'query': {'description': 'Query to search the document.', 'type': 'str'}}\n",
"Tool Description: Search the document for the given query.\n",
"Tool Name: Search the internet\n",
"Tool Arguments: {'search_query': {'description': 'Mandatory search query you want to use to search the internet', 'type': 'str'}}\n",
"Tool Description: A tool that can be used to search the internet with a search_query.\n",
"\n",
"Use the following format:\n",
"\n",
"Thought: you should always think about what to do\n",
"Action: the action to take, only one name of [DocumentSearchTool, Search the internet], just the name, exactly as it's written.\n",
"Action Input: the input to the action, just a simple python dictionary, enclosed in curly braces, using \" to wrap keys and values.\n",
"Observation: the result of the action\n",
"\n",
"Once all necessary information is gathered:\n",
"\n",
"Thought: I now know the final answer\n",
"Final Answer: the final answer to the original input question\n",
"\u001b[00m\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/akshaypachaar/miniconda3/envs/env_crewai/lib/python3.10/site-packages/crewai/tools/tool_usage.py:162: PydanticDeprecatedSince20: The `schema` method is deprecated; use `model_json_schema` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n",
" acceptable_args = tool.args_schema.schema()[\"properties\"].keys() # type: ignore # Item \"None\" of \"type[BaseModel] | None\" has no attribute \"schema\"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Thought:\u001b[00m \u001b[92mThought: I have found multiple relevant sources from the web about Elon Musk. I will now extract the complete content from the best source.\u001b[00m\n",
"\u001b[95m## Using tool:\u001b[00m \u001b[92mSearch the internet\u001b[00m\n",
"\u001b[95m## Tool Input:\u001b[00m \u001b[92m\n",
"\"{\\\"search_query\\\": \\\"Elon Musk biography\\\"}\"\u001b[00m\n",
"\u001b[95m## Tool Output:\u001b[00m \u001b[92m\n",
"\n",
"Search results: Title: Elon Musk - Wikipedia\n",
"Link: https://en.wikipedia.org/wiki/Elon_Musk\n",
"Snippet: Elon Reeve Musk is a businessman known for his key roles in the space company SpaceX and the automotive company Tesla, Inc. He is also known for his ...\n",
"---\n",
"Title: Elon Musk: Biography, Entrepreneur, SpaceX and Tesla Founder\n",
"Link: https://www.biography.com/business-leaders/elon-musk\n",
"Snippet: Elon Reeve Musk was born on June 28, 1971, in Pretoria, South Africa. His mother, Maye Musk, is a Canadian model and the oldest woman to star in ...\n",
"---\n",
"Title: Elon Musk: Isaacson, Walter: 9781982181284 - Amazon.com\n",
"Link: https://www.amazon.com/Elon-Musk-Walter-Isaacson/dp/1982181281\n",
"Snippet: The #1 New York Times bestseller from the author of Steve Jobs--this is the astonishingly intimate story of the most fascinating and controversial innovator of our era--a rule-breaking visionary who helped to lead the world into the era of electric vehicles, private space exploration, and artificial intelligence.\n",
"---\n",
"Title: Elon Musk | Book by Walter Isaacson | Official Publisher Page\n",
"Link: https://www.simonandschuster.com/books/Elon-Musk/Walter-Isaacson/9781982181284\n",
"Snippet: The #1 New York Times bestseller from the author of Steve Jobs--this is the astonishingly intimate story of the most fascinating and controversial innovator of our era--a rule-breaking visionary who helped to lead the world into the era of electric vehicles, private space exploration, and artificial intelligence.\n",
"---\n",
"Title: Elon Musk | Biography, SpaceX, Tesla, Twitter, X, Trump, & Facts\n",
"Link: https://www.britannica.com/money/Elon-Musk\n",
"Snippet: South African-born American entrepreneur who cofounded the electronic-payment firm PayPal and formed SpaceX, maker of launch vehicles and spacecraft.\n",
"---\n",
"Title: Book Review: 'Elon Musk,' by Walter Isaacson - The New York Times\n",
"Link: https://www.nytimes.com/2023/09/09/books/review/elon-musk-walter-isaacson.html\n",
"Snippet: Walter Isaacson's biography of the billionaire entrepreneur depicts a mercurial “man-child” with grandiose ambitions and an ego to match.\n",
"---\n",
"Title: Who is Elon Musk and what is his net worth? - BBC\n",
"Link: https://www.bbc.com/news/business-61234231\n",
"Snippet: Born in Pretoria, South Africa, Mr Musk showed his talents for entrepreneurship early, going door-to-door with his brother selling homemade ...\n",
"---\n",
"Title: Elon Musk: Tesla, SpaceX, and the Quest for a Fantastic Future\n",
"Link: https://www.amazon.com/Elon-Musk-SpaceX-Fantastic-Future/dp/0062301233\n",
"Snippet: In the spirit of Steve Jobs and Moneyball, Elon Musk is both an illuminating and authorized look at the extraordinary life of one of Silicon Valley's most exciting, unpredictable, and ambitious entrepreneurs--a real-life Tony Stark--and a fascinating exploration of the renewal of American invention and its new ``makers.''\n",
"---\n",
"Title: Elon Musk's Biography Summary - Dante\n",
"Link: https://dantekim.com/notes/elon-musk-tesla-spacex-and-the-quest-for-a-fantastic-future/\n",
"Snippet: In his biography you get to dig much deeper and see his flaws, extreme perseverance and genius in such close detail.\n",
"---\n",
"Title: Elon Musk By Walter Isaacson: A Biography Book On Hardcore Hope\n",
"Link: https://www.youtube.com/watch?v=srlhWHvRVuQ\n",
"Snippet: Elon Musk: the name synonymous with Tesla, SpaceX, and audacious dreams of colonizing Mars. But what's the man behind the ambition?\n",
"---\n",
"\u001b[00m\n",
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mRetrieve relevant information to answer the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Final Answer:\u001b[00m \u001b[92m\n",
"**Elon Reeve Musk** (born June 28, 1971) is a South African-born American entrepreneur and business magnate. He is the founder, CEO, and chief engineer of SpaceX; early investor, CEO, and product architect of Tesla, Inc.; founder of The Boring Company; co-founder of Neuralink; and co-founder and initial co-chairman of OpenAI. Musk is known for his ambitious vision for the future, including the colonization of Mars and the advent of sustainable energy. \n",
"\n",
"Born in Pretoria, South Africa, Musk showed an early interest in computing and technology. He taught himself computer programming at a young age and created a video game, which he sold at the age of 12. After moving to the U.S. to study at the University of Pennsylvania, Musk dropped out to pursue his entrepreneurial ambitions, co-founding Zip2, X.com, which later became PayPal, and then aiming to revolutionize transportation with his work on electric vehicles through Tesla.\n",
"\n",
"Musk established SpaceX in 2002 to reduce space transportation costs and enable the colonization of Mars. He made significant advancements with the Falcon rocket series and the Dragon spacecraft. With Tesla, Musk aims to accelerate the world's transition to sustainable energy, to help combat climate change.\n",
"\n",
"Musk has been recognized for his contributions and endeavors in the technology industry and has been listed among the most powerful and influential people in the world.\n",
"\n",
"For a more detailed exploration of his life, contributions, and impact, you can read further on his [Wikipedia page](https://en.wikipedia.org/wiki/Elon_Musk).\u001b[00m\n",
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mResponse synthesizer agent for the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Task:\u001b[00m \u001b[92mSynthesize the final response for the user query: Who is elon musk?\n",
"\u001b[00m\n",
"\n",
"\n",
"\u001b[1m\u001b[95m# Agent:\u001b[00m \u001b[1m\u001b[92mResponse synthesizer agent for the user query: Who is elon musk?\u001b[00m\n",
"\u001b[95m## Final Answer:\u001b[00m \u001b[92m\n",
"**Elon Reeve Musk** (born June 28, 1971) is a South African-born American entrepreneur and business magnate, renowned for his groundbreaking contributions in various technology sectors. He is the founder, CEO, and chief engineer of SpaceX; an early investor, CEO, and product architect of Tesla, Inc.; the founder of The Boring Company; and a co-founder of Neuralink and OpenAI. Musk is celebrated for his ambitious vision for the future, which includes plans for the colonization of Mars and the promotion of sustainable energy.\n",
"\n",
"Born in Pretoria, South Africa, Musk displayed a keen interest in computing from a young age, teaching himself programming and creating a video game that he sold by the age of 12. He moved to the United States to study at the University of Pennsylvania but left before completing his degree to pursue entrepreneurial ventures, co-founding Zip2 and later X.com, which evolved into PayPal. His ambitions continued with Tesla, where he aims to revolutionize transportation through electric vehicles and combat climate change.\n",
"\n",
"Musk started SpaceX in 2002 with the goal of reducing space transportation costs and enabling the colonization of Mars, achieving significant milestones with the Falcon rocket series and the Dragon spacecraft. Recognized globally, Musk has been cited as one of the most powerful and influential people in the world due to his innovative works and contributions to technology. For further details about his life and impact, refer to his [Wikipedia page](https://en.wikipedia.org/wiki/Elon_Musk).\u001b[00m\n",
"\n",
"\n",
"**Elon Reeve Musk** (born June 28, 1971) is a South African-born American entrepreneur and business magnate, renowned for his groundbreaking contributions in various technology sectors. He is the founder, CEO, and chief engineer of SpaceX; an early investor, CEO, and product architect of Tesla, Inc.; the founder of The Boring Company; and a co-founder of Neuralink and OpenAI. Musk is celebrated for his ambitious vision for the future, which includes plans for the colonization of Mars and the promotion of sustainable energy.\n",
"\n",
"Born in Pretoria, South Africa, Musk displayed a keen interest in computing from a young age, teaching himself programming and creating a video game that he sold by the age of 12. He moved to the United States to study at the University of Pennsylvania but left before completing his degree to pursue entrepreneurial ventures, co-founding Zip2 and later X.com, which evolved into PayPal. His ambitions continued with Tesla, where he aims to revolutionize transportation through electric vehicles and combat climate change.\n",
"\n",
"Musk started SpaceX in 2002 with the goal of reducing space transportation costs and enabling the colonization of Mars, achieving significant milestones with the Falcon rocket series and the Dragon spacecraft. Recognized globally, Musk has been cited as one of the most powerful and influential people in the world due to his innovative works and contributions to technology. For further details about his life and impact, refer to his [Wikipedia page](https://en.wikipedia.org/wiki/Elon_Musk).\n"
]
}
],
"source": [
"result = crew.kickoff(inputs={\"query\": \"Who is elon musk?\"})\n",
"print(result)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "env_crewai",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.15"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,24 @@
# routing_agent:
# role: >
# Understand the user query: {query}, and route it to the most appropriate knowledge base
# goal: >
# Route user queries to the most appropriate knowledge base
# backstory: >
# You are a user query routing agent. You are knowln for your ability to carefully understand user queries and route them to the most appropriate knowledge base.
retriever_agent:
role: >
Retrieve relevant information to answer the user query: {query}
goal: >
Retrieve the most relevant information from the available sources for the user query: {query}, always try to use the pdf search tool first. If you are not able to retrieve the information from the pdf search tool then try to use the web search tool.
backstory: >
You're a meticulous analyst with a keen eye for detail. You're known for
your ability understand the user query: {query} and retrieve knowlege from the most suitable knowledge base.
response_synthesizer_agent:
role: >
Response synthesizer agent for the user query: {query}
goal: >
Synthesize the retrieved information into a concise and coherent response based on the user query: {query}. If you are not ble to retrieve the information then respond with "I'm sorry, I couldn't find the information you're looking for."
backstory: >
You're a skilled communicator with a knack for turning complex information into clear and concise responses.
@@ -0,0 +1,20 @@
# routing_task:
# description: >
# Understand the user query: {query}, and route it to the most appropriate knowledge base
# expected_output: >
# The most appropriate knowledge base to route the user query to, the answer should be either 'pdf_search' or 'pdf_search'
# agent: routing_agent
retrieval_task:
description: >
Retrieve the most relevant information from the available sources for the user query: {query}
expected_output: >
The most relevant information in form of text as retrieved from the sources.
agent: retriever_agent
response_task:
description: >
Synthesize the final response for the user query: {query}
expected_output: >
A concise and coherent response based on the retrieved infromation from the right source for the user query: {query}. If you are not ble to retrieve the information then respond with "I'm sorry, I couldn't find the information you're looking for."
agent: response_synthesizer_agent
@@ -0,0 +1,76 @@
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
from crewai_tools import SerperDevTool
from crewai_tools import PDFSearchTool
# from tools.custom_tool import DocumentSearchTool
from agentic_rag.tools.custom_tool import DocumentSearchTool
# Initialize the tool with a specific PDF path for exclusive search within that document
pdf_tool = DocumentSearchTool(pdf='/Users/akshaypachaar/Eigen/ai-engineering/agentic_rag/knowledge/dspy.pdf')
web_search_tool = SerperDevTool()
@CrewBase
class AgenticRag():
"""AgenticRag crew"""
agents_config = 'config/agents.yaml'
tasks_config = 'config/tasks.yaml'
# If you would like to add tools to your agents, you can learn more about it here:
# https://docs.crewai.com/concepts/agents#agent-tools
# @agent
# def routing_agent(self) -> Agent:
# return Agent(
# config=self.agents_config['routing_agent'],
# verbose=True
# )
@agent
def retriever_agent(self) -> Agent:
return Agent(
config=self.agents_config['retriever_agent'],
verbose=True,
tools=[
pdf_tool,
web_search_tool
]
)
@agent
def response_synthesizer_agent(self) -> Agent:
return Agent(
config=self.agents_config['response_synthesizer_agent'],
verbose=True
)
# @task
# def routing_task(self) -> Task:
# return Task(
# config=self.tasks_config['routing_task'],
# )
@task
def retrieval_task(self) -> Task:
return Task(
config=self.tasks_config['retrieval_task'],
)
@task
def response_task(self) -> Task:
return Task(
config=self.tasks_config['response_task'],
)
@crew
def crew(self) -> Crew:
"""Creates the AgenticRag crew"""
# To learn how to add knowledge sources to your crew, check out the documentation:
# https://docs.crewai.com/concepts/knowledge#what-is-knowledge
return Crew(
agents=self.agents, # Automatically created by the @agent decorator
tasks=self.tasks, # Automatically created by the @task decorator
process=Process.sequential,
verbose=True,
# process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/
)
@@ -0,0 +1,58 @@
#!/usr/bin/env python
import sys
import warnings
from agentic_rag.crew import AgenticRag
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
# This main file is intended to be a way for you to run your
# crew locally, so refrain from adding unnecessary logic into this file.
# Replace with inputs you want to test with, it will automatically
# interpolate any tasks and agents information
def run():
"""
Run the crew.
"""
inputs = {
'query': 'Who is elon musk?'
}
AgenticRag().crew().kickoff(inputs=inputs)
def train():
"""
Train the crew for a given number of iterations.
"""
inputs = {
"topic": "AI LLMs"
}
try:
AgenticRag().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs)
except Exception as e:
raise Exception(f"An error occurred while training the crew: {e}")
def replay():
"""
Replay the crew execution from a specific task.
"""
try:
AgenticRag().crew().replay(task_id=sys.argv[1])
except Exception as e:
raise Exception(f"An error occurred while replaying the crew: {e}")
def test():
"""
Test the crew execution and returns the results.
"""
inputs = {
"topic": "AI LLMs"
}
try:
AgenticRag().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs)
except Exception as e:
raise Exception(f"An error occurred while replaying the crew: {e}")
@@ -0,0 +1,92 @@
import os
from crewai.tools import BaseTool
from typing import Type
from pydantic import BaseModel, Field, ConfigDict
from groundx import Document, GroundX
from dotenv import load_dotenv
load_dotenv()
class DocumentSearchToolInput(BaseModel):
"""Input schema for DocumentSearchTool."""
query: str = Field(..., description="Query to search the document.")
class DocumentSearchTool(BaseTool):
name: str = "DocumentSearchTool"
description: str = "Search the document for the given query."
args_schema: Type[BaseModel] = DocumentSearchToolInput
model_config = ConfigDict(extra="allow")
def __init__(self, file_path: str):
"""Initialize the searcher with a PDF file path and set up the Qdrant collection."""
super().__init__()
self.file_path = file_path
self.client = GroundX(
api_key=os.getenv("GROUNDX_API_KEY")
)
self.bucket_id = self._create_bucket()
self.process_id = self._upload_document()
def _upload_document(self):
ingest = self.client.ingest(
documents=[
Document(
bucket_id=self.bucket_id,
file_name=os.path.basename(self.file_path),
file_path=self.file_path,
file_type="pdf",
search_data=dict(
key = "value",
),
)
]
)
return ingest.ingest.process_id
def _create_bucket(self):
response = self.client.buckets.create(
name="agentic_rag"
)
return response.bucket.bucket_id
def _run(self, query: str) -> str:
# Check processing status
status_response = self.client.documents.get_processing_status_by_id(
process_id=self.process_id
)
# Check if processing is complete
if status_response.ingest.status != "complete":
return "Document is still being processed..."
# If processing is complete, proceed with search
search_response = self.client.search.content(
id=self.bucket_id,
query=query,
n=10,
verbosity=2
)
# Format the results with separators
formatted_results = ""
for result in search_response.search.results:
formatted_results += f"{result.text}\n____\n"
return formatted_results.rstrip('____\n')
# Test the implementation
def test_document_searcher():
# Test file path
pdf_path = "/Users/akshay/Eigen/ai-engineering-hub/agentic_rag_deepseek/knowledge/dspy.pdf"
# Create instance
searcher = DocumentSearchTool(file_path=pdf_path)
# Test search
result = searcher._run("What is the purpose of DSpy?")
print("Search Results:", result)
if __name__ == "__main__":
test_document_searcher()
+6
View File
@@ -0,0 +1,6 @@
ZEP_API_KEY="z_1d..."
ANAM_API_KEY="NDd..."
ZEP_DOCS_USER_ID=zep-sample-kg-user
ANAM_AVATAR_ID="30fa96d0-26c4-4e55-94a0-517025942e18"
ANAM_VOICE_ID="6bfbe25a-979d-40f3-a92b-5394170af54b"
OPENROUTER_API_KEY="sk-or-v1-..."
+45
View File
@@ -0,0 +1,45 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual Environment
venv/
env/
ENV/
# Environment Variables
.env
.env.local
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Logs
*.log
logs/
# OS
.DS_Store
Thumbs.db
+1
View File
@@ -0,0 +1 @@
3.12
+72
View File
@@ -0,0 +1,72 @@
# AI Avatar Demo powered by Zep
A conversational AI assistant powered by [Zep](https://www.getzep.com/) knowledge graphs, custom LLM integration, and Anam AI avatar—creating natural conversations with memory and context-aware responses.
We use:
- [Zep](https://www.getzep.com/) for conversation memory and knowledge graph management
- Anam AI for realistic avatar and voice interactions
- OpenRouter with Minimax M2 (LLM)
- FastAPI for streaming backend
- Streamlit to wrap the logic in an interactive UI
## Architecture
![Architecture Diagram](assets/architecture.gif)
## Set Up
Run these commands in project root
### Install Dependencies
```bash
uv sync
```
### Configure Environment
Create a `.env` file in the project root, similar to `.env.example`, and add your API keys:
[Get your Zep API keys here](https://www.getzep.com/)
### Ingest Data
To populate the knowledge graph with your data:
```bash
python scripts/ingest_to_graph.py
```
### Run the Application
Start the backend server:
```bash
uvicorn backend:app --port 8000 --reload
```
In a separate terminal, start the frontend:
```bash
streamlit run app.py
```
## Usage
1. Enter your session unique name in the sidebar
2. Click "Initialize New Session" to create a Zep session
3. Click "Start Conversation" to interact with the avatar
4. The assistant uses knowledge graph context and conversation history for personalized responses
Note: To understand Zep better, we recommend going through the Jupyter notebook (`zep_demo.ipynb`) provided in the project root.
## 📬 Stay Updated with Our Newsletter!
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
## Contribution
Contributions are welcome! Feel free to fork this repository and submit pull requests with your improvements.
+463
View File
@@ -0,0 +1,463 @@
import streamlit as st
import streamlit.components.v1 as components
import asyncio
# Your services
from services.zep_service import zep_service
from services.anam_service import anam_service
from config.settings import settings
# -------------------------------
# STREAMLIT UI CONFIG
# -------------------------------
st.set_page_config(
page_title="Zep AI Assistant",
layout="wide"
)
st.title("AI Consultant")
st.caption("Powered by Zep Knowledge Graph + Anam AI Avatar")
st.markdown("""
<div style="padding: 1rem 0; display: flex; gap: 2rem; align-items: center;">
<img src="https://www.getzep.com/cdn-cgi/image/width=256,format=auto/zep-logo-lockup-daisy-bush-rgb.svg" width="150" style="margin-bottom: 1rem;" onerror="this.style.display='none'">
<img src="https://anam.ai/favicon.ico" width="50" style="margin-bottom: 1rem;" onerror="this.style.display='none'">
</div>""", unsafe_allow_html=True)
# Custom CSS for cleaner UI
st.markdown("""
<style>
.main > div {
padding-top: 2rem;
}
.stButton button {
width: 100%;
}
</style>
""", unsafe_allow_html=True)
# -------------------------------
# Session Setup (Zep)
# -------------------------------
if "session_id" not in st.session_state:
st.session_state.session_id = None
if "user_id" not in st.session_state:
st.session_state.user_id = None
if "anam_session_token" not in st.session_state:
st.session_state.anam_session_token = None
# Sidebar for session management
with st.sidebar:
st.header("Session Management")
# User name input
user_name = st.text_input(
"Your Name", value="Demo User", key="user_name_input")
if st.button("Initialize New Session", type="primary"):
# Generate user_id from name
user_id = user_name.lower().replace(" ", "-")
session_id = f"session-{user_id}"
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
# Create Zep user
loop.run_until_complete(
zep_service.create_user(
user_id=user_id,
first_name=user_name.split()[0] if user_name else "Demo"
)
)
# Create Zep thread
loop.run_until_complete(
zep_service.create_thread(
thread_id=session_id,
user_id=user_id
)
)
# Clear old session data
st.session_state.user_id = user_id
st.session_state.session_id = session_id
st.session_state.anam_session_token = None
st.success(f"Session initialized for {user_name}!")
st.rerun()
except Exception as e:
st.error(f"Error initializing session: {e}")
# Display current session info
if st.session_state.session_id:
st.divider()
st.info(f"**Active User:** {st.session_state.user_id}")
st.caption(f"Session: {st.session_state.session_id}")
if st.button("Restart Anam Session", type="secondary"):
st.session_state.anam_session_token = None
st.success("Anam session ended!")
st.rerun()
st.caption("End the session when done to save costs")
# -------------------------------
# MAIN: ANAM AVATAR INTERFACE
# -------------------------------
if st.session_state.session_id:
if st.session_state.anam_session_token is None:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
session_data = loop.run_until_complete(
anam_service.create_session_token(
persona_name="Zep Assistant",
system_prompt="You are a helpful AI assistant with access to relevant knowledge to assist the user.",
avatar_id=settings.anam_avatar_id,
voice_id=settings.anam_voice_id,
llm_id="CUSTOMER_CLIENT_V1" # Explicitly set custom LLM mode
)
)
if not session_data or "sessionToken" not in session_data:
st.error("Failed to create Anam session token.")
st.stop()
st.session_state.anam_session_token = session_data["sessionToken"]
st.success("Anam session created with custom LLM!")
session_token = st.session_state.anam_session_token
# Center the avatar
# Full HTML block for Anam avatar
# NOTE: Calls FastAPI backend at http://localhost:8000
anam_html = f"""
<!DOCTYPE html>
<html>
<head>
<style>
body {{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
}}
#persona-video {{
width: 100%;
max-width: 600px;
border-radius: 12px;
background:black;
margin: 0 auto;
}}
#status {{
text-align:center;
margin-top:10px;
font-size:12px;
color:#666;
font-weight: 500;
}}
.controls {{
text-align: center;
margin-top: 15px;
}}
button {{
padding: 8px 20px;
margin: 0 5px;
border-radius: 6px;
border: none;
cursor: pointer;
font-size: 14px;
}}
.start-btn {{
background: #4CAF50;
color: white;
}}
.stop-btn {{
background: #f44336;
color: white;
}}
.end-btn {{
background: #ff9800;
color: white;
}}
</style>
</head>
<body>
<video id="persona-video" autoplay playsinline></video>
<div id="status">Initializing…</div>
<div class="controls">
<button class="start-btn" id="start-btn" onclick="startConversation()">Start Conversation</button>
<button class="stop-btn" id="stop-btn" onclick="stopConversation()" style="display:none;">Stop Conversation</button>
<button class="end-btn" id="end-btn" onclick="endSession()">End Session</button>
</div>
<script type="module">
import {{ createClient }} from "https://esm.sh/@anam-ai/js-sdk@latest";
import {{ AnamEvent }} from "https://esm.sh/@anam-ai/js-sdk@latest/dist/module/types";
const sessionToken = "{session_token}";
const sessionId = "{st.session_state.session_id}";
const statusEl = document.getElementById("status");
const startBtn = document.getElementById("start-btn");
const stopBtn = document.getElementById("stop-btn");
const endBtn = document.getElementById("end-btn");
let anamClient = null;
// ---- Custom LLM Response Handler ----
async function handleUserMessage(messageHistory) {{
console.log('Message history updated:', messageHistory);
// Only respond to user messages
if (messageHistory.length === 0) {{
console.log('Empty message history, skipping');
return;
}}
const lastMessage = messageHistory[messageHistory.length - 1];
if (lastMessage.role !== 'user') {{
console.log('Last message not from user, skipping');
return;
}}
if (!anamClient) {{
console.error('Anam client not initialized');
return;
}}
try {{
console.log('Getting custom LLM response with Zep KG');
console.log('User message:', lastMessage.content);
// Convert Anam message format to standard format
const messages = messageHistory.map((msg) => ({{
role: msg.role === 'user' ? 'user' : 'assistant',
content: msg.content,
}}));
console.log('Sending request to backend:', `http://localhost:8000/llm/stream?session_id=${{sessionId}}`);
// Create a streaming talk session FIRST
const talkStream = anamClient.createTalkMessageStream();
console.log('Talk stream created, active:', talkStream.isActive());
// Give the stream a moment to initialize
await new Promise(resolve => setTimeout(resolve, 50));
// Call our FastAPI backend with Zep integration
const response = await fetch(
`http://localhost:8000/llm/stream?session_id=${{sessionId}}`,
{{
method: "POST",
headers: {{ "Content-Type": "application/json" }},
body: JSON.stringify({{ messages }}),
}}
);
console.log('Response received, status:', response.status);
console.log('Response headers:', Array.from(response.headers.entries()));
if (!response.ok) {{
const errorText = await response.text();
console.error('Backend error response:', errorText);
throw new Error(`Backend returned ${{response.status}}: ${{errorText}}`);
}}
// Verify we got the right content type
const contentType = response.headers.get('content-type');
console.log('Content-Type:', contentType);
if (!contentType || !contentType.includes('text/event-stream')) {{
console.warn('Expected text/event-stream but got:', contentType);
}}
const reader = response.body?.getReader();
if (!reader) {{
throw new Error('Failed to get response stream reader');
}}
const textDecoder = new TextDecoder();
console.log('Starting to stream LLM response to Anam persona...');
let chunkCount = 0;
let totalContent = '';
// Stream the response chunks to the persona
while (true) {{
const {{ done, value }} = await reader.read();
if (done) {{
console.log('LLM streaming complete');
console.log(`Received ${{chunkCount}} chunks, total length: ${{totalContent.length}}`);
if (talkStream.isActive()) {{
console.log('Ending talk stream');
talkStream.endMessage();
}}
break;
}}
if (value) {{
const text = textDecoder.decode(value, {{ stream: true }});
const lines = text.split('\\n').filter((line) => line.trim());
for (const line of lines) {{
if (line.startsWith('data: ')) {{
try {{
const jsonStr = line.slice(6);
const data = JSON.parse(jsonStr);
if (data.content) {{
chunkCount++;
totalContent += data.content;
if (talkStream.isActive()) {{
talkStream.streamMessageChunk(data.content, false);
if (chunkCount % 10 === 0) {{
console.log(`Streamed ${{chunkCount}} chunks so far...`);
}}
}} else {{
console.warn('Talk stream no longer active at chunk', chunkCount);
break;
}}
}}
}} catch (parseError) {{
console.warn('Failed to parse SSE data:', line, parseError);
}}
}}
}}
}}
}}
console.log('Final response:', totalContent.substring(0, 100) + '...');
}} catch (error) {{
console.error('Custom LLM error:', error);
console.error('Error details:', {{
message: error.message,
stack: error.stack,
name: error.name
}});
// Check if it's a network error (backend not running)
if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {{
console.error('BACKEND NOT REACHABLE - Is FastAPI running on port 8000?');
if (anamClient) {{
anamClient.talk(
"I cannot connect to the backend server. Please ensure the FastAPI server is running on port 8000."
);
}}
}} else if (anamClient) {{
anamClient.talk(
"I'm sorry, I encountered an error while processing your request. Please check the console for details."
);
}}
}}
}}
// ---- Startup ----
async function start() {{
try {{
statusEl.textContent = "Connecting…";
// Test backend connectivity
console.log('Testing backend connection...');
try {{
const healthCheck = await fetch('http://localhost:8000/health');
if (healthCheck.ok) {{
console.log('Backend is reachable');
}} else {{
console.warn('Backend health check failed:', healthCheck.status);
}}
}} catch (e) {{
console.error('Backend is NOT reachable. Make sure FastAPI is running on port 8000');
console.error('Run: uvicorn backend:app --reload');
}}
// Create Anam client
anamClient = createClient(sessionToken);
// Set up event listeners
anamClient.addListener(AnamEvent.SESSION_READY, () => {{
console.log('Anam session ready - Custom LLM with Zep KG active!');
statusEl.textContent = "Connected - Custom LLM active";
statusEl.style.color = "#22c55e";
startBtn.style.display = "inline-block";
stopBtn.style.display = "none";
endBtn.style.display = "inline-block";
}});
anamClient.addListener(AnamEvent.CONNECTION_CLOSED, () => {{
console.log('🔌 Connection closed');
statusEl.textContent = "Disconnected";
statusEl.style.color = "#dc3545";
}});
// This is the KEY event for custom LLM integration
anamClient.addListener(AnamEvent.MESSAGE_HISTORY_UPDATED, handleUserMessage);
// Handle stream interruptions
anamClient.addListener(AnamEvent.TALK_STREAM_INTERRUPTED, () => {{
console.log('Talk stream interrupted by user');
}});
// Start streaming to video element
await anamClient.streamToVideoElement("persona-video");
console.log('Custom LLM persona with Zep KG started successfully!');
}} catch (error) {{
console.error('Failed to start conversation:', error);
statusEl.textContent = `Error: ${{error.message}}`;
statusEl.style.color = "#dc3545";
}}
}}
// Button handlers
window.startConversation = function() {{
if (anamClient) {{
anamClient.talk("Hello! How can I help you today?");
startBtn.style.display = "none";
stopBtn.style.display = "inline-block";
statusEl.textContent = "Listening...";
}}
}};
window.stopConversation = function() {{
if (anamClient) {{
anamClient.stopStreaming();
startBtn.style.display = "inline-block";
stopBtn.style.display = "none";
statusEl.textContent = "Connected - Custom LLM active";
}}
}};
window.endSession = function() {{
if (anamClient) {{
anamClient.stopStreaming();
statusEl.textContent = "Session ended.";
statusEl.style.color = "#ff9800";
startBtn.style.display = "none";
stopBtn.style.display = "none";
endBtn.style.display = "none";
}}
}};
start();
</script>
</body>
</html>
"""
# Render the avatar
components.html(anam_html, height=650, scrolling=False)
else:
st.info("Please initialize a session from the sidebar to start.")
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

+279
View File
@@ -0,0 +1,279 @@
import json
import time
from typing import List, Optional, Dict, Any
from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from services.zep_service import zep_service
from services.llm_service import llm_service
from config.settings import settings
# -------------------------------
# Pydantic Models
# -------------------------------
class Message(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
messages: List[Message]
class SessionCreateRequest(BaseModel):
user_id: str
first_name: Optional[str] = "Demo"
last_name: Optional[str] = None
email: Optional[str] = None
# -------------------------------
# FastAPI App
# -------------------------------
app = FastAPI(
title="Zep + LLM Backend for Anam",
description="FastAPI backend that integrates Zep Cloud KG + your LLM for Anam avatar.",
version="1.0.0",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allow all origins for local development
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["*"],
)
# -------------------------------
# Health & Debug Endpoints
# -------------------------------
@app.get("/health")
async def health_check():
return {"status": "ok"}
@app.get("/zep/test-graph")
async def test_graph(q: str = Query("what is Zep?")):
"""
Simple debug endpoint to verify Zep graph connectivity.
"""
try:
nodes = await zep_service.search_graph(
query=q,
user_id=settings.zep_docs_user_id,
limit=3,
scope="nodes",
)
return {"query": q, "results": nodes}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# -------------------------------
# Zep Session Helper Endpoint
# -------------------------------
@app.post("/zep/session")
async def create_zep_session(body: SessionCreateRequest):
"""
Helper endpoint to create a Zep user + thread (session) for a given user_id.
You can call this from Streamlit or any client instead of doing it inline.
"""
try:
user = await zep_service.create_user(
user_id=body.user_id,
first_name=body.first_name,
last_name=body.last_name,
email=body.email,
)
# 2) Create thread for this user
session_id = f"session-{body.user_id}"
thread = await zep_service.create_thread(
thread_id=session_id,
user_id=body.user_id,
)
return {
"user": user,
"thread": thread,
"session_id": session_id,
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# -------------------------------
# Core: LLM Stream Endpoint for Anam
# -------------------------------
@app.post("/llm/stream")
async def llm_stream(
payload: ChatRequest,
session_id: str = Query(..., description="Zep thread/session ID"),
):
"""
Streaming LLM endpoint with Zep KG integration.
This is what Anam's customLLMHandler() should call.
- Expects: JSON body { "messages": [ { "role": "...", "content": "..." }, ... ] }
- Requires: ?session_id=... query param (thread_id in Zep)
- Returns: SSE stream with chunks as { "text": "<token or chunk>" }
"""
messages: List[Message] = payload.messages
if not messages:
raise HTTPException(status_code=400, detail="No messages provided")
# Extract latest user message
user_message: Optional[str] = None
for m in reversed(messages):
if m.role == "user":
user_message = m.content
break
if not user_message:
raise HTTPException(status_code=400, detail="No user message found")
async def event_generator():
"""
Async generator that:
1) Saves the user message to Zep
2) Queries the Zep Knowledge Graph for docs context
3) Gets conversation context from Zep
4) Calls your LLM in streaming mode
5) Yields SSE 'data: { "text": ... }' lines to the client
6) Saves the final assistant message back into Zep
"""
full_response: str = ""
try:
print(f"\n{'='*60}")
print(f"NEW USER MESSAGE for session: {session_id}")
print(f"Message: {user_message}")
print(f"{'='*60}")
# 1) Save user message into Zep thread
await zep_service.add_message(
thread_id=session_id,
role="user",
content=user_message,
)
print("User message saved to Zep thread")
# 2) Build base system prompt
system_prompt = """
You are a helpful AI assistant. When provided with relevant information or context below,
use it to answer the user's question accurately and completely. Always use the information
provided to give thorough, detailed answers. Be conversational and friendly while being informative.
If the context contains the answer, state it clearly and add relevant details from the context.
""".strip()
# 3) Zep Knowledge Graph lookup
print(f"\nQUERYING KNOWLEDGE GRAPH...")
print(f" Query: '{user_message}'")
print(f" User ID: {settings.zep_docs_user_id}")
graph_start_time = time.time()
graph_results = await zep_service.search_graph(
query=user_message,
user_id=settings.zep_docs_user_id,
limit=3,
scope="nodes",
)
graph_end_time = time.time()
# Convert to milliseconds
graph_duration = (graph_end_time - graph_start_time) * 1000
print(
f"\nKNOWLEDGE GRAPH RESULTS: {len(graph_results)} nodes found")
print(f"Fetch time: {graph_duration:.2f}ms")
if graph_results:
print(f"\nRetrieved nodes:")
for i, node in enumerate(graph_results, 1):
print(f" {i}. {node.get('name', 'N/A')}")
summary_preview = node.get('summary', '')[:100]
print(f" Summary: {summary_preview}...")
docs_context = "\n\n".join(
[f"- {node['name']}: {node['summary']}" for node in graph_results]
)
system_prompt += f"\n\n=== RELEVANT INFORMATION ===\n{docs_context}\n\nUse the above information to answer the user's question."
print(
f"\nGRAPH CONTEXT ADDED TO PROMPT: {len(docs_context)} characters")
else:
print(
f"\nNO GRAPH RESULTS FOUND - LLM will respond without context")
# 4) Conversation context from this thread
print(f"\nFETCHING USER CONTEXT for thread: {session_id}")
user_context = await zep_service.get_user_context(thread_id=session_id)
if user_context:
system_prompt += f"\n\n=== CONVERSATION CONTEXT ===\n{user_context}"
print(f"USER CONTEXT ADDED: {len(user_context)} characters")
else:
print(f"No user context available yet (new conversation)")
# 5) Reformat messages for LLM
formatted_messages: List[Dict[str, Any]] = [
{"role": m.role, "content": m.content} for m in messages
]
# 6) Stream from LLM and send SSE chunks
print(f"\nSTREAMING LLM RESPONSE...")
print(f" Total prompt size: {len(system_prompt)} characters")
chunk_count = 0
async for chunk in llm_service.stream_chat_completion(
messages=formatted_messages,
system_prompt=system_prompt,
):
if not chunk:
continue
chunk_count += 1
full_response += chunk
payload = json.dumps({"content": chunk})
yield f"data: {payload}\n\n"
if chunk_count % 10 == 0:
print(f" Streamed {chunk_count} chunks so far...")
print(f" Total chunks streamed: {chunk_count}")
# 7) Save assistant message in Zep
if full_response.strip():
await zep_service.add_message(
thread_id=session_id,
role="assistant",
content=full_response,
)
print(
f"\nASSISTANT RESPONSE SAVED: {len(full_response)} characters")
print(f"{'='*60}\n")
except Exception as e:
# On error, stream an error message as SSE
err_payload = json.dumps({"content": "Error: " + str(e)})
yield f"data: {err_payload}\n\n"
# Return a streaming SSE response
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
},
)
+4
View File
@@ -0,0 +1,4 @@
"""Configuration package."""
from .settings import settings
__all__ = ["settings"]
+28
View File
@@ -0,0 +1,28 @@
"""Configuration settings for the Zep Demo application."""
from pydantic_settings import BaseSettings
from typing import Optional
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
# API Keys
anam_api_key: str
zep_api_key: str
openrouter_api_key: str
# Anam AI Configuration (for avatar/voice only - we use custom LLM)
anam_api_base_url: str = "https://api.anam.ai"
anam_avatar_id: str = "30fa96d0-26c4-4e55-94a0-517025942e18" # Default
anam_voice_id: str = "6bfbe25a-979d-40f3-a92b-5394170af54b" # Default
# Zep Config
zep_docs_user_id: str
class Config:
env_file = ".env"
case_sensitive = False
# Global settings instance
settings = Settings()
+21
View File
@@ -0,0 +1,21 @@
# Zep Documentation Data Directory
This directory contains the (sample) data used for the Zep knowledge graph.
## Files
- **chunked-docs.json** - Sample chunked data provided for ingestion into the Zep knowledge graph.
## Using Your Own Data
If you want to use your own data, you must generate a `chunked-docs.json` file with a similar structure to the provided sample. Ensure the file is formatted correctly for ingestion.
## Ingesting the Data
To ingest the `chunked-docs.json` file into the Zep knowledge graph:
```bash
python scripts/ingest_to_graph.py
```
After running the script, make sure to check the Zep dashboard and wait a few minutes for the data to be fully processed and available for use.

Some files were not shown because too many files have changed in this diff Show More