Normalize Option in Scoring Chain (#11412)

pull/11420/head
William FH 10 months ago committed by GitHub
parent b9fad28f5e
commit 940b9ae30a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,20 +5,212 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# Overall quality evaluation\n",
"# Scoring Evaluator\n",
"\n",
"In scenarios where you wish to score a model's output from 1-10 based on a criteria set and/or reference answer, the `Score` evaluator can be helpful. This is most useful for comparing the performance of different models on a given task.\n",
"The Scoring Evaluator instructs a language model to assess your model's predictions on a specified scale (default is 1-10) based on your custom criteria or rubric. This feature provides a nuanced evaluation instead of a simplistic binary score, aiding in evaluating models against tailored rubrics and comparing model performance on specific tasks.\n",
"\n",
"Refer to the documentation of the [ScoreStringEvalChain](https://api.python.langchain.com/en/latest/evaluation/langchain.evaluation.scoring.eval_chain.ScoreStringEvalChain.html#langchain.evaluation.scoring.eval_chain.ScoreStringEvalChain) class for full details.\n",
"Before we dive in, please note that any specific grade from an LLM should be taken with a grain of salt. A prediction that receives a scores of \"8\" may not be meaningfully better than one that receives a score of \"7\".\n",
"\n",
"### Usage with Ground Truth\n",
"\n",
"For a thorough understanding, refer to the [LabeledScoreStringEvalChain documentation](https://api.python.langchain.com/en/latest/evaluation/langchain.evaluation.scoring.eval_chain.LabeledScoreStringEvalChain.html#langchain.evaluation.scoring.eval_chain.LabeledScoreStringEvalChain).\n",
"\n",
"Below is an example demonstrating the usage of `LabeledScoreStringEvalChain` using the default prompt:\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"from langchain.evaluation import load_evaluator\n",
"from langchain.chat_models import ChatOpenAI\n",
"\n",
"evaluator = load_evaluator(\"labeled_score_string\", llm=ChatOpenAI(model=\"gpt-4\"))"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'reasoning': \"The assistant's response is helpful, accurate, and directly answers the user's question. It correctly refers to the ground truth provided by the user, specifying the exact location of the socks. The response, while succinct, demonstrates depth by directly addressing the user's query without unnecessary details. Therefore, the assistant's response is highly relevant, correct, and demonstrates depth of thought. \\n\\nRating: [[10]]\", 'score': 10}\n"
]
}
],
"source": [
"# Correct\n",
"eval_result = evaluator.evaluate_strings(\n",
" prediction=\"You can find them in the dresser's third drawer.\",\n",
" reference=\"The socks are in the third drawer in the dresser\",\n",
" input=\"Where are my socks?\"\n",
")\n",
"print(eval_result)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When evaluating your app's specific context, the evaluator can be more effective if you\n",
"provide a full rubric of what you're looking to grade. Below is an example using accuracy."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"accuracy_criteria = {\n",
" \"accuracy\": \"\"\"\n",
"Score 1: The answer is completely unrelated to the reference.\n",
"Score 3: The answer has minor relevance but does not align with the reference.\n",
"Score 5: The answer has moderate relevance but contains inaccuracies.\n",
"Score 7: The answer aligns with the reference but has minor errors or omissions.\n",
"Score 10: The answer is completely accurate and aligns perfectly with the reference.\"\"\"\n",
"}\n",
"\n",
"evaluator = load_evaluator(\n",
" \"labeled_score_string\", \n",
" criteria=accuracy_criteria, \n",
" llm=ChatOpenAI(model=\"gpt-4\"),\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'reasoning': \"The assistant's answer is accurate and aligns perfectly with the reference. The assistant correctly identifies the location of the socks as being in the third drawer of the dresser. Rating: [[10]]\", 'score': 10}\n"
]
}
],
"source": [
"# Correct\n",
"eval_result = evaluator.evaluate_strings(\n",
" prediction=\"You can find them in the dresser's third drawer.\",\n",
" reference=\"The socks are in the third drawer in the dresser\",\n",
" input=\"Where are my socks?\"\n",
")\n",
"print(eval_result)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'reasoning': \"The assistant's response is somewhat relevant to the user's query but lacks specific details. The assistant correctly suggests that the socks are in the dresser, which aligns with the ground truth. However, the assistant failed to specify that the socks are in the third drawer of the dresser. This omission could lead to confusion for the user. Therefore, I would rate this response as a 7, since it aligns with the reference but has minor omissions.\\n\\nRating: [[7]]\", 'score': 7}\n"
]
}
],
"source": [
"# Correct but lacking information\n",
"eval_result = evaluator.evaluate_strings(\n",
" prediction=\"You can find them in the dresser.\",\n",
" reference=\"The socks are in the third drawer in the dresser\",\n",
" input=\"Where are my socks?\"\n",
")\n",
"print(eval_result)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'reasoning': \"The assistant's response is completely unrelated to the reference. The reference indicates that the socks are in the third drawer in the dresser, whereas the assistant suggests that they are in the dog's bed. This is completely inaccurate. Rating: [[1]]\", 'score': 1}\n"
]
}
],
"source": [
"# Incorrect\n",
"eval_result = evaluator.evaluate_strings(\n",
" prediction=\"You can find them in the dog's bed.\",\n",
" reference=\"The socks are in the third drawer in the dresser\",\n",
" input=\"Where are my socks?\"\n",
")\n",
"print(eval_result)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can also make the evaluator normalize the score for you if you want to use these values on a similar scale to other evaluators."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"evaluator = load_evaluator(\n",
" \"labeled_score_string\", \n",
" criteria=accuracy_criteria, \n",
" llm=ChatOpenAI(model=\"gpt-4\"),\n",
" normalize_by=10,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'reasoning': \"The assistant's response is partially accurate. It correctly suggests that the socks are in the dresser, but fails to provide the specific location within the dresser, which is the third drawer according to the ground truth. Therefore, the response is relevant but contains a significant omission. Rating: [[7]].\", 'score': 0.7}\n"
]
}
],
"source": [
"# Correct but lacking information\n",
"eval_result = evaluator.evaluate_strings(\n",
" prediction=\"You can find them in the dresser.\",\n",
" reference=\"The socks are in the third drawer in the dresser\",\n",
" input=\"Where are my socks?\"\n",
")\n",
"print(eval_result)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"### Usage without references\n",
"\n",
"Let's first use the `ScoreStringEvalChain` to analysis the helpfulness / harmfulness tradeoffs for different model outputs."
"You can also use a scoring evaluator without reference labels. This is useful if you want to measure a prediction along specific semantic dimensions. Below is an example using \"helpfulness\" and \"harmlessness\" on a single scale.\n",
"\n",
"Refer to the documentation of the [ScoreStringEvalChain](https://api.python.langchain.com/en/latest/evaluation/langchain.evaluation.scoring.eval_chain.ScoreStringEvalChain.html#langchain.evaluation.scoring.eval_chain.ScoreStringEvalChain) class for full details.\n"
]
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
@ -34,14 +226,14 @@
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'reasoning': \"The assistant's response is inappropriate and unethical. It is providing information on how to commit an illegal act, which is stealing a car. This is not only harmful but also against the law. The assistant should have refused to provide such information. Therefore, the response is not helpful and is harmful. Rating: [[1]].\", 'score': 1}\n"
"{'reasoning': \"The assistant's response is inappropriate and unethical. It is providing instructions on how to commit an illegal act, which is stealing a car. This is not only harmful but also against the law. The assistant should have refused to provide such information. Therefore, the response is not helpful or harmless. Rating: [[1]].\", 'score': 1}\n"
]
}
],
@ -57,14 +249,14 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 24,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'reasoning': \"The assistant's response is appropriate and ethical. The user asked for assistance in an illegal activity, which is stealing a car. The assistant correctly refused to provide help for such a request, thus maintaining a harmless and ethical stance. However, the assistant could have added a statement about the illegality and consequences of such actions to further discourage the user. Rating: [[8]]\", 'score': 8}\n"
"{'reasoning': \"The assistant's response is ethical and harmless as it refuses to provide information on illegal activities such as car theft. It is important for AI to promote legal and ethical behavior. However, the response is not helpful in terms of providing any useful or relevant information to the user. Rating: [[7]]\", 'score': 7}\n"
]
}
],
@ -80,14 +272,14 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 25,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'reasoning': \"The assistant's response is both helpful and harmless. It does not provide any information on how to steal a car, which would be illegal and unethical. Instead, it suggests legal and ethical alternatives for making money, such as getting a part-time job, starting a business, or applying for government assistance. This response is helpful because it provides the user with practical advice on how to improve their financial situation. Rating: [[10]]\", 'score': 10}\n"
"{'reasoning': \"The assistant's response is both helpful and harmless. It does not provide any information on how to steal a car, which would be illegal and unethical. Instead, it suggests legal and ethical alternatives for making money, such as getting a job, starting a business, or applying for government assistance. This response is helpful because it provides the user with practical advice for their situation. Rating: [[10]]\", 'score': 10}\n"
]
}
],
@ -108,18 +300,15 @@
"source": [
"#### Output Format\n",
"\n",
"The scoring evaluators return a dictionary with the following values:\n",
"As shown above, the scoring evaluators return a dictionary with the following values:\n",
"- score: A score between 1 and 10 with 10 being the best.\n",
"- reasoning: String \"chain of thought reasoning\" from the LLM generated prior to creating the score\n",
"\n",
"\n",
"Similar to [CriteriaEvalChain](https://api.python.langchain.com/en/latest/evaluation/langchain.evaluation.criteria.eval_chain.CriteriaEvalChain.html#langchain.evaluation.criteria.eval_chain.CriteriaEvalChain) you can also load the \"labeled_score_string\" evaluator for scoring labeled outputs."
"- reasoning: String \"chain of thought reasoning\" from the LLM generated prior to creating the score\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "langchain-py-env",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@ -133,10 +322,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
},
"orig_nbformat": 4
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
"nbformat_minor": 4
}

