from langgraph.graph.message import add_messages from langchain_core.messages import AnyMessage, HumanMessage, AIMessage, SystemMessage from langgraph.prebuilt import ToolNode from langgraph.graph import START, StateGraph from langgraph.prebuilt import tools_condition from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace from tools import extract_text, describe_image from langchain_community.tools import DuckDuckGoSearchRun from langchain_openai import ChatOpenAI from typing import TypedDict, Annotated, Optional class AgentState(TypedDict): messages: Annotated[list[AnyMessage], add_messages] class BasicAgent(): def __init__(self, llm): chat = ChatHuggingFace(llm=llm, verbose=True) search_tool = DuckDuckGoSearchRun() vision_llm = ChatOpenAI(model="gpt-4o") self.tools = [extract_text, describe_image, search_tool] self.chat_with_tools = vision_llm.bind_tools(self.tools) self._initialize_graph() print("BasicAgent initialized.") def _initialize_graph(self): builder = StateGraph(AgentState) # Define nodes builder.add_node("assistant", self.assistant) builder.add_node("tools", ToolNode(self.tools)) # Define edges builder.add_edge(START, "assistant") builder.add_conditional_edges("assistant",tools_condition) builder.add_edge("tools", "assistant") # Compile the graph self.agent = builder.compile() def __call__(self, question: str) -> str: print(f"Agent received question: {question}.") messages=[HumanMessage(content=question)] response = self.agent.invoke({"messages":messages}) answer = response['messages'][-1].content print(f"Agent returning answer: {answer}") return answer def assistant(self, state: AgentState): sys_msg = SystemMessage(content=f""" You are a general AI assistant. I will ask you a question. Finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string. When providing the final answer, ONLY give [YOUR FINAL ANSWER]. Do not add anything else, no additional motivation or explanation, and do not return 'FINAL ANSWER:'. """) response = self.chat_with_tools.invoke([sys_msg] + state["messages"]) # Ensure the response is not None if response is None: raise RuntimeError("chat_with_tools.invoke returned None") return { "messages": response, }