Spaces:
Runtime error
Runtime error
File size: 6,845 Bytes
c663214 e288957 ac0f508 e288957 a16f585 ec2661f abc1112 5845cb8 ac0f508 3416046 af1f85e a16f585 6d34792 afd11f2 e288957 ec2661f e288957 c663214 7ff993c e288957 6e06bba 39d720c ac0f508 c663214 abc1112 e288957 c663214 3416046 c663214 e288957 c663214 1eadb17 c663214 1eadb17 c663214 5659eb0 abc1112 3416046 abc1112 e288957 abc1112 afd11f2 c663214 e288957 c663214 e288957 c663214 e288957 c999b95 4fd7379 afd11f2 4fd7379 6d34792 c999b95 b392389 e288957 6d34792 37f768f 6d34792 37f768f 6d34792 37f768f 6d34792 37f768f 6d34792 37f768f 6d34792 37f768f 6d34792 37f768f 6d34792 37f768f 6d34792 37f768f c663214 ac0f508 37f768f a16f585 6d34792 90979e0 a16f585 ac0f508 6513634 ac0f508 a16f585 ac0f508 6513634 ac0f508 37f768f ac0f508 c663214 ac0f508 4fd7379 c663214 4fd7379 ac0f508 457c7a2 ac0f508 6db9b41 ac0f508 c663214 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | import smtplib
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph,START,END
from langgraph.prebuilt import tools_condition
from langgraph.graph.message import add_messages
from langchain_core.tools import tool
from dotenv import load_dotenv
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, ToolMessage
from langchain_tavily import TavilySearch
from langchain_groq import ChatGroq
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command, interrupt
from google_auth_helpers import AUTH_REQUIRED_PREFIX
from drive_tools import search_and_download_doc_tool
from calendar_tools import create_calendar_event_tool
import os
# ==================== LOAD ENV =======================
# memory = MemorySaver()
load_dotenv()
base_model = "openai/gpt-oss-120b"
api = os.getenv("GROQ_API_KEY")
tavily = os.getenv("TAVILY_API")
SENDER_EMAIL = os.getenv("SENDER_EMAIL")
SENDER_PASSWORD = os.getenv("SENDER_PASSWORD")
SENDGRID_API_KEY = os.getenv("SENDGRID_API_KEY")
# ==================== LLM =======================
llm = ChatGroq(
api_key = api,
model = base_model,
temperature=0.3
)
# ==================== TOOL =======================
@tool
def send_email_tool(to_email: str, subject: str, body: str) -> str:
"""
Sends an email to a recipient.
Args:
to_email: Recipient email address
subject: Email subject
body: Email body content
"""
try:
message = Mail(
from_email=SENDER_EMAIL,
to_emails=to_email,
subject=subject,
plain_text_content=body,
)
sg = SendGridAPIClient(SENDGRID_API_KEY)
sg.send(message)
return f"Email successfully sent to {to_email}"
except Exception as e:
return f"Failed to send email: {str(e)}"
tools = [search_and_download_doc_tool, send_email_tool, create_calendar_event_tool]
llm_with_tools = llm.bind_tools(tools)
# ==================== STATE =======================
class State(TypedDict):
messages: Annotated[list,add_messages]
# ==================== NODES =======================
def chatbot(state:State):
response = llm_with_tools.invoke([
SystemMessage(content="""
You are an AI assistant with access to tools.
Available tools:
1. send_email_tool β Use when the user wants to send an email.
2. search_and_download_doc_tool β Use when the user wants to find or download a document from Google Drive.
3. create_calendar_event_tool β Use when the user wants to schedule a meeting, send a calendar invite, or create a Google Meet.
Rules:
- Always call the appropriate tool when the request requires action.
- Do NOT respond with plain text if an action is required.
- After tool execution, summarize the result for the user.
- Never invent or fabricate Google OAuth URLs. If authentication is required, the tool
result or system interrupt will provide the real sign-in link.
"""),
*state["messages"]
])
return {"messages":[response]}
_AUTH_PRODUCT_LABELS = {
"search_and_download_doc_tool": "Google Drive",
"create_calendar_event_tool": "Google Calendar",
}
def handle_tools(state: State):
"""
Custom tool-execution node that intercepts AUTH_REQUIRED:: sentinels
from Google tools and surfaces them via LangGraph interrupt.
"""
last_message: AIMessage = state["messages"][-1]
results = []
for tool_call in last_message.tool_calls:
matched_tool = next(
(t for t in tools if t.name == tool_call["name"]), None
)
if matched_tool is None:
result_content = f"Unknown tool: {tool_call['name']}"
else:
result_content = matched_tool.invoke(tool_call["args"])
if isinstance(result_content, str) and result_content.startswith(AUTH_REQUIRED_PREFIX):
first_line = result_content.split("\n")[0]
auth_url = first_line.removeprefix(AUTH_REQUIRED_PREFIX).strip()
product = _AUTH_PRODUCT_LABELS.get(
tool_call["name"], "Google (Drive & Calendar)"
)
interrupt({
"type": "auth_required",
"auth_url": auth_url,
"message": (
f"π {product} access is required. "
"Please authenticate by visiting the link below, then retry your request.\n\n"
f"π {auth_url}"
),
})
result_content = (
"Authentication flow initiated. Once you have completed Google sign-in, "
"please repeat your request."
)
results.append(
ToolMessage(
content=result_content,
tool_call_id=tool_call["id"],
)
)
return {"messages": results}
# ==================== GRAPH =======================
# Adding Node
memory = MemorySaver()
graph_builder=StateGraph(State)
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("tools", handle_tools)
graph_builder.add_edge(START, "chatbot")
graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
{
"tools": "tools",
"__end__": END
}
)
graph_builder.add_edge("tools","chatbot")
graph=graph_builder.compile(checkpointer=memory)
# ==================== ENTRY FUNCTION =======================
def run_agent(user_input: str):
result = graph.invoke({
"messages": [HumanMessage(content=user_input)]
})
return result["messages"][-1].content
# Start
# LLM + promt -> Chatbot
#
#
#
# External Yools Tool Node
# Make a tool Call || Tavily || -><-
#
#
# End
# How does chatbot know the when to use tools ?
# LLM binds with the tools :
# Addition function added as tool to the LLM
# Doc String is used to know what are thre inputs and arguments : If they match LLM make a call to Tool
# Instead of relying on its own response.
#
# ReACT agent aplits the query into multiple statements and repeatedly solves query part by part
# 1. Act
# 2. Observe
# 3. Reason
# #
# Binding tools with LLMs #
# Stategraph
# tokenizer = AutoTokenizer.from_pretrained(
# base_model,
# use_auth_token=api,
# cache_dir=local_dir,
# )
# model = AutoModelForCausalLM.from_pretrained(
# base_model,
# use_auth_token=api,
# torch_dtype=torch.float16,
# cache_dir=local_dir,
# device_map="auto",
# )
# hf_pipeline = pipeline(
# "text-generation",
# model=model,
# tokenizer=tokenizer,
# max_new_tokens=512,
# do_sample=True,
# temperature=0.7
# )
# hf_llm = HuggingFacePipeline(pipeline=hf_pipeline) |