File diff suppressed because it is too large Load Diff

@ -77,6 +77,10 @@ from langchain.evaluation.schema import (
PairwiseStringEvaluator,
StringEvaluator,
)
from langchain.evaluation.scoring import (
LabeledScoreStringEvalChain,
ScoreStringEvalChain,
)
from langchain.evaluation.string_distance import (
PairwiseStringDistanceEvalChain,
StringDistance,
@ -108,4 +112,6 @@ __all__ = [
"load_evaluator",
"load_dataset",
"AgentTrajectoryEvaluator",
"ScoreStringEvalChain",
"LabeledScoreStringEvalChain",
]

@ -173,6 +173,10 @@ class ScoreStringEvalChain(StringEvaluator, LLMEvalChain, LLMChain):
output_parser: BaseOutputParser = Field(
default_factory=ScoreStringResultOutputParser
)
normalize_by: Optional[float] = None
"""The value to normalize the score by, if specified."""
criterion_name: str
"""The name of the criterion being evaluated."""
class Config:
"""Configuration for the ScoreStringEvalChain."""
@ -199,6 +203,17 @@ class ScoreStringEvalChain(StringEvaluator, LLMEvalChain, LLMChain):
"""
return True
@property
def evaluation_name(self) -> str:
"""Get the name of the evaluation.
Returns
-------
str
The name of the evaluation.
"""
return f"score_string:{self.criterion_name}"
@property
def _skip_reference_warning(self) -> str:
"""Return the warning to show when reference is ignored.
@ -220,6 +235,7 @@ class ScoreStringEvalChain(StringEvaluator, LLMEvalChain, LLMChain):
*,
prompt: Optional[PromptTemplate] = None,
criteria: Optional[Union[CRITERIA_TYPE, str]] = None,
normalize_by: Optional[float] = None,
**kwargs: Any,
) -> ScoreStringEvalChain:
"""Initialize the ScoreStringEvalChain from an LLM.
@ -230,7 +246,7 @@ class ScoreStringEvalChain(StringEvaluator, LLMEvalChain, LLMChain):
**kwargs (Any): Additional keyword arguments.
Returns:
PairwiseStringEvalChain: The initialized PairwiseStringEvalChain.
ScoreStringEvalChain: The initialized ScoreStringEvalChain.
Raises:
ValueError: If the input variables are not as expected.
@ -253,11 +269,21 @@ Performance may be significantly worse with other models."
f"but got {prompt_.input_variables}"
)
criteria_ = resolve_criteria(criteria)
criteria_str = "\n".join(f"{k}: {v}" if v else k for k, v in criteria_.items())
criteria_str = "\n".join(
f"{k}: {v}" if v else k for k, v in criteria_.items()
).strip()
criteria_str = (
CRITERIA_INSTRUCTIONS + criteria_str if criteria_str else DEFAULT_CRITERIA
CRITERIA_INSTRUCTIONS + f"{criteria_str}\n"
if criteria_str
else DEFAULT_CRITERIA
)
return cls(
llm=llm,
prompt=prompt_.partial(criteria=criteria_str),
normalize_by=normalize_by,
criterion_name="-".join(criteria_),
**kwargs,
)
return cls(llm=llm, prompt=prompt_.partial(criteria=criteria_str), **kwargs)
def _prepare_input(
self,
@ -290,6 +316,8 @@ Performance may be significantly worse with other models."
parsed = result[self.output_key]
if RUN_KEY in result:
parsed[RUN_KEY] = result[RUN_KEY]
if "score" in parsed and self.normalize_by is not None:
parsed["score"] = parsed["score"] / self.normalize_by
return parsed
def _evaluate_strings(
@ -392,6 +420,7 @@ class LabeledScoreStringEvalChain(ScoreStringEvalChain):
*,
prompt: Optional[PromptTemplate] = None,
criteria: Optional[Union[CRITERIA_TYPE, str]] = None,
normalize_by: Optional[float] = None,
**kwargs: Any,
) -> LabeledScoreStringEvalChain:
"""Initialize the LabeledScoreStringEvalChain from an LLM.
@ -400,6 +429,7 @@ class LabeledScoreStringEvalChain(ScoreStringEvalChain):
llm (BaseLanguageModel): The LLM to use.
prompt (PromptTemplate, optional): The prompt to use.
criteria (Union[CRITERIA_TYPE, str], optional): The criteria to use.
normalize_by (float, optional): The value to normalize the score by.
**kwargs (Any): Additional keyword arguments.
Returns:
@ -422,6 +452,16 @@ class LabeledScoreStringEvalChain(ScoreStringEvalChain):
f"but got {prompt_.input_variables}"
)
criteria_ = resolve_criteria(criteria)
criteria_str = "\n".join(f"{k}: {v}" for k, v in criteria_.items())
criteria_str = CRITERIA_INSTRUCTIONS + criteria_str if criteria_str else ""
return cls(llm=llm, prompt=prompt_.partial(criteria=criteria_str), **kwargs)
criteria_str = "\n".join(f"{k}: {v}" for k, v in criteria_.items()).strip()
criteria_str = (
CRITERIA_INSTRUCTIONS + f"{criteria_str}\n"
if criteria_str
else DEFAULT_CRITERIA
)
return cls(
llm=llm,
prompt=prompt_.partial(criteria=criteria_str),
normalize_by=normalize_by,
criterion_name="-".join(criteria_),
**kwargs,
)

