Source code for langchain.evaluation.embedding_distance.base
"""A chain for comparing the output of two models using embeddings."""importfunctoolsimportloggingfromenumimportEnumfromimportlibimportutilfromtypingimportAny,Optionalfromlangchain_core.callbacksimportCallbacksfromlangchain_core.callbacks.managerimport(AsyncCallbackManagerForChainRun,CallbackManagerForChainRun,)fromlangchain_core.embeddingsimportEmbeddingsfromlangchain_core.utilsimportpre_initfrompydanticimportConfigDict,Fieldfromtyping_extensionsimportoverridefromlangchain.chains.baseimportChainfromlangchain.evaluation.schemaimportPairwiseStringEvaluator,StringEvaluatorfromlangchain.schemaimportRUN_KEYdef_import_numpy()->Any:try:importnumpyasnpexceptImportErrorase:msg="Could not import numpy, please install with `pip install numpy`."raiseImportError(msg)fromereturnnplogger=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,)exceptImportErrorase:msg=("Could not import OpenAIEmbeddings. Please install the ""OpenAIEmbeddings package using `pip install langchain-openai`.")raiseImportError(msg)fromereturnOpenAIEmbeddings()
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_:msg=("Could not import OpenAIEmbeddings. Please install the ""OpenAIEmbeddings package using `pip install langchain-openai`.")raiseImportError(msg)ifisinstance(embeddings,tuple(types_)):try:importtiktoken# noqa: F401exceptImportErrorase:msg=("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`.")raiseImportError(msg)fromereturnvaluesmodel_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]msg=f"Invalid metric: {metric}"raiseValueError(msg)@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_core.vectorstores.utilsimport_cosine_similarityreturn1.0-_cosine_similarity(a,b)exceptImportError:# Fallback to scipy if availabletry:fromscipy.spatial.distanceimportcosinereturncosine(a.flatten(),b.flatten())exceptImportError:# Pure numpy fallbackif_check_numpy():np=_import_numpy()a_flat=a.flatten()b_flat=b.flatten()dot_product=np.dot(a_flat,b_flat)norm_a=np.linalg.norm(a_flat)norm_b=np.linalg.norm(b_flat)ifnorm_a==0ornorm_b==0:return0.0return1.0-(dot_product/(norm_a*norm_b))# Pure Python implementationa_flat=aifhasattr(a,"__len__")else[a]b_flat=bifhasattr(b,"__len__")else[b]ifhasattr(a,"flatten"):a_flat=a.flatten()ifhasattr(b,"flatten"):b_flat=b.flatten()dot_product=sum(x*yforx,yinzip(a_flat,b_flat))norm_a=sum(x*xforxina_flat)**0.5norm_b=sum(x*xforxinb_flat)**0.5ifnorm_a==0ornorm_b==0:return0.0return1.0-(dot_product/(norm_a*norm_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. """try:fromscipy.spatial.distanceimporteuclideanreturneuclidean(a.flatten(),b.flatten())exceptImportError: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. """try:fromscipy.spatial.distanceimportcityblockreturncityblock(a.flatten(),b.flatten())exceptImportError: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. """try:fromscipy.spatial.distanceimportchebyshevreturnchebyshev(a.flatten(),b.flatten())exceptImportError: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. """try:fromscipy.spatial.distanceimporthammingreturnhamming(a.flatten(),b.flatten())exceptImportError: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):"""Embedding distance evaluation chain. 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@property@overridedefevaluation_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"]@overridedef_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}@overrideasyncdef_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}@overridedef_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: The output string from the first model. reference: The output string from the second model. callbacks: The callbacks to use. tags: The tags to apply. metadata: The metadata to use. include_run_info: Whether to include run information in the output. **kwargs: Additional keyword arguments. Returns: 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)@overrideasyncdef_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:"""Evaluate the embedding distance between a prediction and reference. Args: prediction: The output string from the first model. reference: The output string from the second model. callbacks: The callbacks to use. tags: The tags to apply. metadata: The metadata to use. include_run_info: Whether to include run information in the output. **kwargs: Additional keyword arguments. Returns: 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:"""Return the evaluation name."""returnf"pairwise_embedding_{self.distance_metric.value}_distance"@overridedef_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}@overrideasyncdef_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}@overridedef_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: The output string from the first model. prediction_b: The output string from the second model. callbacks: The callbacks to use. tags: The tags to apply. metadata: The metadata to use. include_run_info: Whether to include run information in the output. **kwargs: Additional keyword arguments. Returns: 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)@overrideasyncdef_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: The output string from the first model. prediction_b: The output string from the second model. callbacks: The callbacks to use. tags: The tags to apply. metadata: The metadata to use. include_run_info: Whether to include run information in the output. **kwargs: Additional keyword arguments. Returns: 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)