Final_Assignment / graphs /evaluation.py
sebastianfrench's picture
add python file download
b4cd776
from models.models import groq_model, anthropic_model
from tools import taivily_search, serper_search, execute_code, get_youtube_transcript, execute_python_file_url
from langgraph.graph import StateGraph, START, END
from langchain_core.messages import SystemMessage, AIMessage, ToolMessage
from typing import List, TypedDict
from langgraph.prebuilt import ToolNode
tools = [
taivily_search,
serper_search,
get_youtube_transcript,
execute_code,
execute_python_file_url
]
class EvaluationState(TypedDict):
messages: List
question: str
answer: str
external_information: str
has_enough_information: bool
is_valid_answer: bool
step_counter: dict[str, int]
bounded_model_groq = groq_model.bind_tools(tools)
def call_node(state: EvaluationState):
"""
This node call the model with the question and the tools
"""
# Convert any ToolMessage objects to a format Groq can handle
response = bounded_model_groq.invoke(state["messages"])
state["messages"].append(response)
return state
tool_node = ToolNode(tools)
def answer_question(state: EvaluationState):
"""
This node get the context information and call the model to get the answer.
"""
prompt = f"""## Instruction \n I will ask you a question. Report your thoughts, and finish with only 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.
## Question
{state["question"]}
## Relevant information
{state["external_information"]}
## answer"""
response = anthropic_model.invoke(prompt)
state["messages"].append(AIMessage(content=response.content))
state["answer"] = response.content
return state
def map_answer(state: EvaluationState):
"""
Map the answer to the final answer
"""
answer = state["answer"]
prompt = f"""## Instruction
map the answer to the final answer. The final answer should be a number, string or a list of numbers and/or strings. Remove quotes.
## Answer
{answer}
## Final answer"""
answer = anthropic_model.invoke(prompt)
return {
"answer": answer.content
}
def validator(state: EvaluationState):
"""
Validate if the answer fills the requirements
"""
answer = state["answer"]
result = anthropic_model.invoke(f"Validate if the answer fits the next requirements: \n\n{answer}\n\nThe answer should be a number, string or a list of numbers and/or strings. If the answer fits the requirements, return just 'yes', otherwise return 'no'.")
state["is_valid_answer"] = result.content.startswith("yes")
state["messages"].append(SystemMessage(content=f"Validator: {result.content}"))
return state
def route_validator(state):
state["step_counter"]["validator"] = state["step_counter"].get("validator", 0) + 1
if state["is_valid_answer"] or state["step_counter"]["validator"] > 2:
return END
else:
return "map_answer"
def evaluator(state):
"""
Evaluate if the context information is enough to answer the question.
"""
prompt = f"""## Instruction
Answer just "yes" (without the quotes), if the context information is enough to answer the question.
## Question
{state["question"]}
## Relevant information
{state["external_information"]}
"""
result = anthropic_model.invoke(prompt)
state["has_enough_information"] = result.content.startswith("yes")
state["messages"].append(SystemMessage(content=f"Evaluator: {result.content}"))
return state
def route_iteration(state):
state["step_counter"]["iteration"] = state["step_counter"].get("iteration", 0) + 1
if state["has_enough_information"] or state["step_counter"]["iteration"] > 2:
return "answer_question"
else:
return "agent"
def build_workflow():
"""
Build search workflow with conditional edge for evaluation and iteration.
"""
workflow = StateGraph(EvaluationState)
workflow.add_node("agent", call_node)
workflow.add_node("action", tool_node)
workflow.add_node("evaluator", evaluator)
workflow.add_node("answer_question", answer_question)
workflow.add_node("map_answer", map_answer)
workflow.add_node("validator", validator)
workflow.add_edge(START, "agent")
workflow.add_edge("agent", "action")
workflow.add_edge("action", "evaluator")
workflow.add_conditional_edges("evaluator", route_iteration, {"answer_question":"answer_question","agent":"agent"})
workflow.add_edge("answer_question","map_answer")
workflow.add_edge("map_answer", "validator")
workflow.add_conditional_edges("validator", route_validator, {"map_answer": "map_answer", END: END})
return workflow.compile()
""" if __name__ == "__main__":
graph = build_workflow()
mermaid_text = graph.get_graph().draw_mermaid()
print(mermaid_text) """