Source code for langchain.evaluation.embedding_distance.base
"""A chain for comparing the output of two models using embeddings."""importfunctoolsimportloggingfromenumimportEnumfromimportlibimportutilfromtypingimportAny,Dict,List,Optionalfromlangchain_core.callbacks.managerimport(AsyncCallbackManagerForChainRun,CallbackManagerForChainRun,Callbacks,)fromlangchain_core.embeddingsimportEmbeddingsfromlangchain_core.utilsimportpre_initfrompydanticimportConfigDict,Fieldfromlangchain.chains.baseimportChainfromlangchain.evaluation.schemaimportPairwiseStringEvaluator,StringEvaluatorfromlangchain.schemaimportRUN_KEYdef_import_numpy()->Any:try:importnumpyasnpreturnnpexceptImportErrorase:raiseImportError("Could not import numpy, please install with `pip install numpy`.")fromelogger=logging.getLogger(__name__)@functools.lru_cache(maxsize=1)def_check_numpy()->bool:ifbool(util.find_spec("numpy")):returnTruelogger.warning("NumPy not found in the current Python environment. ""langchain will use a pure Python implementation for embedding distance ""operations, which may significantly impact performance, especially for large ""datasets. For optimal speed and efficiency, consider installing NumPy: ""pip install numpy")returnFalsedef_embedding_factory()->Embeddings:"""Create an Embeddings object. Returns: Embeddings: The created Embeddings object. """# Here for backwards compatibility.# Generally, we do not want to be seeing imports from langchain community# or partner packages in langchain.try:fromlangchain_openaiimportOpenAIEmbeddingsexceptImportError:try:fromlangchain_community.embeddings.openaiimport(# type: ignore[no-redef]OpenAIEmbeddings,)exceptImportError:raiseImportError("Could not import OpenAIEmbeddings. Please install the ""OpenAIEmbeddings package using `pip install langchain-openai`.")returnOpenAIEmbeddings()
class_EmbeddingDistanceChainMixin(Chain):"""Shared functionality for embedding distance evaluators. Attributes: embeddings (Embeddings): The embedding objects to vectorize the outputs. distance_metric (EmbeddingDistance): The distance metric to use for comparing the embeddings. """embeddings:Embeddings=Field(default_factory=_embedding_factory)distance_metric:EmbeddingDistance=Field(default=EmbeddingDistance.COSINE)@pre_initdef_validate_tiktoken_installed(cls,values:Dict[str,Any])->Dict[str,Any]:"""Validate that the TikTok library is installed. Args: values (Dict[str, Any]): The values to validate. Returns: Dict[str, Any]: The validated values. """embeddings=values.get("embeddings")types_=[]try:fromlangchain_openaiimportOpenAIEmbeddingstypes_.append(OpenAIEmbeddings)exceptImportError:passtry:fromlangchain_community.embeddings.openaiimport(# type: ignore[no-redef]OpenAIEmbeddings,)types_.append(OpenAIEmbeddings)exceptImportError:passifnottypes_:raiseImportError("Could not import OpenAIEmbeddings. Please install the ""OpenAIEmbeddings package using `pip install langchain-openai`.")ifisinstance(embeddings,tuple(types_)):try:importtiktoken# noqa: F401exceptImportError:raiseImportError("The tiktoken library is required to use the default ""OpenAI embeddings with embedding distance evaluators."" Please either manually select a different Embeddings object"" or install tiktoken using `pip install tiktoken`.")returnvaluesmodel_config=ConfigDict(arbitrary_types_allowed=True,)@propertydefoutput_keys(self)->List[str]:"""Return the output keys of the chain. Returns: List[str]: The output keys. """return["score"]def_prepare_output(self,result:dict)->dict:parsed={"score":result["score"]}ifRUN_KEYinresult:parsed[RUN_KEY]=result[RUN_KEY]returnparseddef_get_metric(self,metric:EmbeddingDistance)->Any:"""Get the metric function for the given metric name. Args: metric (EmbeddingDistance): The metric name. Returns: Any: The metric function. """metrics={EmbeddingDistance.COSINE:self._cosine_distance,EmbeddingDistance.EUCLIDEAN:self._euclidean_distance,EmbeddingDistance.MANHATTAN:self._manhattan_distance,EmbeddingDistance.CHEBYSHEV:self._chebyshev_distance,EmbeddingDistance.HAMMING:self._hamming_distance,}ifmetricinmetrics:returnmetrics[metric]else:raiseValueError(f"Invalid metric: {metric}")@staticmethoddef_cosine_distance(a:Any,b:Any)->Any:"""Compute the cosine distance between two vectors. Args: a (np.ndarray): The first vector. b (np.ndarray): The second vector. Returns: np.ndarray: The cosine distance. """try:fromlangchain_community.utils.mathimportcosine_similarityexceptImportError:raiseImportError("The cosine_similarity function is required to compute cosine distance."" Please install the langchain-community package using"" `pip install langchain-community`.")return1.0-cosine_similarity(a,b)@staticmethoddef_euclidean_distance(a:Any,b:Any)->Any:"""Compute the Euclidean distance between two vectors. Args: a (np.ndarray): The first vector. b (np.ndarray): The second vector. Returns: np.floating: The Euclidean distance. """if_check_numpy():importnumpyasnpreturnnp.linalg.norm(a-b)returnsum((x-y)*(x-y)forx,yinzip(a,b))**0.5@staticmethoddef_manhattan_distance(a:Any,b:Any)->Any:"""Compute the Manhattan distance between two vectors. Args: a (np.ndarray): The first vector. b (np.ndarray): The second vector. Returns: np.floating: The Manhattan distance. """if_check_numpy():np=_import_numpy()returnnp.sum(np.abs(a-b))returnsum(abs(x-y)forx,yinzip(a,b))@staticmethoddef_chebyshev_distance(a:Any,b:Any)->Any:"""Compute the Chebyshev distance between two vectors. Args: a (np.ndarray): The first vector. b (np.ndarray): The second vector. Returns: np.floating: The Chebyshev distance. """if_check_numpy():np=_import_numpy()returnnp.max(np.abs(a-b))returnmax(abs(x-y)forx,yinzip(a,b))@staticmethoddef_hamming_distance(a:Any,b:Any)->Any:"""Compute the Hamming distance between two vectors. Args: a (np.ndarray): The first vector. b (np.ndarray): The second vector. Returns: np.floating: The Hamming distance. """if_check_numpy():np=_import_numpy()returnnp.mean(a!=b)returnsum(1forx,yinzip(a,b)ifx!=y)/len(a)def_compute_score(self,vectors:Any)->float:"""Compute the score based on the distance metric. Args: vectors (np.ndarray): The input vectors. Returns: float: The computed score. """metric=self._get_metric(self.distance_metric)if_check_numpy()andisinstance(vectors,_import_numpy().ndarray):score=metric(vectors[0].reshape(1,-1),vectors[1].reshape(1,-1)).item()else:score=metric(vectors[0],vectors[1])returnfloat(score)
[docs]classEmbeddingDistanceEvalChain(_EmbeddingDistanceChainMixin,StringEvaluator):"""Use embedding distances to score semantic difference between a prediction and reference. Examples: >>> chain = EmbeddingDistanceEvalChain() >>> result = chain.evaluate_strings(prediction="Hello", reference="Hi") >>> print(result) {'score': 0.5} """@propertydefrequires_reference(self)->bool:"""Return whether the chain requires a reference. Returns: bool: True if a reference is required, False otherwise. """returnTrue@propertydefevaluation_name(self)->str:returnf"embedding_{self.distance_metric.value}_distance"@propertydefinput_keys(self)->List[str]:"""Return the input keys of the chain. Returns: List[str]: The input keys. """return["prediction","reference"]def_call(self,inputs:Dict[str,Any],run_manager:Optional[CallbackManagerForChainRun]=None,)->Dict[str,Any]:"""Compute the score for a prediction and reference. Args: inputs (Dict[str, Any]): The input data. run_manager (Optional[CallbackManagerForChainRun], optional): The callback manager. Returns: Dict[str, Any]: The computed score. """vectors=self.embeddings.embed_documents([inputs["prediction"],inputs["reference"]])if_check_numpy():np=_import_numpy()vectors=np.array(vectors)score=self._compute_score(vectors)return{"score":score}asyncdef_acall(self,inputs:Dict[str,Any],run_manager:Optional[AsyncCallbackManagerForChainRun]=None,)->Dict[str,Any]:"""Asynchronously compute the score for a prediction and reference. Args: inputs (Dict[str, Any]): The input data. run_manager (AsyncCallbackManagerForChainRun, optional): The callback manager. Returns: Dict[str, Any]: The computed score. """vectors=awaitself.embeddings.aembed_documents([inputs["prediction"],inputs["reference"],])if_check_numpy():np=_import_numpy()vectors=np.array(vectors)score=self._compute_score(vectors)return{"score":score}def_evaluate_strings(self,*,prediction:str,reference:Optional[str]=None,callbacks:Callbacks=None,tags:Optional[List[str]]=None,metadata:Optional[Dict[str,Any]]=None,include_run_info:bool=False,**kwargs:Any,)->dict:"""Evaluate the embedding distance between a prediction and reference. Args: prediction (str): The output string from the first model. reference (str): The reference string (required) callbacks (Callbacks, optional): The callbacks to use. **kwargs (Any): Additional keyword arguments. Returns: dict: A dictionary containing: - score: The embedding distance between the two predictions. """result=self(inputs={"prediction":prediction,"reference":reference},callbacks=callbacks,tags=tags,metadata=metadata,include_run_info=include_run_info,)returnself._prepare_output(result)asyncdef_aevaluate_strings(self,*,prediction:str,reference:Optional[str]=None,callbacks:Callbacks=None,tags:Optional[List[str]]=None,metadata:Optional[Dict[str,Any]]=None,include_run_info:bool=False,**kwargs:Any,)->dict:"""Asynchronously evaluate the embedding distance between a prediction and reference. Args: prediction (str): The output string from the first model. reference (str): The output string from the second model. callbacks (Callbacks, optional): The callbacks to use. **kwargs (Any): Additional keyword arguments. Returns: dict: A dictionary containing: - score: The embedding distance between the two predictions. """result=awaitself.acall(inputs={"prediction":prediction,"reference":reference},callbacks=callbacks,tags=tags,metadata=metadata,include_run_info=include_run_info,)returnself._prepare_output(result)
[docs]classPairwiseEmbeddingDistanceEvalChain(_EmbeddingDistanceChainMixin,PairwiseStringEvaluator):"""Use embedding distances to score semantic difference between two predictions. Examples: >>> chain = PairwiseEmbeddingDistanceEvalChain() >>> result = chain.evaluate_string_pairs(prediction="Hello", prediction_b="Hi") >>> print(result) {'score': 0.5} """@propertydefinput_keys(self)->List[str]:"""Return the input keys of the chain. Returns: List[str]: The input keys. """return["prediction","prediction_b"]@propertydefevaluation_name(self)->str:returnf"pairwise_embedding_{self.distance_metric.value}_distance"def_call(self,inputs:Dict[str,Any],run_manager:Optional[CallbackManagerForChainRun]=None,)->Dict[str,Any]:"""Compute the score for two predictions. Args: inputs (Dict[str, Any]): The input data. run_manager (CallbackManagerForChainRun, optional): The callback manager. Returns: Dict[str, Any]: The computed score. """vectors=self.embeddings.embed_documents([inputs["prediction"],inputs["prediction_b"],])if_check_numpy():np=_import_numpy()vectors=np.array(vectors)score=self._compute_score(vectors)return{"score":score}asyncdef_acall(self,inputs:Dict[str,Any],run_manager:Optional[AsyncCallbackManagerForChainRun]=None,)->Dict[str,Any]:"""Asynchronously compute the score for two predictions. Args: inputs (Dict[str, Any]): The input data. run_manager (AsyncCallbackManagerForChainRun, optional): The callback manager. Returns: Dict[str, Any]: The computed score. """vectors=awaitself.embeddings.aembed_documents([inputs["prediction"],inputs["prediction_b"],])if_check_numpy():np=_import_numpy()vectors=np.array(vectors)score=self._compute_score(vectors)return{"score":score}def_evaluate_string_pairs(self,*,prediction:str,prediction_b:str,callbacks:Callbacks=None,tags:Optional[List[str]]=None,metadata:Optional[Dict[str,Any]]=None,include_run_info:bool=False,**kwargs:Any,)->dict:"""Evaluate the embedding distance between two predictions. Args: prediction (str): The output string from the first model. prediction_b (str): The output string from the second model. callbacks (Callbacks, optional): The callbacks to use. tags (List[str], optional): Tags to apply to traces metadata (Dict[str, Any], optional): metadata to apply to **kwargs (Any): Additional keyword arguments. Returns: dict: A dictionary containing: - score: The embedding distance between the two predictions. """result=self(inputs={"prediction":prediction,"prediction_b":prediction_b},callbacks=callbacks,tags=tags,metadata=metadata,include_run_info=include_run_info,)returnself._prepare_output(result)asyncdef_aevaluate_string_pairs(self,*,prediction:str,prediction_b:str,callbacks:Callbacks=None,tags:Optional[List[str]]=None,metadata:Optional[Dict[str,Any]]=None,include_run_info:bool=False,**kwargs:Any,)->dict:"""Asynchronously evaluate the embedding distance between two predictions. Args: prediction (str): The output string from the first model. prediction_b (str): The output string from the second model. callbacks (Callbacks, optional): The callbacks to use. tags (List[str], optional): Tags to apply to traces metadata (Dict[str, Any], optional): metadata to apply to traces **kwargs (Any): Additional keyword arguments. Returns: dict: A dictionary containing: - score: The embedding distance between the two predictions. """result=awaitself.acall(inputs={"prediction":prediction,"prediction_b":prediction_b},callbacks=callbacks,tags=tags,metadata=metadata,include_run_info=include_run_info,)returnself._prepare_output(result)