Spaces:
Running
Running
import gradio as gr | |
from langchain_groq import ChatGroq | |
import os | |
from langgraph.graph import StateGraph, START, END | |
from langchain_text_splitters import RecursiveCharacterTextSplitter | |
from langchain_chroma import Chroma | |
from typing import Annotated | |
from typing_extensions import TypedDict | |
from pydantic import BaseModel, Field | |
from langchain_core.messages import HumanMessage | |
import time | |
import os | |
from langchain_community.document_loaders import PyPDFLoader | |
from langchain_huggingface import HuggingFaceEmbeddings | |
class State(TypedDict): | |
query: str | |
is_safe: bool | |
is_relevant: bool | |
company_description: str | |
answer: str | |
vectorstoredb: Chroma | |
class checker_class(BaseModel): | |
is_relevant: bool = Field(description="Check whether the given query is relevant to the company.") | |
def invoke_llm(query): | |
llm = ChatGroq(model='llama-3.3-70b-versatile') | |
try: | |
res = llm.invoke(query) | |
except: | |
time.sleep(60) | |
res = llm.invoke(query) | |
return res.content | |
def invoke_relevance_checker_llm(query): | |
llm = ChatGroq(model='llama-3.3-70b-versatile') | |
checker_llm = llm.with_structured_output(checker_class) | |
try: | |
res = checker_llm.invoke([HumanMessage(content=query)]) | |
except: | |
time.sleep(60) | |
res = checker_llm.invoke([HumanMessage(content=query)]) | |
return res.is_relevant | |
def safety_checker(state:State): | |
llm = ChatGroq(model='meta-llama/llama-guard-4-12b') | |
query = state['query'] | |
res = llm.invoke(query) | |
if res.content == 'safe': | |
return {'is_safe':True} | |
else: | |
return {'is_safe':False, 'answer':"<SAFETY CHECKER> That prompt was harmful, please try something else"} | |
def relevance_checker(state:State): | |
prompt = "You are a lenient relevance-checking assistant. You will be given a user query and a company description. Your job is to decide whether the query is relevant to the company.\nβ Approve most queries that are even loosely related.\nπ« Only reject queries that are **clearly unrelated** or have **no connection at all**.\n\n" | |
prompt += f"\nQuery: {state['query']}" | |
prompt += f"\nDescription: {state['company_description']}" | |
res = invoke_relevance_checker_llm(prompt) | |
return {'is_relevant':res, 'answer':"Sorry! That doesn't seem to be relevant to us, please try something else."} | |
def agent(state:State): | |
relevant_text = "" | |
search_docs = state['vectorstoredb'].similarity_search(state['query']) | |
for chunk in search_docs: | |
relevant_text += f"\n{chunk.page_content}" | |
prompt = f"You have to answer this query: {state['query']} based only on the following information: {relevant_text}. Reply only with the answer." | |
try: | |
res = invoke_llm(prompt) | |
except: | |
time.sleep(60) | |
res = invoke_llm(prompt) | |
finally: | |
return {'answer':res} | |
def safety_assigner(state:State): | |
if state['is_safe']: | |
return 'relevant' | |
else: | |
return 'END' | |
def relevant_assigner(state:State): | |
if state['is_relevant']: | |
return 'Agent' | |
else: | |
return 'END' | |
def chat(query, vect, dec): | |
yield gr.update(visible=True), "" | |
mess = {'query':query, 'vectorstoredb': vect, 'company_description': dec} | |
res = graph.invoke(mess) | |
yield gr.update(visible=False), res['answer'] | |
def setter(pdf_file, description, company_name): | |
yield gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), "", "", "" | |
loader = PyPDFLoader(pdf_file) | |
docs = loader.load() | |
consise_pdf = docs[1].page_content if len(docs) > 1 else docs[0].page_content | |
consise_pdf = consise_pdf[:5555] | |
full_pdf = "" | |
for content in docs: | |
full_pdf += f"\n{content.page_content}" | |
embeddings = HuggingFaceEmbeddings(model_name='sentence-transformers/all-mpnet-base-v2') | |
splitter = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=100) | |
chunks = splitter.split_text(full_pdf) | |
vector_db = Chroma.from_texts(chunks, embeddings) | |
prompt = "You are a company description generator assistant. " | |
prompt += "You will be given the name of a company, a short description provided by the owner, " | |
prompt += "and additional content extracted from a company file (such as a brochure or document). " | |
prompt += "Using this information, generate a concise and professional 3β4 line description of the company. Also, reply in markdown\n\n" | |
prompt += f"Company Name: {company_name}\n" | |
prompt += f"Owner's Description: {description}\n" | |
prompt += f"File Content: {consise_pdf}\n" | |
prompt += "Final Description:" | |
response = invoke_llm(prompt) | |
yield gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), response, response, vector_db | |
builder = StateGraph(State) | |
builder.add_node("Safety Checker", safety_checker) | |
builder.add_node("Relevance Checker", relevance_checker) | |
builder.add_node("Agent", agent) | |
builder.add_edge(START, "Safety Checker") | |
builder.add_conditional_edges("Safety Checker", safety_assigner, {'relevant':"Relevance Checker", 'END': END}) | |
builder.add_conditional_edges("Relevance Checker", relevant_assigner, {'Agent':"Agent", 'END':END}) | |
builder.add_edge("Agent",END) | |
graph = builder.compile() | |
with gr.Blocks(css=".section {margin-bottom: 20px;}") as ui: | |
vectorstore_db = gr.State() | |
company_generated_description = gr.State() | |
# π CSS + HTML animation injection | |
header = gr.HTML(""" | |
<style> | |
.fade-in { | |
animation: fadeIn 1.2s ease-in; | |
} | |
.slide-up { | |
animation: slideUp 0.8s ease-out; | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; } | |
to { opacity: 1; } | |
} | |
@keyframes slideUp { | |
from { transform: translateY(20px); opacity: 0; } | |
to { transform: translateY(0); opacity: 1; } | |
} | |
</style> | |
<div class='fade-in'> | |
<h1 style="text-align:center; font-size: 2.4em;">π Welcome to Your Personalized AI Agent Demo β¨</h1> | |
<p style="text-align:center; font-size: 1.2em;">π Automate marketing, save time, and scale smartly using AI Agents</p> | |
</div> | |
""", visible=True) | |
with gr.Column(visible=True) as setup_page: | |
with gr.Group(elem_classes=["slide-up", "section"]): | |
gr.Markdown("### πΌ Whatβs the name of your company/service?") | |
company_name = gr.Textbox(lines=1, placeholder="e.g., SwiftSync AI") | |
with gr.Group(elem_classes=["slide-up", "section"]): | |
gr.Markdown("### π Tell us briefly what your company does:") | |
company_desc = gr.Textbox(lines=3, placeholder="We provide AI-driven automation tools...") | |
with gr.Group(elem_classes=["slide-up", "section"]): | |
gr.Markdown("### π Got a business PDF? Upload it here to make your AI Agent smarter:") | |
pdf_file = gr.File(file_types=[".pdf"], label="Upload your PDF") | |
with gr.Group(elem_classes=["slide-up"]): | |
setup_submit = gr.Button("β¨ Build My Agent Now") | |
with gr.Column(visible=False) as processing_page: | |
processing_msg = gr.HTML(""" | |
<style> | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
@keyframes fade { | |
0%, 100% { opacity: 0.2; } | |
50% { opacity: 1; } | |
} | |
.loader { | |
border: 6px solid #e0e0e0; | |
border-top: 6px solid #00bcd4; | |
border-radius: 50%; | |
width: 50px; | |
height: 50px; | |
animation: spin 1s linear infinite; | |
box-shadow: 0 0 10px rgba(0,188,212,0.4); | |
} | |
.processing-text { | |
font-size: 1.1em; | |
margin-top: 15px; | |
font-weight: 500; | |
color: #555; | |
animation: fade 2s infinite ease-in-out; | |
} | |
</style> | |
<div style="display: flex; flex-direction: column; align-items: center; margin-top: 40px;"> | |
<div class="loader"></div> | |
<div class="processing-text">π§ Building your AI Agent...</div> | |
</div> | |
""", visible=True) | |
with gr.Column(visible=False) as agent_page: | |
# Header Section | |
gr.HTML(""" | |
<style> | |
.title-box { | |
text-align: center; | |
padding: 15px 0; | |
background: linear-gradient(90deg, #007bff 0%, #00c2ff 100%); | |
color: white; | |
border-radius: 12px; | |
box-shadow: 0 4px 10px rgba(0,0,0,0.15); | |
} | |
.info-card { | |
background: #f9f9f9; | |
border-left: 4px solid #007bff; | |
padding: 12px 20px; | |
border-radius: 8px; | |
font-size: 15px; | |
margin-bottom: 20px; | |
color: #333; | |
} | |
.query-area { | |
padding: 20px; | |
border-radius: 12px; | |
background: #fff; | |
box-shadow: 0 2px 8px rgba(0,0,0,0.08); | |
} | |
.footer-note { | |
text-align: center; | |
color: #888; | |
font-size: 13.5px; | |
padding: 15px 0; | |
margin-top: 20px; | |
} | |
</style> | |
<div class="title-box"> | |
<h1>π§ Your Personalized AI Agent</h1> | |
<p style="margin-top: -10px;">Supercharged for Safety, Relevance, and Results</p> | |
</div> | |
""") | |
gr.HTML(""" | |
<style> | |
.built-by-card { | |
margin-top: 30px; | |
padding: 15px; | |
background: #f0f4ff; | |
color: #333; | |
text-align: center; | |
border-radius: 12px; | |
font-size: 14px; | |
box-shadow: 0 2px 8px rgba(0,0,0,0.05); | |
font-weight: 500; | |
transition: all 0.3s ease; | |
} | |
.built-by-card:hover { | |
box-shadow: 0 4px 14px rgba(0,0,0,0.1); | |
background: #e6f0ff; | |
} | |
</style> | |
<div class="built-by-card"> | |
π Built with β€οΈ by <strong>Darsh Tayal</strong> | |
</div> | |
""", visible = True) | |
# Company Description | |
comp_descri = gr.Markdown("") | |
# Agent Info Features | |
gr.HTML(""" | |
<div class="info-card"> | |
β This agent uses a <strong>relevance checker</strong> to block off-topic questions.<br> | |
π It also runs a <strong>safety filter</strong> to protect users from harmful content.<br> | |
π <em>Saving your time while keeping things secure.</em> | |
</div> | |
""") | |
# Query Section | |
gr.HTML("<div class='query-area'>") | |
gr.Markdown("### π¬ Ask something related to your business/service:") | |
query = gr.Textbox(lines=2, placeholder="e.g., What are the top 3 features of our service?") | |
agent_submit = gr.Button("π Submit Query") | |
loading_spinner = gr.HTML(""" | |
<style> | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
.loader { | |
border: 5px solid #f3f3f3; | |
border-top: 5px solid #00bcd4; | |
border-radius: 50%; | |
width: 40px; | |
height: 40px; | |
animation: spin 1s linear infinite; | |
} | |
.loading-text { | |
margin-top: 8px; | |
color: #666; | |
font-size: 14px; | |
animation: pulse 1.8s infinite ease-in-out; | |
} | |
@keyframes pulse { | |
0%, 100% { opacity: 0.4; } | |
50% { opacity: 1; } | |
} | |
</style> | |
<div style="display:flex; flex-direction:column; align-items:center; margin-top: 10px;" id="spinner"> | |
<div class="loader"></div> | |
<div class="loading-text">Thinking... generating magic β¨</div> | |
</div> | |
""", visible=False) | |
answer = gr.TextArea(label='π€ AI Response', lines=4, interactive=False) | |
gr.HTML("</div>") # Close .query-area div | |
# Footer CTA | |
gr.HTML(""" | |
<div class="footer-note"> | |
π‘ This was just a general demo. Want a version tailored to your business?<br> | |
π Email me at <strong>darshtayal8@gmail.com</strong><br> | |
π We can connect this agent to whatsapp, or any other marketing channel you use<br> | |
βοΈ Start automating, or get left behind. | |
</div> | |
""") | |
setup_submit.click(fn=setter, inputs=[pdf_file, company_desc, company_name], outputs=[setup_page, processing_page, agent_page, header, comp_descri, company_generated_description, vectorstore_db]) | |
agent_submit.click(fn=chat, inputs=[query, vectorstore_db, company_generated_description], outputs=[loading_spinner, answer]) | |
ui.launch() |