|  | from typing import TypedDict, Annotated, Optional | 
					
						
						|  | from langgraph.graph.message import add_messages | 
					
						
						|  | from langchain_core.messages import AnyMessage, HumanMessage, SystemMessage, ToolMessage | 
					
						
						|  | from langgraph.prebuilt import ToolNode, tools_condition | 
					
						
						|  | from langgraph.graph import START, StateGraph, END | 
					
						
						|  | from langchain_openai import ChatOpenAI | 
					
						
						|  | from pydantic import SecretStr | 
					
						
						|  | import os | 
					
						
						|  | from dotenv import load_dotenv | 
					
						
						|  | from tools import download_file_from_url, basic_web_search, extract_url_content, wikipedia_reader, transcribe_audio_file, question_youtube_video | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | load_dotenv() | 
					
						
						|  |  | 
					
						
						|  | OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "") | 
					
						
						|  | MAIN_LLM_MODEL = os.getenv("MAIN_LLM_MODEL", "google/gemini-2.0-flash-lite-001") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if not OPENROUTER_API_KEY: | 
					
						
						|  | raise ValueError("OPENROUTER_API_KEY is not set. Please ensure it is defined in your .env file or environment variables.") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def create_agent_graph(): | 
					
						
						|  |  | 
					
						
						|  | main_llm = ChatOpenAI( | 
					
						
						|  | model=MAIN_LLM_MODEL, | 
					
						
						|  | api_key=SecretStr(OPENROUTER_API_KEY), | 
					
						
						|  | base_url="https://openrouter.ai/api/v1", | 
					
						
						|  | verbose=True | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | tools = [download_file_from_url, basic_web_search, extract_url_content, wikipedia_reader, transcribe_audio_file, question_youtube_video] | 
					
						
						|  | chat_with_tools = main_llm.bind_tools(tools) | 
					
						
						|  |  | 
					
						
						|  | class AgentState(TypedDict): | 
					
						
						|  | messages: Annotated[list[AnyMessage], add_messages] | 
					
						
						|  | file_url: Optional[str | None] | 
					
						
						|  | file_ext: Optional[str | None] | 
					
						
						|  | local_file_path: Optional[str | None] | 
					
						
						|  | final_answer: Optional[str | None] | 
					
						
						|  |  | 
					
						
						|  | def assistant(state: AgentState): | 
					
						
						|  | return { | 
					
						
						|  | "messages": [chat_with_tools.invoke(state["messages"])], | 
					
						
						|  | "file_url": state.get("file_url", None), | 
					
						
						|  | "file_ext": state.get("file_ext", None), | 
					
						
						|  | "local_file_path": state.get("local_file_path", None), | 
					
						
						|  | "final_answer": state.get("final_answer", None) | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | def file_path_updater_node(state: AgentState): | 
					
						
						|  | download_tool_response = state["messages"][-1].content | 
					
						
						|  | file_path = download_tool_response.split("Local File Path: ")[-1].strip() | 
					
						
						|  | return { | 
					
						
						|  | "local_file_path": file_path | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | def file_path_condition(state: AgentState) -> str: | 
					
						
						|  | if state["messages"] and isinstance(state["messages"][-1], ToolMessage): | 
					
						
						|  | tool_response = state["messages"][-1] | 
					
						
						|  | if tool_response.name == "download_file_from_url": | 
					
						
						|  | return "update_file_path" | 
					
						
						|  | return "assistant" | 
					
						
						|  |  | 
					
						
						|  | def format_final_answer_node(state: AgentState) -> AgentState: | 
					
						
						|  | """ | 
					
						
						|  | Formats the final answer based on the state. | 
					
						
						|  | This node is reached when the assistant has completed its task. | 
					
						
						|  | """ | 
					
						
						|  | final_answer = state["messages"][-1].content if state["messages"] else None | 
					
						
						|  | if final_answer: | 
					
						
						|  | state["final_answer"] = final_answer.split("FINAL ANSWER:")[-1].strip() | 
					
						
						|  | return state | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | builder = StateGraph(AgentState) | 
					
						
						|  |  | 
					
						
						|  | builder.add_node("assistant", assistant) | 
					
						
						|  | builder.add_edge(START, "assistant") | 
					
						
						|  | builder.add_node("tools", ToolNode(tools)) | 
					
						
						|  | builder.add_node("file_path_updater_node", file_path_updater_node) | 
					
						
						|  | builder.add_node("format_final_answer_node", format_final_answer_node) | 
					
						
						|  |  | 
					
						
						|  | builder.add_conditional_edges( | 
					
						
						|  | "assistant", | 
					
						
						|  | tools_condition, | 
					
						
						|  | { | 
					
						
						|  | "tools": "tools", | 
					
						
						|  | "__end__": "format_final_answer_node" | 
					
						
						|  | } | 
					
						
						|  | ) | 
					
						
						|  | builder.add_conditional_edges( | 
					
						
						|  | "tools", | 
					
						
						|  | file_path_condition, | 
					
						
						|  | { | 
					
						
						|  | "update_file_path": "file_path_updater_node", | 
					
						
						|  | "assistant": "assistant" | 
					
						
						|  | } | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | builder.add_edge("file_path_updater_node", "assistant") | 
					
						
						|  | builder.add_edge("format_final_answer_node", END) | 
					
						
						|  | graph = builder.compile() | 
					
						
						|  | return graph | 
					
						
						|  |  | 
					
						
						|  | class BasicAgent: | 
					
						
						|  | """ | 
					
						
						|  | A basic agent that can answer questions and download files. | 
					
						
						|  | Requires a system message be defined in 'system_prompt.txt'. | 
					
						
						|  | """ | 
					
						
						|  | def __init__(self, graph=None): | 
					
						
						|  |  | 
					
						
						|  | with open("system_prompt.txt", "r", encoding="utf-8") as f: | 
					
						
						|  | self.system_message = SystemMessage(content=f.read()) | 
					
						
						|  |  | 
					
						
						|  | if graph is None: | 
					
						
						|  | self.graph = create_agent_graph() | 
					
						
						|  | else: | 
					
						
						|  | self.graph = graph | 
					
						
						|  |  | 
					
						
						|  | def __call__(self, question: str, file_url: Optional[str] = None, file_ext: Optional[str] = None) -> str: | 
					
						
						|  | """ | 
					
						
						|  | Call the agent with a question and optional file URL and extension. | 
					
						
						|  |  | 
					
						
						|  | Args: | 
					
						
						|  | question (str): The user's question. | 
					
						
						|  | file_url (Optional[str]): The URL of the file to download. | 
					
						
						|  | file_ext (Optional[str]): The file extension for the downloaded file. | 
					
						
						|  |  | 
					
						
						|  | Returns: | 
					
						
						|  | str: The agent's response. | 
					
						
						|  | """ | 
					
						
						|  | if file_url and file_ext: | 
					
						
						|  | question += f"\nREFERENCE FILE MUST BE RETRIEVED\nFile URL: {file_url}, File Extension: {file_ext}\nUSE A TOOL TO DOWNLOAD THIS FILE." | 
					
						
						|  | state = { | 
					
						
						|  | "messages": [self.system_message, HumanMessage(content=question)], | 
					
						
						|  | "file_url": file_url, | 
					
						
						|  | "file_ext": file_ext, | 
					
						
						|  | "local_file_path": None, | 
					
						
						|  | "final_answer": None | 
					
						
						|  | } | 
					
						
						|  | response = self.graph.invoke(state) | 
					
						
						|  | for m in response["messages"]: | 
					
						
						|  | m.pretty_print() | 
					
						
						|  | return response["final_answer"] if response["final_answer"] else "No final answer generated." |