Spaces:
Running
on
Zero
Running
on
Zero
| import torch | |
| from typing import Annotated, TypedDict, Literal | |
| from langchain_community.tools import DuckDuckGoSearchRun | |
| from langchain_core.tools import tool | |
| from langgraph.prebuilt import ToolNode, tools_condition | |
| from langgraph.graph import StateGraph, START, END | |
| from langgraph.graph.message import add_messages | |
| from langchain_core.messages import SystemMessage, trim_messages, AIMessage, HumanMessage, ToolCall | |
| from langchain_huggingface.llms import HuggingFacePipeline | |
| from langchain_huggingface import ChatHuggingFace | |
| from langchain_core.prompts import PromptTemplate, ChatPromptTemplate | |
| from langchain_core.runnables import chain | |
| from uuid import uuid4 | |
| import re | |
| import matplotlib.pyplot as plt | |
| from chem_nodes import * | |
| from app import chat_model | |
| import gradio as gr | |
| from PIL import Image | |
| def first_node(state: State) -> State: | |
| ''' | |
| The first node of the agent. This node receives the input and asks the LLM | |
| to determine which is the best tool to use to answer the QUERY TASK. | |
| Input: the initial prompt from the user. should contain only one of more of the following: | |
| smiles: the smiles string, task: the query task, path: the path to the file, | |
| reference: the reference smiles | |
| the value should be separated from the name by a ':' and each field should | |
| be separated from the previous one by a ','. | |
| All of these values are saved to the state | |
| Output: the tool choice | |
| ''' | |
| query_smiles = None | |
| state["query_smiles"] = query_smiles | |
| query_task = None | |
| state["query_task"] = query_task | |
| query_name = None | |
| state["query_name"] = query_name | |
| query_reference = None | |
| state["query_reference"] = query_reference | |
| state['similars_img'] = None | |
| props_string = "" | |
| state["props_string"] = props_string | |
| state["loop_again"] = None | |
| raw_input = state["messages"][-1].content | |
| #print(raw_input) | |
| parts = raw_input.split(',') | |
| for part in parts: | |
| if 'query_smiles' in part: | |
| query_smiles = part.split(':')[1] | |
| if query_smiles.lower() == 'none': | |
| query_smiles = None | |
| state["query_smiles"] = query_smiles | |
| if 'query_task' in part: | |
| query_task = part.split(':')[1] | |
| state["query_task"] = query_task | |
| if 'query_name' in part: | |
| query_name = part.split(':')[1] | |
| if query_name.lower() == 'none': | |
| query_name = None | |
| state["query_name"] = query_name | |
| if 'query_reference' in part: | |
| query_reference = part.split(':')[1] | |
| state["query_reference"] = query_reference | |
| prompt = f'For the QUERY_TASK given below, determine if one or two of the tools descibed below \ | |
| can complete the task. If so, reply with only the tool names followed by "#". If two tools \ | |
| are required, reply with both tool names separated by a comma and followed by "#". \ | |
| If the tools cannot complete the task, reply with "None #".\n \ | |
| QUERY_TASK: {query_task}.\n \ | |
| The information provided by the user is:\n \ | |
| QUERY_SMILES: {query_smiles}.\n \ | |
| QUERY_NAME: {query_name}.\n \ | |
| Tools: \n \ | |
| smiles_tool: queries Pubchem for the smiles string of the molecule based on the name.\n \ | |
| name_tool: queries Pubchem for the NAME of the molecule based on the smiles string.\n \ | |
| similars_tool: queries Pubchem for similar molecules based on the smiles string or name and returns 20 results. \ | |
| returns the names, SMILES strings, molecular weights and logP values for the similar molecules. \n \ | |
| ' | |
| res = chat_model.invoke(prompt) | |
| tool_choices = str(res).split('<|assistant|>')[1].split('#')[0].strip() | |
| tool_choices = tool_choices.split(',') | |
| if len(tool_choices) == 1: | |
| if tool_choices[0].strip().lower() == 'none': | |
| tool_choice = (None, None) | |
| else: | |
| tool_choice = (tool_choices[0].strip().lower(), None) | |
| elif len(tool_choices) == 2: | |
| if tool_choices[0].strip().lower() == 'none': | |
| tool_choice = (None, tool_choices[1].strip().lower()) | |
| elif tool_choices[1].strip().lower() == 'none': | |
| tool_choice = (tool_choices[0].strip().lower(), None) | |
| else: | |
| tool_choice = (tool_choices[0].strip().lower(), tool_choices[1].strip().lower()) | |
| else: | |
| tool_choice = None | |
| state["tool_choice"] = tool_choice | |
| state["which_tool"] = 0 | |
| print(f"The chosen tools are: {tool_choice}") | |
| return state | |
| def retry_node(state: State) -> State: | |
| ''' | |
| If the previous loop of the agent does not get enough informartion from the | |
| tools to answer the query, this node is called to retry the previous loop. | |
| Input: the previous loop of the agent. | |
| Output: the tool choice | |
| ''' | |
| query_task = state["query_task"] | |
| query_smiles = state["query_smiles"] | |
| query_name = state["query_name"] | |
| prompt = f'You were previously given the QUERY_TASK below, and asked to determine if one \ | |
| or two of the tools descibed below could complete the task. TYou tool choices did not succeed. \ | |
| Please re-examine the tool choices and determine if one or two of the tools descibed below \ | |
| can complete the task. If so, reply with only the tool names followed by "#". If two tools \ | |
| are required, reply with both tool names separated by a comma and followed by "#". \ | |
| If the tools cannot complete the task, reply with "None #".\n \ | |
| QUERY_TASK: {query_task}.\n \ | |
| The information provided by the user is:\n \ | |
| QUERY_SMILES: {query_smiles}.\n \ | |
| QUERY_NAME: {query_name}.\n \ | |
| Tools: \n \ | |
| smiles_tool: queries Pubchem for the smiles string of the molecule based on the name as input.\n \ | |
| name_tool: queries Pubchem for the NAME (IUPAC) of the molecule based on the smiles string as input. \ | |
| Also returns a short list of common names for the molecule. \n \ | |
| similars_tool: queries Pubchem for similar molecules based on the smiles string or name as input and returns 20 results. \ | |
| Returns the names, SMILES strings, molecular weights and logP values for the similar molecules. \n \ | |
| ' | |
| res = chat_model.invoke(prompt) | |
| tool_choices = str(res).split('<|assistant|>')[1].split('#')[0].strip() | |
| tool_choices = tool_choices.split(',') | |
| if len(tool_choices) == 1: | |
| if tool_choices[0].strip().lower() == 'none': | |
| tool_choice = (None, None) | |
| else: | |
| tool_choice = (tool_choices[0].strip().lower(), None) | |
| elif len(tool_choices) == 2: | |
| if tool_choices[0].strip().lower() == 'none': | |
| tool_choice = (None, tool_choices[1].strip().lower()) | |
| elif tool_choices[1].strip().lower() == 'none': | |
| tool_choice = (tool_choices[0].strip().lower(), None) | |
| else: | |
| tool_choice = (tool_choices[0].strip().lower(), tool_choices[1].strip().lower()) | |
| elif 'none' in tool_choices[0].strip().lower(): | |
| tool_choice = None | |
| else: | |
| tool_choice = None | |
| state["tool_choice"] = tool_choice | |
| state["which_tool"] = 0 | |
| print(f"The chosen tools are (Retry): {tool_choice}") | |
| return state | |
| def loop_node(state: State) -> State: | |
| ''' | |
| This node accepts the tool returns and decides if it needs to call another | |
| tool or go on to the parser node. | |
| Input: the tool returns. | |
| Output: the next node to call. | |
| ''' | |
| return state | |
| def parser_node(state: State) -> State: | |
| ''' | |
| This is the third node in the agent. It receives the output from the tool, | |
| puts it into a prompt as CONTEXT, and asks the LLM to answer the original | |
| query. | |
| Input: the output from the tool. | |
| Output: the answer to the original query. | |
| ''' | |
| props_string = state["props_string"] | |
| query_task = state["query_task"] | |
| check_prompt = f'Determine if there is enough CONTEXT below to answer the original \ | |
| QUERY TASK. If there is, respond with "PROCEED #" . If there is not enough information \ | |
| to answer the QUERY TASK, respond with "LOOP #" \n \ | |
| CONTEXT: {props_string}.\n \ | |
| QUERY_TASK: {query_task}.\n' | |
| res = chat_model.invoke(check_prompt) | |
| # print('*'*50) | |
| # print(res) | |
| # print('*'*50) | |
| if str(res).split('<|assistant|>')[1].split('#')[0].strip().lower() == "loop": | |
| state["loop_again"] = "loop_again" | |
| return state | |
| elif str(res).split('<|assistant|>')[1].split('#')[0].strip().lower() == "proceed": | |
| state["loop_again"] = None | |
| prompt = f'Using the CONTEXT below, answer the original query, which \ | |
| was to answer the QUERY_TASK. End your answer with a "#" \ | |
| QUERY_TASK: {query_task}.\n \ | |
| CONTEXT: {props_string}.\n ' | |
| res = chat_model.invoke(prompt) | |
| return {"messages": res} | |
| def reflect_node(state: State) -> State: | |
| ''' | |
| This is the fourth node of the agent. It recieves the LLMs previous answer and | |
| tries to improve it. | |
| Input: the LLMs last answer. | |
| Output: the improved answer. | |
| ''' | |
| previous_answer = state["messages"][-1].content | |
| props_string = state["props_string"] | |
| prompt = f'Look at the PREVIOUS ANSWER below which you provided and the \ | |
| TOOL RESULTS. Write an improved answer based on the PREVIOUS ANSWER and the \ | |
| TOOL RESULTS by adding additional clarifying and enriching information. End \ | |
| your new answer with a "#" \ | |
| PREVIOUS ANSWER: {previous_answer}.\n \ | |
| TOOL RESULTS: {props_string}. ' | |
| res = chat_model.invoke(prompt) | |
| return {"messages": res} | |
| def get_chemtool(state): | |
| ''' | |
| ''' | |
| which_tool = state["which_tool"] | |
| tool_choice = state["tool_choice"] | |
| #print(tool_choice) | |
| if tool_choice == None: | |
| return None | |
| if which_tool == 0 or which_tool == 1: | |
| current_tool = tool_choice[which_tool] | |
| if current_tool == "smiles_tool" and ("query_name" not in state.keys()): | |
| current_tool = "name_tool" | |
| print("Switching from smiles tool to name tool") | |
| elif current_tool == "name_tool" and ("query_smiles" not in state.keys()): | |
| current_tool = "smiles_tool" | |
| print("Switching from name tool to smiles tool") | |
| elif which_tool > 1: | |
| current_tool = None | |
| return current_tool | |
| def loop_or_not(state): | |
| ''' | |
| ''' | |
| print(f"Loop? {state['loop_again']}") | |
| if state["loop_again"] == "loop_again": | |
| return True | |
| else: | |
| return False | |
| def pretty_print(answer): | |
| final = str(answer['messages'][-1]).split('<|assistant|>')[-1].split('#')[0].strip("n").strip('\\').strip('n').strip('\\') | |
| for i in range(0,len(final),100): | |
| print(final[i:i+100]) | |
| def print_short(answer): | |
| for i in range(0,len(answer),100): | |
| print(answer[i:i+100]) |