loteriof's picture
Update app.py
ed0fedb verified
import os
from dotenv import load_dotenv
from langchain_google_genai import ChatGoogleGenerativeAI
import operator
from langgraph.graph import StateGraph, START, END
from typing import Annotated, Literal
from typing_extensions import TypedDict
from langgraph.types import Send
from pydantic import BaseModel
from langchain.tools import DuckDuckGoSearchRun
from langchain_community.utilities import WikipediaAPIWrapper
from langchain_community.tools import WikipediaQueryRun
from langchain_experimental.utilities import PythonREPL
class SmallerQuestions(BaseModel):
questions: list[str]
class Nature(BaseModel):
nature: Literal["research", "computation", "interpretation"]
class OverallState(TypedDict):
initial_question: str = ""
smaller_questions: list[str] = []
current_question: str = ""
context: str = ""
key_words: str = ""
wiki_results: str = ""
web_results: str = ""
nature: Literal["research", "computation", "interpretation"] = "interpretation"
middle_answers: Annotated[list, operator.add] = []
answer: str = ""
#Nodes
def BreakQuestion(state: OverallState):
prompt = """Task:
Break the following question down into a maximum of 7 smaller questions.
Your response must be a list in which each item corresponds to one smaller question.
Beware that the answer to each one of the questions you list should lead to the next question without holdbacks.
Question:
""" + state["initial_question"]
response = llm.with_structured_output(SmallerQuestions).invoke(prompt)
return {"smaller_questions": response.questions,
"current_question": response.questions[0]}
def DecideNature(state: OverallState):
prompt = """Task:
The following question is answerable via one of the following three approaches: "computation", "research" or "interpretation".
Computation is a question that requires a Python interpreter; research is a question that requires a web search in sites like Wikipedia; interpretation is a question that needs only reasoning to be answered.
Decide which of those is the best approach and return only the string corresponding to the one you choose, and nothing else.
Question:
""" + state["current_question"]
response = llm.with_structured_output(Nature).invoke(prompt)
return {"nature": response.nature}
def KeyWords(state: OverallState):
prompt = """Task:
Gather the keywords from the following question to perform a search. Your response must return only keywords and nothing else.
Question:
""" + state["current_question"]
response = llm.invoke(prompt)
return {"key_words": response.content}
def WikiSearch(state: OverallState):
wikipedia_api = WikipediaAPIWrapper(top_k_results=1,doc_content_chars_max=1000)
wikipedia = WikipediaQueryRun(api_wrapper=wikipedia_api)
result = wikipedia.run({"query": state["key_words"]})
return {"wiki_results": result}
def WebSearch(state: OverallState):
search_tool = DuckDuckGoSearchRun()
result = search_tool.invoke(state["key_words"])
return {"web_results": result}
def ContextAnswer(state: OverallState):
context = "Wiki results: " + state.get("wiki_results", "No wiki results found") + "\n\n Web results: " + state.get("web_results", "No web results found")
return {"context": context}
def Computation(state: OverallState):
python_repl = PythonREPL()
prompt = """Task:
The following question requires a computation to be answered. Create a code that solves the problem.
Your response must be only the code that solves the problem and nothing else.
Question:
""" + state["current_question"]
response = llm.invoke(prompt)
code_result = python_repl.run(response.content)
structured_answer = "The result of the following problem is " + code_result + "Problem: " + state["current_question"]
return {"middle_answers": [structured_answer]}
def Reasoning(state: OverallState):
prompt = """Task:
Answer the following question using your reasoning. Your response must contain only the answer to the question and nothing else.
Question:
""" + state["current_question"]
response = llm.invoke(prompt)
return {"middle_answers": [response.content]}
def AdvanceToNextQuestion(state: OverallState):
current_index = state["smaller_questions"].index(state["current_question"])
if current_index < len(state["smaller_questions"]) - 1:
return {"current_question": state["smaller_questions"][current_index + 1],
"context": ""}
return {}
def FinalAnswer(state: OverallState):
middle_answers = "\n\n".join(state["middle_answers"])
prompt = """Task:
You will receive a question and some information from which you must be able to solve the question completely.
Do your best to keep your response as close to the given information as possible. Your response should contain only the final answer to the question and nothing else.
Question:
""" + state["initial_question"] + "Context: " + middle_answers
response = llm.invoke(prompt)
return {"answer": response.content}
#Edges
def Nature(state: OverallState):
if state["nature"] == "computation":
return Send("computation", state)
elif state["nature"] == "research":
return Send("key_words", state)
else:
return Send("interpretation", state)
def NextQuestion(state: OverallState):
if state["current_question"] == state["smaller_questions"][-1]:
return "final_answer"
else:
return "define_nature"
load_dotenv()
llm = ChatGoogleGenerativeAI(
model="gemini-1.5-pro", # or "gemini-1.5-flash" for faster/cheaper
temperature=0,
max_tokens=1024,
timeout=30,
max_retries=2,
google_api_key=os.getenv("GOOGLE_API_KEY")
)
builder = StateGraph(OverallState)
builder.add_node("break_question", BreakQuestion)
builder.add_node("advance", AdvanceToNextQuestion)
builder.add_node("define_nature", DecideNature)
builder.add_node("key_words", KeyWords)
builder.add_node("wiki_search", WikiSearch)
builder.add_node("web_search", WebSearch)
builder.add_node("answer", ContextAnswer)
builder.add_node("computation", Computation)
builder.add_node("interpretation", Reasoning)
builder.add_node("final_answer", FinalAnswer)
builder.add_edge(START, "break_question")
builder.add_conditional_edges("define_nature", Nature)
builder.add_edge("key_words", "wiki_search")
builder.add_edge("key_words", "web_search")
builder.add_edge("web_search", "answer")
builder.add_edge("wiki_search", "answer")
builder.add_edge("answer", "advance")
builder.add_edge("computation", "advance")
builder.add_edge("interpretation", "advance")
builder.add_conditional_edges("advance", NextQuestion)
builder.add_edge("final_answer", END)
graph = builder.compile()