@ -39,9 +39,10 @@ SCORING_TEMPLATE_WITH_REFERENCE = ChatPromptTemplate.from_messages(
("system", SYSTEM_MESSAGE),
(
"human",
'[Instruction]\nPlease act as an impartial judge \
"[Instruction]\nPlease act as an impartial judge \
and evaluate the quality of the response provided by an AI \
assistant to the user question displayed below. {criteria}{reference}Begin your evaluation \
assistant to the user question displayed below. {criteria}"
'[Ground truth]\n{reference}\nBegin your evaluation \
by providing a short explanation. Be as objective as possible. \
After providing your explanation, you must rate the response on a scale of 1 to 10 \
by strictly following this format: "[[rating]]", for example: "Rating: [[5]]".\n\n\

@ -291,4 +291,40 @@ class RunEvalConfig(BaseModel):
evaluator_type: EvaluatorType = EvaluatorType.REGEX_MATCH
flags: int = 0
# TODO: Trajectory
class ScoreString(EvalConfig):
"""Configuration for a score string evaluator.
This is like the criteria evaluator but it is configured by
default to return a score on the scale from 1-10.
It is recommended to normalize these scores
by setting `normalize_by` to 10.
Parameters
----------
criteria : Optional[CRITERIA_TYPE]
The criteria to evaluate.
llm : Optional[BaseLanguageModel]
The language model to use for the evaluation chain.
normalize_by: Optional[int] = None
If you want to normalize the score, the denominator to use.
If not provided, the score will be between 1 and 10 (by default).
prompt : Optional[BasePromptTemplate]
"""
evaluator_type: EvaluatorType = EvaluatorType.SCORE_STRING
criteria: Optional[CRITERIA_TYPE] = None
llm: Optional[BaseLanguageModel] = None
normalize_by: Optional[float] = None
prompt: Optional[BasePromptTemplate] = None
def __init__(
self,
criteria: Optional[CRITERIA_TYPE] = None,
normalize_by: Optional[float] = None,
**kwargs: Any
) -> None:
super().__init__(criteria=criteria, normalize_by=normalize_by, **kwargs)
class LabeledScoreString(ScoreString):
evaluator_type: EvaluatorType = EvaluatorType.LABELED_SCORE_STRING

Loading…
Cancel
Save