Spaces:
Paused
Paused
from langchain.output_parsers import PydanticOutputParser | |
from langchain_core.prompts import ChatPromptTemplate | |
from langchain_core.pydantic_v1 import BaseModel, Field | |
from .db import shortlisted_recipes_to_string | |
def get_grader_chain(llm_model): | |
class GradeRecipes(BaseModel): | |
"""Binary score for relevance check on retrieved documents.""" | |
binary_score: str = Field( | |
description="Document representing recipes are generally relevant to the criteria in the question, 'yes' or 'no'" | |
) | |
integer_score: int = Field( | |
description="Degree to which Documents are relevant to the question, integers from 1 to 100" | |
) | |
# LLM with function call | |
structured_llm_grader = llm_model.with_structured_output(GradeRecipes) | |
# Prompt | |
system = """You are a grader assessing relevance of a retrieved cooking recipe document to a user question. \n | |
It does not need to be a stringent test. The goal is to filter out completely erroneous or irrelevant retrievals. \n | |
If the document contains keyword(s) or semantic meaning related to the user question, grade it as relevant. \n | |
Give a binary score 'yes' or 'no' score to indicate whether the recipe document is relevant to the question. | |
Also give a integer score from 1 to 100 to indicate the degree to which the recipe document is relevant to the question. | |
""" | |
grade_prompt = ChatPromptTemplate.from_messages( | |
[ | |
("system", system), | |
("human", "Retrieved recipe document: \n\n {document} \n\n User question: {question}"), | |
] | |
) | |
retrieval_grader = grade_prompt | structured_llm_grader | |
return retrieval_grader | |
def get_recipe_url_extractor_chain(llm_model): | |
class RecipeUrlsSchema(BaseModel): | |
urls: list[str] = Field(description="A list of urls pointing to specific recipes") | |
structured_llm_grader = llm_model.with_structured_output(RecipeUrlsSchema) | |
pydantic_parser = PydanticOutputParser(pydantic_object=RecipeUrlsSchema) | |
format_instructions = pydantic_parser.get_format_instructions() | |
RECIPE_SEARCH_PROMPT = """ | |
Your goal is to understand and parse out the full http urls in the context corresponding to each recipe. | |
{format_instructions} | |
Context: | |
{context} | |
""" | |
prompt = ChatPromptTemplate.from_template( | |
template=RECIPE_SEARCH_PROMPT, partial_variables={"format_instructions": format_instructions} | |
) | |
retriever = prompt | structured_llm_grader | |
return retriever | |
def get_recipe_selection_chain(llm_model): | |
class RecipeSelectionSchema(BaseModel): | |
asking_for_recipe_suggestions: str = Field( | |
description="Whether the User Question is asking for recipe suggestions based on some criteria, 'yes' or 'no'" | |
) | |
referring_to_specific_recipe: str = Field( | |
description="Whether the User Question is asking about one specific recipe (but NOT asking to just show a specific recipe), 'yes' or 'no'" | |
) | |
referring_to_shortlisted_recipes: str = Field( | |
description="Whether the User Question is asking generally about the 3 shortlisted recipes, 'yes' or 'no'" | |
) | |
show_specific_recipe: str = Field( | |
description="Whether the User Question is asking asking to show a specific recipe, 'yes' or 'no'" | |
) | |
specific_recipe_url: str = Field( | |
description="URL of the specific recipe that the User Question is directed to, if any " | |
) | |
# LLM with function call | |
structured_llm_grader = llm_model.with_structured_output(RecipeSelectionSchema) | |
pydantic_parser = PydanticOutputParser(pydantic_object=RecipeSelectionSchema) | |
format_instructions = pydantic_parser.get_format_instructions() | |
# Prompt | |
RECIPE_SELECTION_PROMPT = """ | |
You are a helpful assistant attempting to categorize the nature of the User question | |
based on the last message sent to he user and the provided context. | |
{format_instructions} | |
User Question: | |
{question} | |
Last message provided to the user: | |
{last_message} | |
Context: | |
{context} | |
""" | |
prompt = ChatPromptTemplate.from_template( | |
template=RECIPE_SELECTION_PROMPT, partial_variables={"format_instructions": format_instructions} | |
) | |
chain = prompt | structured_llm_grader | |
return chain | |
def get_question_type_chain(llm_model): | |
class RecipeSelectionChanceSchema(BaseModel): | |
asking_for_recipe_suggestions: int = Field( | |
description="The likelihood / chance that the User Question is asking for recipe suggestions based on some criteria, integers from 1 to 100" | |
) | |
referring_to_specific_recipe: int = Field( | |
description="The likelihood / chance that the User Question is asking specific questions about a single specific recipe, integers from 1 to 100" | |
) | |
referring_to_shortlisted_recipes: int = Field( | |
description="The likelihood / chance that the User Question is asking generally about more than one recipe provided in the last message, integers from 1 to 100" | |
) | |
show_specific_recipe: int = Field( | |
description="The likelihood / chance that the User Question is asking to show the full recipe for a specific recipe, integers from 1 to 100" | |
) | |
send_text: int = Field( | |
description="The likelihood / chance that the User Question is to send a SMS or text, integers from 1 to 100" | |
) | |
specific_recipe_url: str = Field( | |
description="URL of the specific recipe that the User Question is directed to, if any " | |
) | |
# LLM with function call | |
structured_llm_grader = llm_model.with_structured_output(RecipeSelectionChanceSchema) | |
pydantic_parser = PydanticOutputParser(pydantic_object=RecipeSelectionChanceSchema) | |
format_instructions = pydantic_parser.get_format_instructions() | |
# Prompt | |
RECIPE_SELECTION_PROMPT = """ | |
You are a helpful assistant attempting to categorize the nature of the User question | |
based on the last message sent to he user and the provided context. | |
Note that if there were recipe suggesetions in the last message provided to the user, | |
it is highly likely that the user is asking questions referring to shortlisted recipes. | |
If the last message was a full single recipe, it is generally likely that the user | |
is asking questions referring to specific recipe. | |
If the user is asking to show the full recipe, it is highly likely that they are asking | |
to show a specific recipe and less likely that they are asking for anything else. | |
{format_instructions} | |
User Question: | |
{question} | |
Last message provided to the user: | |
{last_message} | |
Context: | |
{context} | |
""" | |
prompt = ChatPromptTemplate.from_template( | |
template=RECIPE_SELECTION_PROMPT, partial_variables={"format_instructions": format_instructions} | |
) | |
chain = prompt | structured_llm_grader | |
return chain | |
def get_selected_recipe(llm_model, question, shortlisted_recipes, messages): | |
selected_recipe = None | |
recipe_selection_chain = get_recipe_selection_chain(llm_model) | |
recipe_selection_response = recipe_selection_chain.invoke( | |
{ | |
"question": question, | |
"context": shortlisted_recipes_to_string(shortlisted_recipes), | |
"last_message": messages[-1] if messages else "", | |
} | |
) | |
if ( | |
recipe_selection_response.referring_to_specific_recipe == "yes" | |
and recipe_selection_response.specific_recipe_url | |
): | |
selected_recipe = next( | |
(r for r in shortlisted_recipes if r["url"] == recipe_selection_response.specific_recipe_url) | |
) | |
return selected_recipe | |