| 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." |