chore: import upstream snapshot with attribution
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
@@ -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)
|
||||
|
||||
[](https://join.dailydoseofds.com)
|
||||
|
||||
---
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
|
||||
@@ -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:
|
||||
|
||||

|
||||
|
||||
---
|
||||
## 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)
|
||||
|
||||
[](https://join.dailydoseofds.com)
|
||||
|
||||
---
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
|
||||
@@ -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})
|
||||
|
After Width: | Height: | Size: 8.0 KiB |
@@ -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
|
||||
@@ -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)
|
||||
|
||||
[](https://join.dailydoseofds.com)
|
||||
|
||||
---
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
[](https://join.dailydoseofds.com)
|
||||
|
||||
---
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
|
||||
@@ -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)")
|
||||
|
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)
|
||||
|
||||
[](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"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
@@ -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)
|
||||
|
||||
[](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!** 🎉
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`patchy631/ai-engineering-hub`
|
||||
- 原始仓库:https://github.com/patchy631/ai-engineering-hub
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
@@ -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)
|
||||
|
||||
[](https://join.dailydoseofds.com)
|
||||
|
||||
---
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
|
||||
@@ -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")
|
||||
|
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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
[](https://join.dailydoseofds.com)
|
||||
|
||||
---
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
|
||||
@@ -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")
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
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}")
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
|
||||
[](https://join.dailydoseofds.com)
|
||||
|
||||
---
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
[](https://join.dailydoseofds.com)
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions are welcome! Feel free to fork this repository and submit pull requests with your improvements.
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 445 KiB |
|
After Width: | Height: | Size: 500 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
@@ -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",
|
||||
]
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
.env
|
||||
__pycache__/
|
||||
@@ -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
|
||||
[](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)
|
||||
|
||||
[](https://join.dailydoseofds.com)
|
||||
|
||||
---
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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})
|
||||
@@ -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})
|
||||
@@ -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})
|
||||
|
After Width: | Height: | Size: 8.0 KiB |
@@ -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
|
||||
}
|
||||
@@ -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, Russia’s 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",
|
||||
"It’s 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,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()
|
||||
|
After Width: | Height: | Size: 755 KiB |
|
After Width: | Height: | Size: 755 KiB |
@@ -0,0 +1,2 @@
|
||||
GROUNDX_API_KEY=your_groundx_api_key
|
||||
SERPER_API_KEY=your_serper_api_key
|
||||
@@ -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
|
||||
[](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)
|
||||
|
||||
[](https://join.dailydoseofds.com)
|
||||
|
||||
---
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
|
||||
@@ -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})
|
||||
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 770 KiB |
@@ -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, Russia’s 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",
|
||||
"It’s 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()
|
||||
@@ -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-..."
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
3.12
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
## 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)
|
||||
|
||||
[](https://join.dailydoseofds.com)
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions are welcome! Feel free to fork this repository and submit pull requests with your improvements.
|
||||
@@ -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.")
|
||||
|
After Width: | Height: | Size: 2.2 MiB |
@@ -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",
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
"""Configuration package."""
|
||||
from .settings import settings
|
||||
|
||||
__all__ = ["settings"]
|
||||
@@ -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()
|
||||
@@ -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.
|
||||